{"thread":{"id":"59629","subject":"[PATCH 00/14] [RFC] config: remove global state from config iteration","startedAt":"2023-04-21T19:13:26Z","lastAt":"2023-07-11T18:41:36Z","messageCount":115,"participants":["Glen Choo via GitGitGadget","Ævar Arnfjörð Bjarmason","Jonathan Tan","Glen Choo","Phillip Wood","Junio C Hamano","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":14},"messages":[{"id":"475799","messageId":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":null,"subject":"[PATCH 00/14] [RFC] config: remove global state from config iteration","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:04Z","receivedAt":"2023-04-21T19:13:26Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"= Description\n\nThis series removes all global state from config iteration, i.e. parsing\nconfig and iterating configsets, by passing config metadata as a \"struct\nkey_value_info\" arg to the \"config_fn_t\" callbacks. This allows us to get\nrid of:\n\n * \"the_reader\" (formerly \"cf\", \"current_parsing_scope\",\n   \"current_config_kvi\"), and the config.c machinery that maintained it.\n   This only needed to be global because it was read by...\n * The \"current_*\" functions that read metadata about the 'current' config\n   value (\"current_config_scope\", \"current_config_name\", etc), which are\n   replaced by reading values from the \"struct key_value_info\" passed to the\n   callbacks.\n\nAs a result:\n\n * Config iterating code can be moved to into its own library with few\n   modifications. C.f. libification efforts [1].\n * The config iterating code can be safely called in parallel.\n * We expose and fix instances where the \"current_*\" functions were being\n   called outside of config callbacks.\n\nWe've had this idea of doing this \"config_fn_t\" refactor for a long time\n[2], but we've never attempted it because we wanted to avoid churn. After\nattempting it, though, I'm quite convinced that this is the right way\nforward for config, since the lack of global state makes things much easier\nto reason about. The churn is also quite manageable:\n\n * The vast majority of changes can be handled with cocci.\n * The few cases that aren't covered by cocci have obvious fixes.\n * The change is simple enough for in-flight topics to perform and conflicts\n   will be caught at build time anyway.\n * Anecdotally, we don't change config functions often anyway. This merges\n   cleanly with 'seen' and the result passes CI.\n\n= Patch overview\n\nI've arranged the patches in a way that makes them readable, but is\nunsuitable for merging, hence the RFC tag. The \"Post-RFC cleanups\" section\ndescribes all the changes I'll make prior to submitting this as non-RFC.\n\n1-5/14 converts the config.c machinery to set and make use of\n\"config_reader.config_kvi\" instead of \"config_reader.source\" and\n\"config_reader.scope\", which makes it easier to pass \"struct key_value_info\"\nto config callbacks. To minimize churn, I opted to 'de-plumb' \"struct\nconfig_reader\" here instead of plumbing it and removing it later.\n\n6-10/14 actually changes \"config_fn_t\"'s signature. The earlier patches are\nminor refactors that make the cocci application + adjustment easier. Post\nRFC, cocci application + adjustment will be a single patch, but for ease of\nreading I've kept them separate.\n\n11-14/14 teach the config callbacks to use the \"kvi\" parameter, replace the\nnow-obsolete \"current_*\" functions, and remove the now-obsolete \"struct\nconfig_reader\". This requires plumbing \"kvi\" through some more config.c\nmachinery and fixing a sneaky config.c-like code path in trace2/.\n\n= Post-RFC cleanups\n\n * To make future refactors easier, I'm strongly considering replacing the\n   \"struct key_value_info\" arg with something more extensible so that we can\n   modify that without changing 100+ function signatures. An alternative\n   would be to replace all of the args to config_fn_t with a single struct,\n   but that would very significantly increase churn because we'd need to\n   touch all the config_fn_t function bodies too.\n\n * Rearrange the cocci patches to avoid breaking CI in the middle of the\n   series.\n\n * Fix style issues (e.g. spacing around \"kvi\", line wrapping, \"{ 0 }\"\n   instead of dedicated initializer)\n\n= Alternatives considered\n\nÆvar suggested in [3] that we might be able to do the refactor incrementally\nby having both the old \"config_fn_t\" and the new \"config_fn_t with kvi\",\nwhich lets us convert some of config iterating (e.g. configsets) without\ntouching the others (e.g. config parsing). I experimented with that for a\nbit, and it turned out that doing it all at once is actually less work\nbecause we don't have to worry about the case where the same \"config_fn_t\"\nis used in both git_config() and git_config_from_file().\n\nJonathan Tan suggested in [4] that to reduce churn, we might be able to\nconvert many of the config_fn_t-s to the config set API before attempting\nthis refactor. But (hopefully), these patches show that the churn is\nmanageable even without this preparatory step.\n\n[1]\nhttps://lore.kernel.org/git/CAJoAoZ=Cig_kLocxKGax31sU7Xe4==BGzC__Bg2_pr7krNq6MA@mail.gmail.com/\n[2]\nhttps://lore.kernel.org/git/CAPc5daV6bdUKS-ExHmpT4Ppy2S832NXoyPw7aOLP7fG=WrBPgg@mail.gmail.com/\n[3]\nhttps://lore.kernel.org/git/RFC-patch-5.5-2b80d293c83-20230317T042408Z-avarab@gmail.com\n[4]\nhttps://lore.kernel.org/git/20230306195756.3399115-1-jonathantanmy@google.com/\n\nGlen Choo (14):\n  config.c: introduce kvi_fn(), use it for configsets\n  config.c: use kvi for CLI config\n  config: use kvi for config files\n  config: add kvi.path, use it to evaluate includes\n  config: pass source to config_parser_event_fn_t\n  config: inline git_color_default_config\n  urlmatch.h: use config_fn_t type\n  (RFC-only) config: add kvi arg to config_fn_t\n  (RFC-only) config: apply cocci to config_fn_t implementations\n  (RFC-only) config: finish config_fn_t refactor\n  config: remove current_config_(line|name|origin_type)\n  config: remove current_config_scope()\n  config: pass kvi to die_bad_number()\n  config: remove config_reader from configset_add_value\n\n alias.c                                       |   3 +-\n archive-tar.c                                 |   5 +-\n archive-zip.c                                 |   1 +\n builtin/add.c                                 |   5 +-\n builtin/blame.c                               |   5 +-\n builtin/branch.c                              |   8 +-\n builtin/cat-file.c                            |   5 +-\n builtin/checkout.c                            |   7 +-\n builtin/clean.c                               |   9 +-\n builtin/clone.c                               |  10 +-\n builtin/column.c                              |   3 +-\n builtin/commit-graph.c                        |   3 +-\n builtin/commit.c                              |  20 +-\n builtin/config.c                              |  59 +-\n builtin/difftool.c                            |   5 +-\n builtin/fetch.c                               |  12 +-\n builtin/fsmonitor--daemon.c                   |  11 +-\n builtin/grep.c                                |  12 +-\n builtin/help.c                                |   5 +-\n builtin/index-pack.c                          |   9 +-\n builtin/log.c                                 |  12 +-\n builtin/merge.c                               |   7 +-\n builtin/multi-pack-index.c                    |   1 +\n builtin/pack-objects.c                        |  19 +-\n builtin/patch-id.c                            |   5 +-\n builtin/pull.c                                |   5 +-\n builtin/push.c                                |   5 +-\n builtin/read-tree.c                           |   5 +-\n builtin/rebase.c                              |   5 +-\n builtin/receive-pack.c                        |  15 +-\n builtin/reflog.c                              |   7 +-\n builtin/remote.c                              |  12 +-\n builtin/repack.c                              |   5 +-\n builtin/reset.c                               |   5 +-\n builtin/send-pack.c                           |   5 +-\n builtin/show-branch.c                         |   8 +-\n builtin/stash.c                               |   5 +-\n builtin/submodule--helper.c                   |   3 +-\n builtin/tag.c                                 |   9 +-\n builtin/var.c                                 |   5 +-\n builtin/worktree.c                            |   5 +-\n bundle-uri.c                                  |   9 +-\n color.c                                       |   8 -\n color.h                                       |   6 +-\n compat/mingw.c                                |   3 +-\n compat/mingw.h                                |   4 +-\n config.c                                      | 540 +++++++-----------\n config.h                                      |  56 +-\n connect.c                                     |   4 +-\n .../coccinelle/config_fn_kvi.pending.cocci    | 146 +++++\n contrib/coccinelle/git_config_number.cocci    |  27 +\n convert.c                                     |   4 +-\n credential.c                                  |   1 +\n delta-islands.c                               |   3 +-\n diff.c                                        |  19 +-\n diff.h                                        |   7 +-\n fetch-pack.c                                  |   5 +-\n fmt-merge-msg.c                               |   7 +-\n fmt-merge-msg.h                               |   3 +-\n fsck.c                                        |  11 +-\n fsck.h                                        |   4 +-\n git-compat-util.h                             |   2 +\n gpg-interface.c                               |   6 +-\n grep.c                                        |   7 +-\n grep.h                                        |   4 +-\n help.c                                        |  10 +-\n http.c                                        |  15 +-\n ident.c                                       |   3 +-\n ident.h                                       |   4 +-\n imap-send.c                                   |   7 +-\n ll-merge.c                                    |   1 +\n ls-refs.c                                     |   2 +-\n mailinfo.c                                    |   5 +-\n notes-utils.c                                 |   3 +-\n notes.c                                       |   3 +-\n pager.c                                       |   5 +-\n pretty.c                                      |   1 +\n promisor-remote.c                             |   4 +-\n remote.c                                      |   7 +-\n revision.c                                    |   3 +-\n scalar.c                                      |   3 +-\n sequencer.c                                   |  28 +-\n setup.c                                       |  17 +-\n submodule-config.c                            |  31 +-\n submodule-config.h                            |   3 +-\n t/helper/test-config.c                        |  21 +-\n t/helper/test-userdiff.c                      |   4 +-\n trace2.c                                      |   4 +-\n trace2.h                                      |   3 +-\n trace2/tr2_cfg.c                              |  12 +-\n trace2/tr2_sysenv.c                           |   3 +-\n trace2/tr2_tgt.h                              |   4 +-\n trace2/tr2_tgt_event.c                        |   4 +-\n trace2/tr2_tgt_normal.c                       |   4 +-\n trace2/tr2_tgt_perf.c                         |   4 +-\n trailer.c                                     |   2 +\n upload-pack.c                                 |  18 +-\n urlmatch.c                                    |   7 +-\n urlmatch.h                                    |   8 +-\n worktree.c                                    |   2 +-\n xdiff-interface.c                             |   5 +-\n xdiff-interface.h                             |   5 +-\n 102 files changed, 855 insertions(+), 641 deletions(-)\n create mode 100644 contrib/coccinelle/config_fn_kvi.pending.cocci\n create mode 100644 contrib/coccinelle/git_config_number.cocci\n\n\nbase-commit: 9857273be005833c71e2d16ba48e193113e12276\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1497%2Fchooglen%2Fconfig%2Fno-global-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1497/chooglen/config/no-global-v1\nPull-Request: https://github.com/git/git/pull/1497\n-- \ngitgitgadget\n"},{"id":"475800","messageId":"49bc2f6eedc923be631379c9b65956855ec25f35.1682104398.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 01/14] config.c: introduce kvi_fn(), use it for configsets","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:05Z","receivedAt":"2023-04-21T19:13:27Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nIn order to pass \"struct key_value_info\" to config callbacks in a future\ncommit, we want to make sure that a \"struct key_value_info\" is available\nin all code paths that invoke a config callback.\n\nEnforce this by introducing a helper function, \"kvi_fn()\", that sets\n\"the_reader.config_kvi\" before invoking the config callback, and\ntrivially use it in \"configset_iter()\". This closely simulates passing\nan extra \"struct key_value_info\" such that making the switch will be\ntrivial.\n\nThis breaks our rule that no underlying machinery uses \"the_reader\", but\n\"struct config_reader\" will be gone by the end of the series so there's\nno point plumbing it as an arg.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 32 ++++++++++++++++++++------------\n 1 file changed, 20 insertions(+), 12 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 493f47df8ae..daad892e4fd 100644\n--- a/config.c\n+++ b/config.c\n@@ -660,6 +660,17 @@ out_free_ret_1:\n \treturn -CONFIG_INVALID_KEY;\n }\n \n+static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n+\t\t  struct key_value_info *kvi,\n+\t\t  void *data)\n+{\n+\tint ret;\n+\tconfig_reader_set_kvi(&the_reader, kvi);\n+\tret = fn(key, value, data);\n+\tconfig_reader_set_kvi(&the_reader, NULL);\n+\treturn ret;\n+}\n+\n static int config_parse_pair(const char *key, const char *value,\n \t\t\t  config_fn_t fn, void *data)\n {\n@@ -2288,27 +2299,24 @@ int config_with_options(config_fn_t fn, void *data,\n \treturn ret;\n }\n \n-static void configset_iter(struct config_reader *reader, struct config_set *set,\n-\t\t\t   config_fn_t fn, void *data)\n+static void configset_iter(struct config_set *set, config_fn_t fn, void *data)\n {\n \tint i, value_index;\n \tstruct string_list *values;\n \tstruct config_set_element *entry;\n \tstruct configset_list *list = &set->list;\n+\tstruct key_value_info *kvi;\n \n \tfor (i = 0; i < list->nr; i++) {\n \t\tentry = list->items[i].e;\n \t\tvalue_index = list->items[i].value_index;\n \t\tvalues = &entry->value_list;\n+\t\tkvi = values->items[value_index].util;\n \n-\t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n-\n-\t\tif (fn(entry->key, values->items[value_index].string, data) < 0)\n-\t\t\tgit_die_config_linenr(entry->key,\n-\t\t\t\t\t      reader->config_kvi->filename,\n-\t\t\t\t\t      reader->config_kvi->linenr);\n-\n-\t\tconfig_reader_set_kvi(reader, NULL);\n+\t\tif (kvi_fn(fn, entry->key, values->items[value_index].string,\n+\t\t\t   kvi, data) < 0)\n+\t\t\tgit_die_config_linenr(entry->key, kvi->filename,\n+\t\t\t\t\t      kvi->linenr);\n \t}\n }\n \n@@ -2696,7 +2704,7 @@ static void repo_config_clear(struct repository *repo)\n void repo_config(struct repository *repo, config_fn_t fn, void *data)\n {\n \tgit_config_check_init(repo);\n-\tconfigset_iter(&the_reader, repo->config, fn, data);\n+\tconfigset_iter(repo->config, fn, data);\n }\n \n int repo_config_get(struct repository *repo, const char *key)\n@@ -2815,7 +2823,7 @@ void git_protected_config(config_fn_t fn, void *data)\n {\n \tif (!protected_config.hash_initialized)\n \t\tread_protected_config();\n-\tconfigset_iter(&the_reader, &protected_config, fn, data);\n+\tconfigset_iter(&protected_config, fn, data);\n }\n \n /* Functions used historically to read configuration from 'the_repository' */\n-- \ngitgitgadget\n\n"},{"id":"475801","messageId":"a682612cff23449e682023303f17a8be1d73fc31.1682104398.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 02/14] config.c: use kvi for CLI config","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:06Z","receivedAt":"2023-04-21T19:13:30Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPlumb \"struct key_value_info\" and use \"kvi_fn()\" when parsing CLI\nconfig. Do this by refactoring out and reusing the logic that sets the\n\"struct key_value_info\" members when caching CLI config in a configset.\n\nThis lets us get rid of the fake \"struct config_source\" in\n\"git_config_from_parameters()\", so we now only have to maintain one\nimplementation. Additionally, this plumbing also reveals that\n\"git_config_parse_parameter()\" hasn't been setting either\n\"the_reader.source\" or \"the_reader.config_kvi\", so any calls to\n\"current_*\" would either BUG() or return *_UNKNOWN values.\n\nAlso, get rid of the BUG() checks that forbid setting \".config_kvi\" and\n\".source\" at the same time, since we will run afoul of that check. They\nwill soon be unnecessary when we remove \".source\".\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 58 ++++++++++++++++++++++++++++----------------------------\n 1 file changed, 29 insertions(+), 29 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex daad892e4fd..4e9e8e7abb9 100644\n--- a/config.c\n+++ b/config.c\n@@ -99,8 +99,6 @@ static struct config_reader the_reader;\n static inline void config_reader_push_source(struct config_reader *reader,\n \t\t\t\t\t     struct config_source *top)\n {\n-\tif (reader->config_kvi)\n-\t\tBUG(\"source should not be set while iterating a config set\");\n \ttop->prev = reader->source;\n \treader->source = top;\n }\n@@ -118,16 +116,12 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\n static inline void config_reader_set_kvi(struct config_reader *reader,\n \t\t\t\t\t struct key_value_info *kvi)\n {\n-\tif (kvi && (reader->source || reader->parsing_scope))\n-\t\tBUG(\"kvi should not be set while parsing a config source\");\n \treader->config_kvi = kvi;\n }\n \n static inline void config_reader_set_scope(struct config_reader *reader,\n \t\t\t\t\t   enum config_scope scope)\n {\n-\tif (scope && reader->config_kvi)\n-\t\tBUG(\"scope should only be set when iterating through a config source\");\n \treader->parsing_scope = scope;\n }\n \n@@ -672,7 +666,8 @@ static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n }\n \n static int config_parse_pair(const char *key, const char *value,\n-\t\t\t  config_fn_t fn, void *data)\n+\t\t\t     struct key_value_info *kvi,\n+\t\t\t     config_fn_t fn, void *data)\n {\n \tchar *canonical_name;\n \tint ret;\n@@ -682,17 +677,30 @@ static int config_parse_pair(const char *key, const char *value,\n \tif (git_config_parse_key(key, &canonical_name, NULL))\n \t\treturn -1;\n \n-\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n+\tret = (kvi_fn(fn, canonical_name, value, kvi, data) < 0) ? -1 : 0;\n \tfree(canonical_name);\n \treturn ret;\n }\n \n+\n+/* for values read from `git_config_from_parameters()` */\n+static void kvi_from_param(struct key_value_info *out)\n+{\n+\tout->filename = NULL;\n+\tout->linenr = -1;\n+\tout->origin_type = CONFIG_ORIGIN_CMDLINE;\n+\tout->scope = CONFIG_SCOPE_COMMAND;\n+}\n+\n int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n \tconst char *value;\n \tstruct strbuf **pair;\n \tint ret;\n+\tstruct key_value_info kvi = { 0 };\n+\n+\tkvi_from_param(&kvi);\n \n \tpair = strbuf_split_str(text, '=', 2);\n \tif (!pair[0])\n@@ -711,12 +719,13 @@ int git_config_parse_parameter(const char *text,\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \t}\n \n-\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n+\tret = config_parse_pair(pair[0]->buf, value, &kvi, fn, data);\n \tstrbuf_list_free(pair);\n \treturn ret;\n }\n \n-static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n+static int parse_config_env_list(char *env, struct key_value_info *kvi,\n+\t\t\t\t config_fn_t fn, void *data)\n {\n \tchar *cur = env;\n \twhile (cur && *cur) {\n@@ -750,7 +759,7 @@ static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n \t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n \t\t\t}\n \n-\t\t\tif (config_parse_pair(key, value, fn, data) < 0)\n+\t\t\tif (config_parse_pair(key, value, kvi, fn, data) < 0)\n \t\t\t\treturn -1;\n \t\t}\n \t\telse {\n@@ -774,11 +783,9 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tstruct strvec to_free = STRVEC_INIT;\n \tint ret = 0;\n \tchar *envw = NULL;\n-\tstruct config_source source = CONFIG_SOURCE_INIT;\n-\n-\tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n-\tconfig_reader_push_source(&the_reader, &source);\n+\tstruct key_value_info kvi = { 0 };\n \n+\tkvi_from_param(&kvi);\n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n \t\tunsigned long count;\n@@ -814,7 +821,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \t\t\t}\n \t\t\tstrbuf_reset(&envvar);\n \n-\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n+\t\t\tif (config_parse_pair(key, value, &kvi, fn, data) < 0) {\n \t\t\t\tret = -1;\n \t\t\t\tgoto out;\n \t\t\t}\n@@ -825,7 +832,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tif (env) {\n \t\t/* sq_dequote will write over it */\n \t\tenvw = xstrdup(env);\n-\t\tif (parse_config_env_list(envw, fn, data) < 0) {\n+\t\tif (parse_config_env_list(envw, &kvi, fn, data) < 0) {\n \t\t\tret = -1;\n \t\t\tgoto out;\n \t\t}\n@@ -835,7 +842,6 @@ out:\n \tstrbuf_release(&envvar);\n \tstrvec_clear(&to_free);\n \tfree(envw);\n-\tconfig_reader_pop_source(&the_reader);\n \treturn ret;\n }\n \n@@ -2241,7 +2247,7 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\tfree(path);\n \t}\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);\n+\tconfig_reader_set_scope(reader, 0);\n \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n \t\tdie(_(\"unable to parse command-line config\"));\n \n@@ -2423,19 +2429,13 @@ static int configset_add_value(struct config_reader *reader,\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (!reader->source)\n-\t\tBUG(\"configset_add_value has no source\");\n-\tif (reader->source->name) {\n+\tif (reader->source && reader->source->name) {\n \t\tkv_info->filename = strintern(reader->source->name);\n \t\tkv_info->linenr = reader->source->linenr;\n \t\tkv_info->origin_type = reader->source->origin_type;\n-\t} else {\n-\t\t/* for values read from `git_config_from_parameters()` */\n-\t\tkv_info->filename = NULL;\n-\t\tkv_info->linenr = -1;\n-\t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n-\t}\n-\tkv_info->scope = reader->parsing_scope;\n+\t\tkv_info->scope = reader->parsing_scope;\n+\t} else\n+\t\tkvi_from_param(kv_info);\n \tsi->util = kv_info;\n \n \treturn 0;\n-- \ngitgitgadget\n\n"},{"id":"475802","messageId":"cb0218106888f0dc27890ba875dd415e1304a654.1682104398.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 06/14] config: inline git_color_default_config","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:10Z","receivedAt":"2023-04-21T19:13:31Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\ngit_color_default_config() is a shorthand for calling two other config\ncallbacks. There are no other non-static functions that do this and it\nwill complicate our refactoring of config_fn_t so inline it instead.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/branch.c      | 5 ++++-\n builtin/clean.c       | 6 ++++--\n builtin/grep.c        | 5 ++++-\n builtin/show-branch.c | 5 ++++-\n builtin/tag.c         | 6 +++++-\n color.c               | 8 --------\n color.h               | 6 +-----\n 7 files changed, 22 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 6413a016c57..c6982181fd5 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -114,7 +114,10 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static const char *branch_get_color(enum color_branch ix)\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 14c0d555eac..a06df48a269 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -129,8 +129,10 @@ static int git_clean_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\t/* inspect the color.ui config variable and others */\n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static const char *clean_get_color(enum color_clean ix)\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex a1b68d90bdb..c880c9538d6 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -290,7 +290,10 @@ static int wait_all(void)\n static int grep_cmd_config(const char *var, const char *value, void *cb)\n {\n \tint st = grep_config(var, value, cb);\n-\tif (git_color_default_config(var, value, NULL) < 0)\n+\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\tst = -1;\n+\telse if (git_default_config(var, value, cb) < 0)\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex 463a8d11c31..82ae2a7e475 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -576,7 +576,10 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static int omit_in_dense(struct commit *commit, struct commit **rev, int n)\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 782bb3aa2ff..7245a4d30e6 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -204,7 +204,11 @@ static int git_tag_config(const char *var, const char *value, void *cb)\n \n \tif (starts_with(var, \"column.\"))\n \t\treturn git_column_config(var, value, \"tag\", &colopts);\n-\treturn git_color_default_config(var, value, cb);\n+\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static void write_tag_body(int fd, const struct object_id *oid)\ndiff --git a/color.c b/color.c\nindex 672dcbb73a6..9bdbffe928d 100644\n--- a/color.c\n+++ b/color.c\n@@ -427,14 +427,6 @@ int git_color_config(const char *var, const char *value, void *cb UNUSED)\n \treturn 0;\n }\n \n-int git_color_default_config(const char *var, const char *value, void *cb)\n-{\n-\tif (git_color_config(var, value, cb) < 0)\n-\t\treturn -1;\n-\n-\treturn git_default_config(var, value, cb);\n-}\n-\n void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb)\n {\n \tif (*color)\ndiff --git a/color.h b/color.h\nindex cfc8f841b23..bb28343be21 100644\n--- a/color.h\n+++ b/color.h\n@@ -88,12 +88,8 @@ extern const int column_colors_ansi_max;\n  */\n extern int color_stdout_is_tty;\n \n-/*\n- * Use the first one if you need only color config; the second is a convenience\n- * if you are just going to change to git_default_config, too.\n- */\n+/* Parse color config. */\n int git_color_config(const char *var, const char *value, void *cb);\n-int git_color_default_config(const char *var, const char *value, void *cb);\n \n /*\n  * Parse a config option, which can be a boolean or one of\n-- \ngitgitgadget\n\n"},{"id":"475803","messageId":"8c7a84137c8b1c9acccd573c3c7bdc0ee027e0b0.1682104398.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 03/14] config: use kvi for config files","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:07Z","receivedAt":"2023-04-21T19:13:38Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPlumb \"struct key_value_info\" and use \"kvi_fn()\" when parsing config\nfiles. As a result, \"config_reader.kvi\" is now always set correctly, so\nwe can remove \"config_reader.scope\" (but not the \".source\" member since\nthat's still needed by some non-parsing machinery). This requires\nplumbing an additional \"enum config_scope\" arg through\n\"git_config_from_file_with_options()\" and the underlying machinery to\nmake up for the fact that \"struct key_value_info\" has a \".scope\" member,\nbut \"struct config_source\" does not.\n\nTo handle \"include\" directives correctly, use push/pop semantics for\n\"config_reader.config_kvi\" (instead of \"set\" semantics) like we do for\n\"config_reader.source\". Otherwise, \"config_reader.config_kvi\" won't be\nset correctly when we finish parsing an included config file and we want\nto \"pop\" it to resume parsing the original file. This distinction only\nmatters while there is a global \"kvi\", i.e. it will be obsolete at the\nend of the series.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n bundle-uri.c       |   1 +\n config.c           | 171 ++++++++++++++++++++++++---------------------\n config.h           |   9 ++-\n fsck.c             |   3 +-\n submodule-config.c |   5 +-\n 5 files changed, 104 insertions(+), 85 deletions(-)\n\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex e2b267cc02b..8c4e2b70b89 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -252,6 +252,7 @@ int bundle_uri_parse_config_format(const char *uri,\n \t}\n \tresult = git_config_from_file_with_options(config_to_bundle_list,\n \t\t\t\t\t\t   filename, list,\n+\t\t\t\t\t\t   CONFIG_SCOPE_UNKNOWN,\n \t\t\t\t\t\t   &opts);\n \n \tif (!result && list->mode == BUNDLE_MODE_NONE) {\ndiff --git a/config.c b/config.c\nindex 4e9e8e7abb9..3369b32d065 100644\n--- a/config.c\n+++ b/config.c\n@@ -78,16 +78,6 @@ struct config_reader {\n \t */\n \tstruct config_source *source;\n \tstruct key_value_info *config_kvi;\n-\t/*\n-\t * The \"scope\" of the current config source being parsed (repo, global,\n-\t * etc). Like \"source\", this is only set when parsing a config source.\n-\t * It's not part of \"source\" because it transcends a single file (i.e.,\n-\t * a file included from .git/config is still in \"repo\" scope).\n-\t *\n-\t * When iterating through a configset, the equivalent value is\n-\t * \"config_kvi.scope\" (see above).\n-\t */\n-\tenum config_scope parsing_scope;\n };\n /*\n  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n@@ -113,16 +103,21 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\n \treturn ret;\n }\n \n-static inline void config_reader_set_kvi(struct config_reader *reader,\n-\t\t\t\t\t struct key_value_info *kvi)\n+static inline void config_reader_push_kvi(struct config_reader *reader,\n+\t\t\t\t\t  struct key_value_info *kvi)\n {\n+\tkvi->prev = reader->config_kvi;\n \treader->config_kvi = kvi;\n }\n \n-static inline void config_reader_set_scope(struct config_reader *reader,\n-\t\t\t\t\t   enum config_scope scope)\n+static inline struct key_value_info *config_reader_pop_kvi(struct config_reader *reader)\n {\n-\treader->parsing_scope = scope;\n+\tstruct key_value_info *ret;\n+\tif (!reader->config_kvi)\n+\t\tBUG(\"tried to pop config_kvi, but we weren't reading config\");\n+\tret = reader->config_kvi;\n+\treader->config_kvi = reader->config_kvi->prev;\n+\treturn ret;\n }\n \n static int pack_compression_seen;\n@@ -244,7 +239,9 @@ static int handle_path_include(struct config_source *cs, const char *path,\n \t\t\t    !cs ? \"<unknown>\" :\n \t\t\t    cs->name ? cs->name :\n \t\t\t    \"the command line\");\n-\t\tret = git_config_from_file(git_config_include, path, inc);\n+\t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n+\t\t\t\t\t\t\tcurrent_config_scope(),\n+\t\t\t\t\t\t\tNULL);\n \t\tinc->depth--;\n \t}\n cleanup:\n@@ -393,18 +390,12 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tenum config_scope store_scope = inc->config_reader->parsing_scope;\n-\n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tconfig_reader_set_scope(inc->config_reader, 0);\n-\n \tinc->remote_urls = xmalloc(sizeof(*inc->remote_urls));\n \tstring_list_init_dup(inc->remote_urls);\n \tconfig_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);\n-\n-\tconfig_reader_set_scope(inc->config_reader, store_scope);\n }\n \n static int forbid_remote_url(const char *var, const char *value UNUSED,\n@@ -471,10 +462,13 @@ static int include_condition_is_true(struct config_source *cs,\n \treturn 0;\n }\n \n+static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n+\t\t  struct key_value_info *kvi, void *data);\n static int git_config_include(const char *var, const char *value, void *data)\n {\n \tstruct config_include_data *inc = data;\n \tstruct config_source *cs = inc->config_reader->source;\n+\tstruct key_value_info *kvi = inc->config_reader->config_kvi;\n \tconst char *cond, *key;\n \tsize_t cond_len;\n \tint ret;\n@@ -483,7 +477,7 @@ static int git_config_include(const char *var, const char *value, void *data)\n \t * Pass along all values, including \"include\" directives; this makes it\n \t * possible to query information on the includes themselves.\n \t */\n-\tret = inc->fn(var, value, inc->data);\n+\tret = kvi_fn(inc->fn, var, value, kvi, inc->data);\n \tif (ret < 0)\n \t\treturn ret;\n \n@@ -655,13 +649,12 @@ out_free_ret_1:\n }\n \n static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n-\t\t  struct key_value_info *kvi,\n-\t\t  void *data)\n+\t\t  struct key_value_info *kvi, void *data)\n {\n \tint ret;\n-\tconfig_reader_set_kvi(&the_reader, kvi);\n+\tconfig_reader_push_kvi(&the_reader, kvi);\n \tret = fn(key, value, data);\n-\tconfig_reader_set_kvi(&the_reader, NULL);\n+\tconfig_reader_pop_kvi(&the_reader);\n \treturn ret;\n }\n \n@@ -942,8 +935,8 @@ static char *parse_value(struct config_source *cs)\n \t}\n }\n \n-static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n-\t\t     struct strbuf *name)\n+static int get_value(struct config_source *cs, struct key_value_info *kvi,\n+\t\t     config_fn_t fn, void *data, struct strbuf *name)\n {\n \tint c;\n \tchar *value;\n@@ -976,7 +969,8 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \t * accurate line number in error messages.\n \t */\n \tcs->linenr--;\n-\tret = fn(name->buf, value, data);\n+\tkvi->linenr = cs->linenr;\n+\tret = kvi_fn(fn, name->buf, value, kvi, data);\n \tif (ret >= 0)\n \t\tcs->linenr++;\n \treturn ret;\n@@ -1075,8 +1069,19 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \treturn 0;\n }\n \n+static void kvi_from_source(struct config_source *cs,\n+\t\t\t    enum config_scope scope,\n+\t\t\t    struct key_value_info *out)\n+{\n+\tout->filename = strintern(cs->name);\n+\tout->origin_type = cs->origin_type;\n+\tout->linenr = cs->linenr;\n+\tout->scope = scope;\n+}\n+\n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n-\t\t\t    void *data, const struct config_options *opts)\n+\t\t\t    struct key_value_info *kvi, void *data,\n+\t\t\t    const struct config_options *opts)\n {\n \tint comment = 0;\n \tsize_t baselen = 0;\n@@ -1160,7 +1165,7 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t */\n \t\tstrbuf_setlen(var, baselen);\n \t\tstrbuf_addch(var, tolower(c));\n-\t\tif (get_value(cs, fn, data, var) < 0)\n+\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n \t\t\tbreak;\n \t}\n \n@@ -2016,9 +2021,11 @@ int git_default_config(const char *var, const char *value, void *cb)\n  * this function.\n  */\n static int do_config_from(struct config_reader *reader,\n-\t\t\t  struct config_source *top, config_fn_t fn, void *data,\n+\t\t\t  struct config_source *top, config_fn_t fn,\n+\t\t\t  void *data, enum config_scope scope,\n \t\t\t  const struct config_options *opts)\n {\n+\tstruct key_value_info kvi = { 0 };\n \tint ret;\n \n \t/* push config-file parsing state stack */\n@@ -2028,8 +2035,9 @@ static int do_config_from(struct config_reader *reader,\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n \tconfig_reader_push_source(reader, top);\n+\tkvi_from_source(top, scope, &kvi);\n \n-\tret = git_parse_source(top, fn, data, opts);\n+\tret = git_parse_source(top, fn, &kvi, data, opts);\n \n \t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n@@ -2043,7 +2051,8 @@ static int do_config_from_file(struct config_reader *reader,\n \t\t\t       config_fn_t fn,\n \t\t\t       const enum config_origin_type origin_type,\n \t\t\t       const char *name, const char *path, FILE *f,\n-\t\t\t       void *data, const struct config_options *opts)\n+\t\t\t       void *data, enum config_scope scope,\n+\t\t\t       const struct config_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n@@ -2058,19 +2067,21 @@ static int do_config_from_file(struct config_reader *reader,\n \ttop.do_ftell = config_file_ftell;\n \n \tflockfile(f);\n-\tret = do_config_from(reader, &top, fn, data, opts);\n+\tret = do_config_from(reader, &top, fn, data, scope, opts);\n \tfunlockfile(f);\n+\n \treturn ret;\n }\n \n-static int git_config_from_stdin(config_fn_t fn, void *data)\n+static int git_config_from_stdin(config_fn_t fn, void *data,\n+\t\t\t\t enum config_scope scope)\n {\n \treturn do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, \"\",\n-\t\t\t\t   NULL, stdin, data, NULL);\n+\t\t\t\t   NULL, stdin, data, scope, NULL);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n-\t\t\t\t      void *data,\n+\t\t\t\t      void *data, enum config_scope scope,\n \t\t\t\t      const struct config_options *opts)\n {\n \tint ret = -1;\n@@ -2081,7 +2092,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \tf = fopen_or_warn(filename, \"r\");\n \tif (f) {\n \t\tret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,\n-\t\t\t\t\t  filename, filename, f, data, opts);\n+\t\t\t\t\t  filename, filename, f, data, scope,\n+\t\t\t\t\t  opts);\n \t\tfclose(f);\n \t}\n \treturn ret;\n@@ -2089,13 +2101,15 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \n int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n {\n-\treturn git_config_from_file_with_options(fn, filename, data, NULL);\n+\treturn git_config_from_file_with_options(fn, filename, data,\n+\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, NULL);\n }\n \n int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type origin_type,\n \t\t\tconst char *name, const char *buf, size_t len,\n-\t\t\tvoid *data, const struct config_options *opts)\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \n@@ -2110,14 +2124,15 @@ int git_config_from_mem(config_fn_t fn,\n \ttop.do_ungetc = config_buf_ungetc;\n \ttop.do_ftell = config_buf_ftell;\n \n-\treturn do_config_from(&the_reader, &top, fn, data, opts);\n+\treturn do_config_from(&the_reader, &top, fn, data, scope, opts);\n }\n \n int git_config_from_blob_oid(config_fn_t fn,\n \t\t\t      const char *name,\n \t\t\t      struct repository *repo,\n \t\t\t      const struct object_id *oid,\n-\t\t\t      void *data)\n+\t\t\t      void *data,\n+\t\t\t      enum config_scope scope)\n {\n \tenum object_type type;\n \tchar *buf;\n@@ -2133,7 +2148,7 @@ int git_config_from_blob_oid(config_fn_t fn,\n \t}\n \n \tret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size,\n-\t\t\t\t  data, NULL);\n+\t\t\t\t  data, scope, NULL);\n \tfree(buf);\n \n \treturn ret;\n@@ -2142,13 +2157,14 @@ int git_config_from_blob_oid(config_fn_t fn,\n static int git_config_from_blob_ref(config_fn_t fn,\n \t\t\t\t    struct repository *repo,\n \t\t\t\t    const char *name,\n-\t\t\t\t    void *data)\n+\t\t\t\t    void *data,\n+\t\t\t\t    enum config_scope scope)\n {\n \tstruct object_id oid;\n \n \tif (repo_get_oid(repo, name, &oid) < 0)\n \t\treturn error(_(\"unable to resolve config blob '%s'\"), name);\n-\treturn git_config_from_blob_oid(fn, name, repo, &oid, data);\n+\treturn git_config_from_blob_oid(fn, name, repo, &oid, data, scope);\n }\n \n char *git_system_config(void)\n@@ -2201,8 +2217,7 @@ int git_config_system(void)\n \treturn !git_env_bool(\"GIT_CONFIG_NOSYSTEM\", 0);\n }\n \n-static int do_git_config_sequence(struct config_reader *reader,\n-\t\t\t\t  const struct config_options *opts,\n+static int do_git_config_sequence(const struct config_options *opts,\n \t\t\t\t  config_fn_t fn, void *data)\n {\n \tint ret = 0;\n@@ -2210,7 +2225,6 @@ static int do_git_config_sequence(struct config_reader *reader,\n \tchar *xdg_config = NULL;\n \tchar *user_config = NULL;\n \tchar *repo_config;\n-\tenum config_scope prev_parsing_scope = reader->parsing_scope;\n \n \tif (opts->commondir)\n \t\trepo_config = mkpathdup(\"%s/config\", opts->commondir);\n@@ -2219,39 +2233,40 @@ static int do_git_config_sequence(struct config_reader *reader,\n \telse\n \t\trepo_config = NULL;\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_SYSTEM);\n \tif (git_config_system() && system_config &&\n \t    !access_or_die(system_config, R_OK,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n-\t\tret += git_config_from_file(fn, system_config, data);\n+\t\tret += git_config_from_file_with_options(fn, system_config,\n+\t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n+\t\t\t\t\t\t\t NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);\n \tgit_global_config(&user_config, &xdg_config);\n \n \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file(fn, xdg_config, data);\n+\t\tret += git_config_from_file_with_options(fn, xdg_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n \tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file(fn, user_config, data);\n+\t\tret += git_config_from_file_with_options(fn, user_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);\n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n-\t\tret += git_config_from_file(fn, repo_config, data);\n+\t\tret += git_config_from_file_with_options(fn, repo_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n \tif (!opts->ignore_worktree && repository_format_worktree_config) {\n \t\tchar *path = git_pathdup(\"config.worktree\");\n \t\tif (!access_or_die(path, R_OK, 0))\n-\t\t\tret += git_config_from_file(fn, path, data);\n+\t\t\tret += git_config_from_file_with_options(fn, path, data,\n+\t\t\t\t\t\t\t\t CONFIG_SCOPE_WORKTREE,\n+\t\t\t\t\t\t\t\t NULL);\n \t\tfree(path);\n \t}\n \n-\tconfig_reader_set_scope(reader, 0);\n \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n \t\tdie(_(\"unable to parse command-line config\"));\n \n-\tconfig_reader_set_scope(reader, prev_parsing_scope);\n \tfree(system_config);\n \tfree(xdg_config);\n \tfree(user_config);\n@@ -2264,7 +2279,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\t\tconst struct config_options *opts)\n {\n \tstruct config_include_data inc = CONFIG_INCLUDE_INIT;\n-\tenum config_scope prev_scope = the_reader.parsing_scope;\n \tint ret;\n \n \tif (opts->respect_includes) {\n@@ -2277,31 +2291,29 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tdata = &inc;\n \t}\n \n-\tif (config_source)\n-\t\tconfig_reader_set_scope(&the_reader, config_source->scope);\n-\n \t/*\n \t * If we have a specific filename, use it. Otherwise, follow the\n \t * regular lookup sequence.\n \t */\n \tif (config_source && config_source->use_stdin) {\n-\t\tret = git_config_from_stdin(fn, data);\n+\t\tret = git_config_from_stdin(fn, data, config_source->scope);\n \t} else if (config_source && config_source->file) {\n-\t\tret = git_config_from_file(fn, config_source->file, data);\n+\t\tret = git_config_from_file_with_options(fn, config_source->file,\n+\t\t\t\t\t\t\tdata, config_source->scope,\n+\t\t\t\t\t\t\tNULL);\n \t} else if (config_source && config_source->blob) {\n \t\tstruct repository *repo = config_source->repo ?\n \t\t\tconfig_source->repo : the_repository;\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n-\t\t\t\t\t\tdata);\n+\t\t\t\t\t       data, config_source->scope);\n \t} else {\n-\t\tret = do_git_config_sequence(&the_reader, opts, fn, data);\n+\t\tret = do_git_config_sequence(opts, fn, data);\n \t}\n \n \tif (inc.remote_urls) {\n \t\tstring_list_clear(inc.remote_urls, 0);\n \t\tFREE_AND_NULL(inc.remote_urls);\n \t}\n-\tconfig_reader_set_scope(&the_reader, prev_scope);\n \treturn ret;\n }\n \n@@ -2429,13 +2441,7 @@ static int configset_add_value(struct config_reader *reader,\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (reader->source && reader->source->name) {\n-\t\tkv_info->filename = strintern(reader->source->name);\n-\t\tkv_info->linenr = reader->source->linenr;\n-\t\tkv_info->origin_type = reader->source->origin_type;\n-\t\tkv_info->scope = reader->parsing_scope;\n-\t} else\n-\t\tkvi_from_param(kv_info);\n+\tmemcpy(kv_info, reader->config_kvi, sizeof(struct key_value_info));\n \tsi->util = kv_info;\n \n \treturn 0;\n@@ -3473,7 +3479,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \t\t */\n \t\tif (git_config_from_file_with_options(store_aux,\n \t\t\t\t\t\t      config_filename,\n-\t\t\t\t\t\t      &store, &opts)) {\n+\t\t\t\t\t\t      &store, CONFIG_SCOPE_UNKNOWN,\n+\t\t\t\t\t\t      &opts)) {\n \t\t\terror(_(\"invalid config file %s\"), config_filename);\n \t\t\tret = CONFIG_INVALID_FILE;\n \t\t\tgoto out_free;\n@@ -4018,7 +4025,13 @@ enum config_scope current_config_scope(void)\n \tif (the_reader.config_kvi)\n \t\treturn the_reader.config_kvi->scope;\n \telse\n-\t\treturn the_reader.parsing_scope;\n+\t\t/*\n+\t\t * FIXME This should be a BUG, but tr2_list_env_vars_fl is\n+\t\t * calling this outside of a config callback. This will be\n+\t\t * easier to fix when we plumb kvi through the config callbacks,\n+\t\t * so leave this untouched for now.\n+\t\t */\n+\t\treturn CONFIG_SCOPE_UNKNOWN;\n }\n \n int current_config_line(void)\ndiff --git a/config.h b/config.h\nindex 247b572b37b..27d1718ac7b 100644\n--- a/config.h\n+++ b/config.h\n@@ -142,16 +142,18 @@ int git_default_config(const char *, const char *, void *);\n int git_config_from_file(config_fn_t fn, const char *, void *);\n \n int git_config_from_file_with_options(config_fn_t fn, const char *,\n-\t\t\t\t      void *,\n+\t\t\t\t      void *, enum config_scope,\n \t\t\t\t      const struct config_options *);\n int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type,\n \t\t\tconst char *name,\n \t\t\tconst char *buf, size_t len,\n-\t\t\tvoid *data, const struct config_options *opts);\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_options *opts);\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     struct repository *repo,\n-\t\t\t     const struct object_id *oid, void *data);\n+\t\t\t     const struct object_id *oid, void *data,\n+\t\t\t     enum config_scope scope);\n void git_config_push_parameter(const char *text);\n void git_config_push_env(const char *spec);\n int git_config_from_parameters(config_fn_t fn, void *data);\n@@ -672,6 +674,7 @@ struct key_value_info {\n \tint linenr;\n \tenum config_origin_type origin_type;\n \tenum config_scope scope;\n+\tstruct key_value_info *prev;\n };\n \n /**\ndiff --git a/fsck.c b/fsck.c\nindex 8ef1b022346..4238344ed82 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1235,7 +1235,8 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\tdata.ret = 0;\n \t\tconfig_opts.error_action = CONFIG_ERROR_SILENT;\n \t\tif (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB,\n-\t\t\t\t\t\".gitmodules\", buf, size, &data, &config_opts))\n+\t\t\t\t\t\".gitmodules\", buf, size, &data,\n+\t\t\t\t\tCONFIG_SCOPE_UNKNOWN, &config_opts))\n \t\t\tdata.ret |= report(options, oid, OBJ_BLOB,\n \t\t\t\t\tFSCK_MSG_GITMODULES_PARSE,\n \t\t\t\t\t\"could not parse gitmodules blob\");\ndiff --git a/submodule-config.c b/submodule-config.c\nindex ecf0fcf0074..c2f71f0b2e3 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -603,7 +603,7 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \tparameter.gitmodules_oid = &oid;\n \tparameter.overwrite = 0;\n \tgit_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf,\n-\t\t\tconfig, config_size, &parameter, NULL);\n+\t\t\t    config, config_size, &parameter, CONFIG_SCOPE_UNKNOWN, NULL);\n \tstrbuf_release(&rev);\n \tfree(config);\n \n@@ -711,7 +711,8 @@ void gitmodules_config_oid(const struct object_id *commit_oid)\n \n \tif (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) {\n \t\tgit_config_from_blob_oid(gitmodules_cb, rev.buf,\n-\t\t\t\t\t the_repository, &oid, the_repository);\n+\t\t\t\t\t the_repository, &oid, the_repository,\n+\t\t\t\t\t CONFIG_SCOPE_UNKNOWN);\n \t}\n \tstrbuf_release(&rev);\n \n-- \ngitgitgadget\n\n"},{"id":"475804","messageId":"b407fe1ed1d9cf4d9d79f900c48864fe4eab9568.1682104398.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 04/14] config: add kvi.path, use it to evaluate includes","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:08Z","receivedAt":"2023-04-21T19:13:40Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nInclude directives are evaluated using the path of the config file. To\nreduce the dependence on \"config_reader.source\", add a new\n\"key_value_info.path\" member and use that instead of\n\"config_source.path\".\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 40 ++++++++++++++++++++--------------------\n config.h |  1 +\n 2 files changed, 21 insertions(+), 20 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 3369b32d065..b952a4b118d 100644\n--- a/config.c\n+++ b/config.c\n@@ -199,7 +199,7 @@ static const char include_depth_advice[] = N_(\n \"from\\n\"\n \"\t%s\\n\"\n \"This might be due to circular includes.\");\n-static int handle_path_include(struct config_source *cs, const char *path,\n+static int handle_path_include(struct key_value_info *kvi, const char *path,\n \t\t\t       struct config_include_data *inc)\n {\n \tint ret = 0;\n@@ -221,14 +221,14 @@ static int handle_path_include(struct config_source *cs, const char *path,\n \tif (!is_absolute_path(path)) {\n \t\tchar *slash;\n \n-\t\tif (!cs || !cs->path) {\n+\t\tif (!kvi || !kvi->path) {\n \t\t\tret = error(_(\"relative config includes must come from files\"));\n \t\t\tgoto cleanup;\n \t\t}\n \n-\t\tslash = find_last_dir_sep(cs->path);\n+\t\tslash = find_last_dir_sep(kvi->path);\n \t\tif (slash)\n-\t\t\tstrbuf_add(&buf, cs->path, slash - cs->path + 1);\n+\t\t\tstrbuf_add(&buf, kvi->path, slash - kvi->path + 1);\n \t\tstrbuf_addstr(&buf, path);\n \t\tpath = buf.buf;\n \t}\n@@ -236,12 +236,11 @@ static int handle_path_include(struct config_source *cs, const char *path,\n \tif (!access_or_die(path, R_OK, 0)) {\n \t\tif (++inc->depth > MAX_INCLUDE_DEPTH)\n \t\t\tdie(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,\n-\t\t\t    !cs ? \"<unknown>\" :\n-\t\t\t    cs->name ? cs->name :\n+\t\t\t    !kvi ? \"<unknown>\" :\n+\t\t\t    kvi->filename ? kvi->filename :\n \t\t\t    \"the command line\");\n \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n-\t\t\t\t\t\t\tcurrent_config_scope(),\n-\t\t\t\t\t\t\tNULL);\n+\t\t\t\t\t\t\tkvi->scope, NULL);\n \t\tinc->depth--;\n \t}\n cleanup:\n@@ -256,7 +255,7 @@ static void add_trailing_starstar_for_dir(struct strbuf *pat)\n \t\tstrbuf_addstr(pat, \"**\");\n }\n \n-static int prepare_include_condition_pattern(struct config_source *cs,\n+static int prepare_include_condition_pattern(struct key_value_info *kvi,\n \t\t\t\t\t     struct strbuf *pat)\n {\n \tstruct strbuf path = STRBUF_INIT;\n@@ -273,11 +272,11 @@ static int prepare_include_condition_pattern(struct config_source *cs,\n \tif (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {\n \t\tconst char *slash;\n \n-\t\tif (!cs || !cs->path)\n+\t\tif (!kvi || !kvi->path)\n \t\t\treturn error(_(\"relative config include \"\n \t\t\t\t       \"conditionals must come from files\"));\n \n-\t\tstrbuf_realpath(&path, cs->path, 1);\n+\t\tstrbuf_realpath(&path, kvi->path, 1);\n \t\tslash = find_last_dir_sep(path.buf);\n \t\tif (!slash)\n \t\t\tBUG(\"how is this possible?\");\n@@ -292,7 +291,7 @@ static int prepare_include_condition_pattern(struct config_source *cs,\n \treturn prefix;\n }\n \n-static int include_by_gitdir(struct config_source *cs,\n+static int include_by_gitdir(struct key_value_info *kvi,\n \t\t\t     const struct config_options *opts,\n \t\t\t     const char *cond, size_t cond_len, int icase)\n {\n@@ -309,7 +308,7 @@ static int include_by_gitdir(struct config_source *cs,\n \n \tstrbuf_realpath(&text, git_dir, 1);\n \tstrbuf_add(&pattern, cond, cond_len);\n-\tprefix = prepare_include_condition_pattern(cs, &pattern);\n+\tprefix = prepare_include_condition_pattern(kvi, &pattern);\n \n again:\n \tif (prefix < 0)\n@@ -442,16 +441,16 @@ static int include_by_remote_url(struct config_include_data *inc,\n \t\t\t\t\t     inc->remote_urls);\n }\n \n-static int include_condition_is_true(struct config_source *cs,\n+static int include_condition_is_true(struct key_value_info *kvi,\n \t\t\t\t     struct config_include_data *inc,\n \t\t\t\t     const char *cond, size_t cond_len)\n {\n \tconst struct config_options *opts = inc->opts;\n \n \tif (skip_prefix_mem(cond, cond_len, \"gitdir:\", &cond, &cond_len))\n-\t\treturn include_by_gitdir(cs, opts, cond, cond_len, 0);\n+\t\treturn include_by_gitdir(kvi, opts, cond, cond_len, 0);\n \telse if (skip_prefix_mem(cond, cond_len, \"gitdir/i:\", &cond, &cond_len))\n-\t\treturn include_by_gitdir(cs, opts, cond, cond_len, 1);\n+\t\treturn include_by_gitdir(kvi, opts, cond, cond_len, 1);\n \telse if (skip_prefix_mem(cond, cond_len, \"onbranch:\", &cond, &cond_len))\n \t\treturn include_by_branch(cond, cond_len);\n \telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.*.url:\", &cond,\n@@ -467,7 +466,6 @@ static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n static int git_config_include(const char *var, const char *value, void *data)\n {\n \tstruct config_include_data *inc = data;\n-\tstruct config_source *cs = inc->config_reader->source;\n \tstruct key_value_info *kvi = inc->config_reader->config_kvi;\n \tconst char *cond, *key;\n \tsize_t cond_len;\n@@ -482,16 +480,16 @@ static int git_config_include(const char *var, const char *value, void *data)\n \t\treturn ret;\n \n \tif (!strcmp(var, \"include.path\"))\n-\t\tret = handle_path_include(cs, value, inc);\n+\t\tret = handle_path_include(kvi, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n-\t    cond && include_condition_is_true(cs, inc, cond, cond_len) &&\n+\t    cond && include_condition_is_true(kvi, inc, cond, cond_len) &&\n \t    !strcmp(key, \"path\")) {\n \t\tconfig_fn_t old_fn = inc->fn;\n \n \t\tif (inc->opts->unconditional_remote_url)\n \t\t\tinc->fn = forbid_remote_url;\n-\t\tret = handle_path_include(cs, value, inc);\n+\t\tret = handle_path_include(kvi, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -683,6 +681,7 @@ static void kvi_from_param(struct key_value_info *out)\n \tout->linenr = -1;\n \tout->origin_type = CONFIG_ORIGIN_CMDLINE;\n \tout->scope = CONFIG_SCOPE_COMMAND;\n+\tout->path = NULL;\n }\n \n int git_config_parse_parameter(const char *text,\n@@ -1077,6 +1076,7 @@ static void kvi_from_source(struct config_source *cs,\n \tout->origin_type = cs->origin_type;\n \tout->linenr = cs->linenr;\n \tout->scope = scope;\n+\tout->path = cs->path;\n }\n \n static int git_parse_source(struct config_source *cs, config_fn_t fn,\ndiff --git a/config.h b/config.h\nindex 27d1718ac7b..f8bab9fdef4 100644\n--- a/config.h\n+++ b/config.h\n@@ -674,6 +674,7 @@ struct key_value_info {\n \tint linenr;\n \tenum config_origin_type origin_type;\n \tenum config_scope scope;\n+\tconst char *path;\n \tstruct key_value_info *prev;\n };\n \n-- \ngitgitgadget\n\n"},{"id":"475805","messageId":"e80d1b5f4832edb9bd58b6024e3541a34e36f4e9.1682104398.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 05/14] config: pass source to config_parser_event_fn_t","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:09Z","receivedAt":"2023-04-21T19:13:43Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\n..so that the callback can use a \"struct config_source\" parameter\ninstead of \"config_reader.source\". \"struct config_source\" is internal to\nconfig.c, but this refactor is okay because this function has only ever\nbeen (and probably ever will be) used internally by config.c.\n\nThis removes the last user of \"config_reader.source\", so remove it too.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 72 ++++++++++----------------------------------------------\n config.h |  6 +++++\n 2 files changed, 18 insertions(+), 60 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex b952a4b118d..147e422a27b 100644\n--- a/config.c\n+++ b/config.c\n@@ -60,23 +60,6 @@ struct config_source {\n #define CONFIG_SOURCE_INIT { 0 }\n \n struct config_reader {\n-\t/*\n-\t * These members record the \"current\" config source, which can be\n-\t * accessed by parsing callbacks.\n-\t *\n-\t * The \"source\" variable will be non-NULL only when we are actually\n-\t * parsing a real config source (file, blob, cmdline, etc).\n-\t *\n-\t * The \"config_kvi\" variable will be non-NULL only when we are feeding\n-\t * cached config from a configset into a callback.\n-\t *\n-\t * They cannot be non-NULL at the same time. If they are both NULL, then\n-\t * we aren't parsing anything (and depending on the function looking at\n-\t * the variables, it's either a bug for it to be called in the first\n-\t * place, or it's a function which can be reused for non-config\n-\t * purposes, and should fall back to some sane behavior).\n-\t */\n-\tstruct config_source *source;\n \tstruct key_value_info *config_kvi;\n };\n /*\n@@ -86,23 +69,6 @@ struct config_reader {\n  */\n static struct config_reader the_reader;\n \n-static inline void config_reader_push_source(struct config_reader *reader,\n-\t\t\t\t\t     struct config_source *top)\n-{\n-\ttop->prev = reader->source;\n-\treader->source = top;\n-}\n-\n-static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n-{\n-\tstruct config_source *ret;\n-\tif (!reader->source)\n-\t\tBUG(\"tried to pop config source, but we weren't reading config\");\n-\tret = reader->source;\n-\treader->source = reader->source->prev;\n-\treturn ret;\n-}\n-\n static inline void config_reader_push_kvi(struct config_reader *reader,\n \t\t\t\t\t  struct key_value_info *kvi)\n {\n@@ -1059,7 +1025,7 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \n \tif (data->previous_type != CONFIG_EVENT_EOF &&\n \t    data->opts->event_fn(data->previous_type, data->previous_offset,\n-\t\t\t\t offset, data->opts->event_fn_data) < 0)\n+\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n \t\treturn -1;\n \n \tdata->previous_type = type;\n@@ -2020,8 +1986,7 @@ int git_default_config(const char *var, const char *value, void *cb)\n  * fgetc, ungetc, ftell of top need to be initialized before calling\n  * this function.\n  */\n-static int do_config_from(struct config_reader *reader,\n-\t\t\t  struct config_source *top, config_fn_t fn,\n+static int do_config_from(struct config_source *top, config_fn_t fn,\n \t\t\t  void *data, enum config_scope scope,\n \t\t\t  const struct config_options *opts)\n {\n@@ -2034,21 +1999,17 @@ static int do_config_from(struct config_reader *reader,\n \ttop->total_len = 0;\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n-\tconfig_reader_push_source(reader, top);\n \tkvi_from_source(top, scope, &kvi);\n \n \tret = git_parse_source(top, fn, &kvi, data, opts);\n \n-\t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n \tstrbuf_release(&top->var);\n-\tconfig_reader_pop_source(reader);\n \n \treturn ret;\n }\n \n-static int do_config_from_file(struct config_reader *reader,\n-\t\t\t       config_fn_t fn,\n+static int do_config_from_file(config_fn_t fn,\n \t\t\t       const enum config_origin_type origin_type,\n \t\t\t       const char *name, const char *path, FILE *f,\n \t\t\t       void *data, enum config_scope scope,\n@@ -2067,7 +2028,7 @@ static int do_config_from_file(struct config_reader *reader,\n \ttop.do_ftell = config_file_ftell;\n \n \tflockfile(f);\n-\tret = do_config_from(reader, &top, fn, data, scope, opts);\n+\tret = do_config_from(&top, fn, data, scope, opts);\n \tfunlockfile(f);\n \n \treturn ret;\n@@ -2076,8 +2037,8 @@ static int do_config_from_file(struct config_reader *reader,\n static int git_config_from_stdin(config_fn_t fn, void *data,\n \t\t\t\t enum config_scope scope)\n {\n-\treturn do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, \"\",\n-\t\t\t\t   NULL, stdin, data, scope, NULL);\n+\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n+\t\t\t\t   data, scope, NULL);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2091,9 +2052,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \t\tBUG(\"filename cannot be NULL\");\n \tf = fopen_or_warn(filename, \"r\");\n \tif (f) {\n-\t\tret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,\n-\t\t\t\t\t  filename, filename, f, data, scope,\n-\t\t\t\t\t  opts);\n+\t\tret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,\n+\t\t\t\t\t  filename, f, data, scope, opts);\n \t\tfclose(f);\n \t}\n \treturn ret;\n@@ -2124,7 +2084,7 @@ int git_config_from_mem(config_fn_t fn,\n \ttop.do_ungetc = config_buf_ungetc;\n \ttop.do_ftell = config_buf_ftell;\n \n-\treturn do_config_from(&the_reader, &top, fn, data, scope, opts);\n+\treturn do_config_from(&top, fn, data, scope, opts);\n }\n \n int git_config_from_blob_oid(config_fn_t fn,\n@@ -3019,7 +2979,6 @@ void git_die_config(const char *key, const char *err, ...)\n  */\n \n struct config_store_data {\n-\tstruct config_reader *config_reader;\n \tsize_t baselen;\n \tchar *key;\n \tint do_not_match;\n@@ -3065,11 +3024,10 @@ static int matches(const char *key, const char *value,\n \t\t(value && !regexec(store->value_pattern, value, 0, NULL, 0));\n }\n \n-static int store_aux_event(enum config_event_t type,\n-\t\t\t   size_t begin, size_t end, void *data)\n+static int store_aux_event(enum config_event_t type, size_t begin, size_t end,\n+\t\t\t   struct config_source *cs, void *data)\n {\n \tstruct config_store_data *store = data;\n-\tstruct config_source *cs = store->config_reader->source;\n \n \tALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);\n \tstore->parsed[store->parsed_nr].begin = begin;\n@@ -3389,8 +3347,6 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \tsize_t contents_sz;\n \tstruct config_store_data store = CONFIG_STORE_INIT;\n \n-\tstore.config_reader = &the_reader;\n-\n \t/* parse-key returns negative; flip the sign to feed exit(3) */\n \tret = 0 - git_config_parse_key(key, &store.key, &store.baselen);\n \tif (ret)\n@@ -3951,8 +3907,6 @@ static int reader_origin_type(struct config_reader *reader,\n {\n \tif (the_reader.config_kvi)\n \t\t*type = reader->config_kvi->origin_type;\n-\telse if(the_reader.source)\n-\t\t*type = reader->source->origin_type;\n \telse\n \t\treturn 1;\n \treturn 0;\n@@ -4005,8 +3959,6 @@ static int reader_config_name(struct config_reader *reader, const char **out)\n {\n \tif (the_reader.config_kvi)\n \t\t*out = reader->config_kvi->filename;\n-\telse if (the_reader.source)\n-\t\t*out = reader->source->name;\n \telse\n \t\treturn 1;\n \treturn 0;\n@@ -4039,7 +3991,7 @@ int current_config_line(void)\n \tif (the_reader.config_kvi)\n \t\treturn the_reader.config_kvi->linenr;\n \telse\n-\t\treturn the_reader.source->linenr;\n+\t\tBUG(\"current_config_line called outside config callback\");\n }\n \n int lookup_config(const char **mapping, int nr_mapping, const char *var)\ndiff --git a/config.h b/config.h\nindex f8bab9fdef4..525fc0d5e03 100644\n--- a/config.h\n+++ b/config.h\n@@ -73,6 +73,7 @@ enum config_event_t {\n \tCONFIG_EVENT_ERROR\n };\n \n+struct config_source;\n /*\n  * The parser event function (if not NULL) is called with the event type and\n  * the begin/end offsets of the parsed elements.\n@@ -82,6 +83,7 @@ enum config_event_t {\n  */\n typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n \t\t\t\t\tsize_t begin_offset, size_t end_offset,\n+\t\t\t\t\tstruct config_source *cs,\n \t\t\t\t\tvoid *event_fn_data);\n \n struct config_options {\n@@ -101,6 +103,10 @@ struct config_options {\n \n \tconst char *commondir;\n \tconst char *git_dir;\n+\t/*\n+\t * event_fn and event_fn_data are for internal use only. Handles events\n+\t * emitted by the config parser.\n+\t */\n \tconfig_parser_event_fn_t event_fn;\n \tvoid *event_fn_data;\n \tenum config_error_action {\n-- \ngitgitgadget\n\n"},{"id":"475806","messageId":"e0f43eafa07ae39bd6966309977cf36bc2f5131e.1682104399.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 07/14] urlmatch.h: use config_fn_t type","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:11Z","receivedAt":"2023-04-21T19:13:51Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThese are actually used as config callbacks, so use the typedef-ed type\nand make future refactors easier.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n urlmatch.h | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/urlmatch.h b/urlmatch.h\nindex 9f40b00bfb8..bee374a642c 100644\n--- a/urlmatch.h\n+++ b/urlmatch.h\n@@ -2,6 +2,7 @@\n #define URL_MATCH_H\n \n #include \"string-list.h\"\n+#include \"config.h\"\n \n struct url_info {\n \t/* normalized url on success, must be freed, otherwise NULL */\n@@ -48,8 +49,8 @@ struct urlmatch_config {\n \tconst char *key;\n \n \tvoid *cb;\n-\tint (*collect_fn)(const char *var, const char *value, void *cb);\n-\tint (*cascade_fn)(const char *var, const char *value, void *cb);\n+\tconfig_fn_t collect_fn;\n+\tconfig_fn_t cascade_fn;\n \t/*\n \t * Compare the two matches, the one just discovered and the existing\n \t * best match and return a negative value if the found item is to be\n-- \ngitgitgadget\n\n"},{"id":"475807","messageId":"961d06f89cb667c31a4a2c8f41fdd9a2bfda04f8.1682104399.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 08/14] (RFC-only) config: add kvi arg to config_fn_t","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:12Z","receivedAt":"2023-04-21T19:13:53Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\n..without actually changing any of its implementations. This commit does\nnot build - I've split this out for readability, but post-RFC I will\nsquash this with the rest of the refactor + cocci changes.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c                                      |   2 +-\n config.h                                      |  20 +--\n .../coccinelle/config_fn_kvi.pending.cocci    | 146 ++++++++++++++++++\n 3 files changed, 157 insertions(+), 11 deletions(-)\n create mode 100644 contrib/coccinelle/config_fn_kvi.pending.cocci\n\ndiff --git a/config.c b/config.c\nindex 147e422a27b..758d6a5cc3b 100644\n--- a/config.c\n+++ b/config.c\n@@ -617,7 +617,7 @@ static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n {\n \tint ret;\n \tconfig_reader_push_kvi(&the_reader, kvi);\n-\tret = fn(key, value, data);\n+\tret = fn(key, value, kvi, data);\n \tconfig_reader_pop_kvi(&the_reader);\n \treturn ret;\n }\ndiff --git a/config.h b/config.h\nindex 525fc0d5e03..2c7b7399691 100644\n--- a/config.h\n+++ b/config.h\n@@ -117,6 +117,15 @@ struct config_options {\n \t} error_action;\n };\n \n+struct key_value_info {\n+\tconst char *filename;\n+\tint linenr;\n+\tenum config_origin_type origin_type;\n+\tenum config_scope scope;\n+\tconst char *path;\n+\tstruct key_value_info *prev;\n+};\n+\n /**\n  * A config callback function takes three parameters:\n  *\n@@ -135,7 +144,7 @@ struct config_options {\n  * A config callback should return 0 for success, or -1 if the variable\n  * could not be parsed properly.\n  */\n-typedef int (*config_fn_t)(const char *, const char *, void *);\n+typedef int (*config_fn_t)(const char *, const char *, struct key_value_info *, void *);\n \n int git_default_config(const char *, const char *, void *);\n \n@@ -675,15 +684,6 @@ int git_config_get_expiry(const char *key, const char **output);\n /* parse either \"this many days\" integer, or \"5.days.ago\" approxidate */\n int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t now);\n \n-struct key_value_info {\n-\tconst char *filename;\n-\tint linenr;\n-\tenum config_origin_type origin_type;\n-\tenum config_scope scope;\n-\tconst char *path;\n-\tstruct key_value_info *prev;\n-};\n-\n /**\n  * First prints the error message specified by the caller in `err` and then\n  * dies printing the line number and the file name of the highest priority\ndiff --git a/contrib/coccinelle/config_fn_kvi.pending.cocci b/contrib/coccinelle/config_fn_kvi.pending.cocci\nnew file mode 100644\nindex 00000000000..d4c84599afa\n--- /dev/null\n+++ b/contrib/coccinelle/config_fn_kvi.pending.cocci\n@@ -0,0 +1,146 @@\n+// These are safe to apply to *.c *.h builtin/*.c\n+\n+@ get_fn @\n+identifier fn, R;\n+@@\n+(\n+(\n+git_config_from_file\n+|\n+git_config_from_file_with_options\n+|\n+git_config_from_mem\n+|\n+git_config_from_blob_oid\n+|\n+read_early_config\n+|\n+read_very_early_config\n+|\n+config_with_options\n+|\n+git_config\n+|\n+git_protected_config\n+|\n+config_from_gitmodules\n+)\n+  (fn, ...)\n+|\n+repo_config(R, fn, ...)\n+)\n+\n+@ extends get_fn @\n+identifier C1, C2, D;\n+@@\n+int fn(const char *C1, const char *C2,\n++  struct key_value_info *kvi,\n+  void *D);\n+\n+@ extends get_fn @\n+@@\n+int fn(const char *, const char *,\n++  struct key_value_info *,\n+  void *);\n+\n+@ extends get_fn @\n+// Don't change fns that look like callback fns but aren't\n+identifier fn2 != tar_filter_config && != git_diff_heuristic_config &&\n+  != git_default_submodule_config && != git_color_config &&\n+  != bundle_list_update && != parse_object_filter_config;\n+identifier C1, C2, D1, D2, S;\n+attribute name UNUSED;\n+@@\n+int fn(const char *C1, const char *C2,\n++  struct key_value_info *kvi,\n+  void *D1) {\n+<+...\n+(\n+fn2(C1, C2,\n++ kvi,\n+D2);\n+|\n+if(fn2(C1, C2,\n++ kvi,\n+D2) < 0) { ... }\n+|\n+return fn2(C1, C2,\n++ kvi,\n+D2);\n+|\n+S = fn2(C1, C2,\n++ kvi,\n+D2);\n+)\n+...+>\n+  }\n+\n+@ extends get_fn@\n+identifier C1, C2, D;\n+attribute name UNUSED;\n+@@\n+int fn(const char *C1, const char *C2,\n++  struct key_value_info *kvi UNUSED,\n+  void *D) {...}\n+\n+\n+// The previous rules don't catch all callbacks, especially if they're defined\n+// in a separate file from the git_config() call. Fix these manually.\n+@@\n+identifier C1, C2, D;\n+attribute name UNUSED;\n+@@\n+int\n+(\n+git_ident_config\n+|\n+urlmatch_collect_fn\n+|\n+write_one_config\n+|\n+forbid_remote_url\n+|\n+credential_config_callback\n+)\n+  (const char *C1, const char *C2,\n++  struct key_value_info *kvi UNUSED,\n+  void *D) {...}\n+\n+@@\n+identifier C1, C2, D, D2, S, fn2;\n+@@\n+int\n+(\n+http_options\n+|\n+git_status_config\n+|\n+git_commit_config\n+|\n+git_default_core_config\n+|\n+grep_config\n+)\n+  (const char *C1, const char *C2,\n++  struct key_value_info *kvi,\n+  void *D) {\n+<+...\n+(\n+fn2(C1, C2,\n++ kvi,\n+D2);\n+|\n+if(fn2(C1, C2,\n++ kvi,\n+D2) < 0) { ... }\n+|\n+return fn2(C1, C2,\n++ kvi,\n+D2);\n+|\n+S = fn2(C1, C2,\n++ kvi,\n+D2);\n+)\n+...+>\n+  }\n-- \ngitgitgadget\n\n"},{"id":"475808","messageId":"1071e70c92892166e1ed2cf22bcd7eb49bdf3b20.1682104399.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 10/14] (RFC-only) config: finish config_fn_t refactor","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:14Z","receivedAt":"2023-04-21T19:14:00Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nHere's an exhaustive list of all of the changes:\n\n* Cases that need a judgement call\n\n  - trace2/tr2_cfg.c:tr2_cfg_set_fl()\n\n    This function needs to account for tr2_cfg_cb() now using \"kvi\".\n    Since this is only called (indirectly) by git_config_set(), config\n    source information has never been available here, so just pass NULL.\n    It will be tr2_cfg_cb()'s responsibility to not use \"kvi\".\n\n  - builtin/checkout.c:checkout_main()\n\n    This calls git_xmerge_config() as a shorthand for parsing a CLI arg.\n    \"kvi\" doesn't apply, so just pass NULL. This might be worth\n    refactoring away, since git_xmerge_config() can call\n    git_default_config().\n\n  - config.c:git_config_include()\n\n    Replace the local \"kvi\" variable with the \"kvi\" parameter. This\n    makes config_include_data.config_reader obsolete, so remove it.\n\n* Hard for cocci to catch\n\n  - urlmatch.c\n\n    Manually refactor the custom config callbacks in \"struct\n    urlmatch_config\".\n\n  - diff.h, fsck.h, grep.h, ident.h, xdiff-interface.h\n\n    \"struct key_value_info\" hasn't been defined yet, so forward declare\n    it. Alternatively, maybe these files should \"#include config.h\".\n\n* Likely deficiencies in .cocci patch\n\n  - submodule-config.c:gitmodules_cb()\n\n    Manually refactor a parse_config() call that gets missed because it\n    uses a different \"*data\" arg.\n\n  - grep.h, various\n\n    Manually refactor grep_config() calls. Not sure why these don't get\n    picked up.\n\n  - config.c:git_config_include(), http.c:http_options()\n\n    Manually add \"kvi\" where it was missed. Not sure why they get missed.\n\n  - builtin/clone.c:write_one_config()\n\n    Manually refactor a git_clone_config() call. Probably got missed\n    because I didn't include git_config_parse_parameter().\n\n  - ident.h\n\n    Remove the UNUSED attribute. Not sure why this is the only instance\n    of this.\n\n  - git-compat-util.h, compat/mingw.[h|c]\n\n    Manually refactor noop_core_config(), platform_core_config() and\n    mingw_core_config(). I can probably add these as \"manual fixups\" in\n    cocci.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/checkout.c | 2 +-\n builtin/clone.c    | 4 ++--\n builtin/grep.c     | 2 +-\n compat/mingw.c     | 3 ++-\n compat/mingw.h     | 4 +++-\n config.c           | 5 +----\n diff.h             | 1 +\n fsck.h             | 1 +\n git-compat-util.h  | 2 ++\n grep.c             | 6 +++---\n grep.h             | 4 +++-\n http.c             | 5 +++--\n ident.h            | 3 ++-\n submodule-config.c | 4 ++--\n trace2/tr2_cfg.c   | 2 +-\n urlmatch.c         | 6 +++---\n xdiff-interface.h  | 2 ++\n 17 files changed, 33 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 92017ba6696..9641423dc2f 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1687,7 +1687,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \n \tif (opts->conflict_style) {\n \t\topts->merge = 1; /* implied */\n-\t\tgit_xmerge_config(\"merge.conflictstyle\", opts->conflict_style, NULL);\n+\t\tgit_xmerge_config(\"merge.conflictstyle\", opts->conflict_style, NULL, NULL);\n \t}\n \tif (opts->force) {\n \t\topts->discard_changes = 1;\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 1e1cf104194..e654757c45d 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -791,14 +791,14 @@ static int git_clone_config(const char *k, const char *v,\n }\n \n static int write_one_config(const char *key, const char *value,\n-\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n+\t\t\t    struct key_value_info *kvi, void *data)\n {\n \t/*\n \t * give git_clone_config a chance to write config values back to the\n \t * environment, since git_config_set_multivar_gently only deals with\n \t * config-file writes\n \t */\n-\tint apply_failed = git_clone_config(key, value, data);\n+\tint apply_failed = git_clone_config(key, value, kvi, data);\n \tif (apply_failed)\n \t\treturn apply_failed;\n \ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 177befc3ed4..6e795f9f3a2 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -290,7 +290,7 @@ static int wait_all(void)\n static int grep_cmd_config(const char *var, const char *value,\n \t\t\t   struct key_value_info *kvi, void *cb)\n {\n-\tint st = grep_config(var, value, cb);\n+\tint st = grep_config(var, value, kvi, cb);\n \n \tif (git_color_config(var, value, cb) < 0)\n \t\tst = -1;\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex 94c5a1daa40..c8181469a2f 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -242,7 +242,8 @@ static int core_restrict_inherited_handles = -1;\n static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;\n static char *unset_environment_variables;\n \n-int mingw_core_config(const char *var, const char *value, void *cb)\n+int mingw_core_config(const char *var, const char *value,\n+\t\t      struct key_value_info *kvi UNUSED, void *cb)\n {\n \tif (!strcmp(var, \"core.hidedotfiles\")) {\n \t\tif (value && !strcasecmp(value, \"dotgitonly\"))\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 209cf7cebad..4f2b489b883 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -11,7 +11,9 @@ typedef _sigset_t sigset_t;\n #undef _POSIX_THREAD_SAFE_FUNCTIONS\n #endif\n \n-int mingw_core_config(const char *var, const char *value, void *cb);\n+struct key_value_info;\n+int mingw_core_config(const char *var, const char *value,\n+\t\t      struct key_value_info *, void *cb);\n #define platform_core_config mingw_core_config\n \n /*\ndiff --git a/config.c b/config.c\nindex 60f8c0c666b..aa183f6f244 100644\n--- a/config.c\n+++ b/config.c\n@@ -147,7 +147,6 @@ struct config_include_data {\n \tvoid *data;\n \tconst struct config_options *opts;\n \tstruct git_config_source *config_source;\n-\tstruct config_reader *config_reader;\n \n \t/*\n \t * All remote URLs discovered when reading all config files.\n@@ -433,10 +432,9 @@ static int include_condition_is_true(struct key_value_info *kvi,\n static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n \t\t  struct key_value_info *kvi, void *data);\n static int git_config_include(const char *var, const char *value,\n-\t\t\t      struct key_value_info *kvi UNUSED, void *data)\n+\t\t\t      struct key_value_info *kvi, void *data)\n {\n \tstruct config_include_data *inc = data;\n-\tstruct key_value_info *kvi = inc->config_reader->config_kvi;\n \tconst char *cond, *key;\n \tsize_t cond_len;\n \tint ret;\n@@ -2252,7 +2250,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tinc.data = data;\n \t\tinc.opts = opts;\n \t\tinc.config_source = config_source;\n-\t\tinc.config_reader = &the_reader;\n \t\tfn = git_config_include;\n \t\tdata = &inc;\n \t}\ndiff --git a/diff.h b/diff.h\nindex 6a3efa63753..2ceb0fd2d66 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -532,6 +532,7 @@ void free_diffstat_info(struct diffstat_t *diffstat);\n int parse_long_opt(const char *opt, const char **argv,\n \t\t   const char **optarg);\n \n+struct key_value_info;\n int git_diff_basic_config(const char *var, const char *value,\n \t\t\t  struct key_value_info *kvi, void *cb);\n int git_diff_heuristic_config(const char *var, const char *value, void *cb);\ndiff --git a/fsck.h b/fsck.h\nindex a06f202576c..914e67a067d 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -233,6 +233,7 @@ void fsck_put_object_name(struct fsck_options *options,\n const char *fsck_describe_object(struct fsck_options *options,\n \t\t\t\t const struct object_id *oid);\n \n+struct key_value_info;\n /*\n  * git_config() callback for use by fsck-y tools that want to support\n  * fsck.<msg> fsck.skipList etc.\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 4a200a9fb41..6812b592c15 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -440,8 +440,10 @@ typedef uintmax_t timestamp_t;\n #endif\n \n #ifndef platform_core_config\n+struct key_value_info;\n static inline int noop_core_config(const char *var UNUSED,\n \t\t\t\t   const char *value UNUSED,\n+\t\t\t\t   struct key_value_info *kvi UNUSED,\n \t\t\t\t   void *cb UNUSED)\n {\n \treturn 0;\ndiff --git a/grep.c b/grep.c\nindex 1516b0689d0..2d3b9bf5d92 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -55,7 +55,7 @@ define_list_config_array_extra(color_grep_slots, {\"match\"});\n  * the grep_defaults template.\n  */\n int grep_config(const char *var, const char *value,\n-\t\tstruct key_value_info *kvi UNUSED, void *cb)\n+\t\tstruct key_value_info *kvi, void *cb)\n {\n \tstruct grep_opt *opt = cb;\n \tconst char *slot;\n@@ -90,9 +90,9 @@ int grep_config(const char *var, const char *value,\n \tif (!strcmp(var, \"color.grep\"))\n \t\topt->color = git_config_colorbool(var, value);\n \tif (!strcmp(var, \"color.grep.match\")) {\n-\t\tif (grep_config(\"color.grep.matchcontext\", value, cb) < 0)\n+\t\tif (grep_config(\"color.grep.matchcontext\", value, kvi, cb) < 0)\n \t\t\treturn -1;\n-\t\tif (grep_config(\"color.grep.matchselected\", value, cb) < 0)\n+\t\tif (grep_config(\"color.grep.matchselected\", value, kvi, cb) < 0)\n \t\t\treturn -1;\n \t} else if (skip_prefix(var, \"color.grep.\", &slot)) {\n \t\tint i = LOOKUP_CONFIG(color_grep_slots, slot);\ndiff --git a/grep.h b/grep.h\nindex c59592e3bdb..6d2fb0ada54 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -202,7 +202,9 @@ struct grep_opt {\n \t.output = std_output, \\\n }\n \n-int grep_config(const char *var, const char *value, void *);\n+struct key_value_info;\n+int grep_config(const char *var, const char *value, struct key_value_info *kvi,\n+\t\tvoid *);\n void grep_init(struct grep_opt *, struct repository *repo);\n \n void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);\ndiff --git a/http.c b/http.c\nindex d5d82c5230f..3d4292eba6a 100644\n--- a/http.c\n+++ b/http.c\n@@ -361,7 +361,8 @@ static void process_curl_messages(void)\n \t}\n }\n \n-static int http_options(const char *var, const char *value, void *cb)\n+static int http_options(const char *var, const char *value,\n+\t\t\tstruct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(\"http.version\", var)) {\n \t\treturn git_config_string(&curl_http_version, var, value);\n@@ -532,7 +533,7 @@ static int http_options(const char *var, const char *value, void *cb)\n \t}\n \n \t/* Fall back on the default ones */\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, kvi, cb);\n }\n \n static int curl_empty_auth_enabled(void)\ndiff --git a/ident.h b/ident.h\nindex f55db41ff99..4e3bd347c52 100644\n--- a/ident.h\n+++ b/ident.h\n@@ -62,7 +62,8 @@ const char *fmt_name(enum want_ident);\n int committer_ident_sufficiently_given(void);\n int author_ident_sufficiently_given(void);\n \n+struct key_value_info;\n int git_ident_config(const char *, const char *,\n-\t\t     struct key_value_info *UNUSED, void *);\n+\t\t     struct key_value_info *, void *);\n \n #endif\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 522c9cd3213..7d773f33621 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -675,7 +675,7 @@ out:\n }\n \n static int gitmodules_cb(const char *var, const char *value,\n-\t\t\t struct key_value_info *kvi UNUSED, void *data)\n+\t\t\t struct key_value_info *kvi, void *data)\n {\n \tstruct repository *repo = data;\n \tstruct parse_config_parameter parameter;\n@@ -685,7 +685,7 @@ static int gitmodules_cb(const char *var, const char *value,\n \tparameter.gitmodules_oid = null_oid();\n \tparameter.overwrite = 1;\n \n-\treturn parse_config(var, value, &parameter);\n+\treturn parse_config(var, value, kvi, &parameter);\n }\n \n void repo_read_gitmodules(struct repository *repo, int skip_if_read)\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 6871258d468..8ed139c69f4 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -146,5 +146,5 @@ void tr2_cfg_set_fl(const char *file, int line, const char *key,\n \tstruct tr2_cfg_data data = { file, line };\n \n \tif (tr2_cfg_load_patterns() > 0)\n-\t\ttr2_cfg_cb(key, value, &data);\n+\t\ttr2_cfg_cb(key, value, NULL, &data);\n }\ndiff --git a/urlmatch.c b/urlmatch.c\nindex 47683974d8c..c94500b61b3 100644\n--- a/urlmatch.c\n+++ b/urlmatch.c\n@@ -552,7 +552,7 @@ static int cmp_matches(const struct urlmatch_item *a,\n }\n \n int urlmatch_config_entry(const char *var, const char *value,\n-\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n+\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tstruct string_list_item *item;\n \tstruct urlmatch_config *collect = cb;\n@@ -566,7 +566,7 @@ int urlmatch_config_entry(const char *var, const char *value,\n \n \tif (!skip_prefix(var, collect->section, &key) || *(key++) != '.') {\n \t\tif (collect->cascade_fn)\n-\t\t\treturn collect->cascade_fn(var, value, cb);\n+\t\t\treturn collect->cascade_fn(var, value, kvi, cb);\n \t\treturn 0; /* not interested */\n \t}\n \tdot = strrchr(key, '.');\n@@ -610,7 +610,7 @@ int urlmatch_config_entry(const char *var, const char *value,\n \tstrbuf_addstr(&synthkey, collect->section);\n \tstrbuf_addch(&synthkey, '.');\n \tstrbuf_addstr(&synthkey, key);\n-\tretval = collect->collect_fn(synthkey.buf, value, collect->cb);\n+\tretval = collect->collect_fn(synthkey.buf, value, kvi, collect->cb);\n \n \tstrbuf_release(&synthkey);\n \treturn retval;\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex c1676b11702..749cdf77579 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -50,6 +50,8 @@ int buffer_is_binary(const char *ptr, unsigned long size);\n \n void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n+\n+struct key_value_info;\n int git_xmerge_config(const char *var, const char *value,\n \t\t      struct key_value_info *kvi, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n\n"},{"id":"475809","messageId":"b38653477c75685af9d74b0d9b5605c41659c939.1682104399.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 11/14] config: remove current_config_(line|name|origin_type)","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:15Z","receivedAt":"2023-04-21T19:14:01Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nTrivially replace current_config_(line|name|origin_type) by reading the\ncorresponding values from \"struct key_value_info\". This includes some\nlight \"kvi\" plumbing for builtin/config.c, and for *origin_type,\nsplitting out a function that turns \"enum config_origin_type\" into the\nhuman-readable string that callbacks actually want.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/config.c       | 25 +++++++++++++------------\n builtin/remote.c       |  6 +++---\n config.c               | 23 +----------------------\n config.h               |  4 +---\n t/helper/test-config.c |  8 ++++----\n 5 files changed, 22 insertions(+), 44 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex b2ad7351d0a..2ffa25139c6 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -191,16 +191,16 @@ static void check_argc(int argc, int min, int max)\n \tusage_builtin_config();\n }\n \n-static void show_config_origin(struct strbuf *buf)\n+static void show_config_origin(struct key_value_info *kvi, struct strbuf *buf)\n {\n \tconst char term = end_nul ? '\\0' : '\\t';\n \n-\tstrbuf_addstr(buf, current_config_origin_type());\n+\tstrbuf_addstr(buf, config_origin_type_name(kvi->origin_type));\n \tstrbuf_addch(buf, ':');\n \tif (end_nul)\n-\t\tstrbuf_addstr(buf, current_config_name());\n+\t\tstrbuf_addstr(buf, kvi->filename ? kvi->filename : \"\");\n \telse\n-\t\tquote_c_style(current_config_name(), buf, NULL, 0);\n+\t\tquote_c_style(kvi->filename ? kvi->filename : \"\", buf, NULL, 0);\n \tstrbuf_addch(buf, term);\n }\n \n@@ -214,14 +214,14 @@ static void show_config_scope(struct strbuf *buf)\n }\n \n static int show_all_config(const char *key_, const char *value_,\n-\t\t\t   struct key_value_info *kvi UNUSED, void *cb UNUSED)\n+\t\t\t   struct key_value_info *kvi, void *cb UNUSED)\n {\n \tif (show_origin || show_scope) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \t\tif (show_scope)\n \t\t\tshow_config_scope(&buf);\n \t\tif (show_origin)\n-\t\t\tshow_config_origin(&buf);\n+\t\t\tshow_config_origin(kvi, &buf);\n \t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n \t\tfwrite(buf.buf, 1, buf.len, stdout);\n \t\tstrbuf_release(&buf);\n@@ -239,12 +239,13 @@ struct strbuf_list {\n \tint alloc;\n };\n \n-static int format_config(struct strbuf *buf, const char *key_, const char *value_)\n+static int format_config(struct strbuf *buf, const char *key_, const char *value_,\n+\t\t\t struct key_value_info *kvi)\n {\n \tif (show_scope)\n \t\tshow_config_scope(buf);\n \tif (show_origin)\n-\t\tshow_config_origin(buf);\n+\t\tshow_config_origin(kvi, buf);\n \tif (show_keys)\n \t\tstrbuf_addstr(buf, key_);\n \tif (!omit_values) {\n@@ -299,7 +300,7 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value\n }\n \n static int collect_config(const char *key_, const char *value_,\n-\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n+\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tstruct strbuf_list *values = cb;\n \n@@ -316,7 +317,7 @@ static int collect_config(const char *key_, const char *value_,\n \tALLOC_GROW(values->items, values->nr + 1, values->alloc);\n \tstrbuf_init(&values->items[values->nr], 0);\n \n-\treturn format_config(&values->items[values->nr++], key_, value_);\n+\treturn format_config(&values->items[values->nr++], key_, value_, kvi);\n }\n \n static int get_value(const char *key_, const char *regex_, unsigned flags)\n@@ -381,7 +382,7 @@ static int get_value(const char *key_, const char *regex_, unsigned flags)\n \t\tALLOC_GROW(values.items, values.nr + 1, values.alloc);\n \t\titem = &values.items[values.nr++];\n \t\tstrbuf_init(item, 0);\n-\t\tif (format_config(item, key_, default_value) < 0)\n+\t\tif (format_config(item, key_, default_value, NULL) < 0)\n \t\t\tdie(_(\"failed to format default config value: %s\"),\n \t\t\t\tdefault_value);\n \t}\n@@ -618,7 +619,7 @@ static int get_urlmatch(const char *var, const char *url)\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n \t\tformat_config(&buf, item->string,\n-\t\t\t      matched->value_is_null ? NULL : matched->value.buf);\n+\t\t\t      matched->value_is_null ? NULL : matched->value.buf, NULL);\n \t\tfwrite(buf.buf, 1, buf.len, stdout);\n \t\tstrbuf_release(&buf);\n \ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex edb4a9ddd7f..fc4ea993ebb 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -646,7 +646,7 @@ struct push_default_info\n };\n \n static int config_read_push_default(const char *key, const char *value,\n-\tstruct key_value_info *kvi UNUSED, void *cb)\n+\tstruct key_value_info *kvi, void *cb)\n {\n \tstruct push_default_info* info = cb;\n \tif (strcmp(key, \"remote.pushdefault\") ||\n@@ -655,8 +655,8 @@ static int config_read_push_default(const char *key, const char *value,\n \n \tinfo->scope = current_config_scope();\n \tstrbuf_reset(&info->origin);\n-\tstrbuf_addstr(&info->origin, current_config_name());\n-\tinfo->linenr = current_config_line();\n+\tstrbuf_addstr(&info->origin, kvi->filename);\n+\tinfo->linenr = kvi->linenr;\n \n \treturn 0;\n }\ndiff --git a/config.c b/config.c\nindex aa183f6f244..0a5443243dc 100644\n--- a/config.c\n+++ b/config.c\n@@ -3917,13 +3917,8 @@ static int reader_origin_type(struct config_reader *reader,\n \treturn 0;\n }\n \n-const char *current_config_origin_type(void)\n+const char *config_origin_type_name(enum config_origin_type type)\n {\n-\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n-\n-\tif (reader_origin_type(&the_reader, &type))\n-\t\tBUG(\"current_config_origin_type called outside config callback\");\n-\n \tswitch (type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\treturn \"blob\";\n@@ -3969,14 +3964,6 @@ static int reader_config_name(struct config_reader *reader, const char **out)\n \treturn 0;\n }\n \n-const char *current_config_name(void)\n-{\n-\tconst char *name;\n-\tif (reader_config_name(&the_reader, &name))\n-\t\tBUG(\"current_config_name called outside config callback\");\n-\treturn name ? name : \"\";\n-}\n-\n enum config_scope current_config_scope(void)\n {\n \tif (the_reader.config_kvi)\n@@ -3991,14 +3978,6 @@ enum config_scope current_config_scope(void)\n \t\treturn CONFIG_SCOPE_UNKNOWN;\n }\n \n-int current_config_line(void)\n-{\n-\tif (the_reader.config_kvi)\n-\t\treturn the_reader.config_kvi->linenr;\n-\telse\n-\t\tBUG(\"current_config_line called outside config callback\");\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex e4893e237e1..fb16c4aca77 100644\n--- a/config.h\n+++ b/config.h\n@@ -375,9 +375,7 @@ void git_global_config(char **user, char **xdg);\n int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n \n enum config_scope current_config_scope(void);\n-const char *current_config_origin_type(void);\n-const char *current_config_name(void);\n-int current_config_line(void);\n+const char *config_origin_type_name(enum config_origin_type type);\n \n /*\n  * Match and parse a config key of the form:\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 00cd49e5145..337587df41d 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -43,7 +43,7 @@\n  */\n \n static int iterate_cb(const char *var, const char *value,\n-\t\t      struct key_value_info *kvi UNUSED, void *data UNUSED)\n+\t\t      struct key_value_info *kvi, void *data UNUSED)\n {\n \tstatic int nr;\n \n@@ -52,9 +52,9 @@ static int iterate_cb(const char *var, const char *value,\n \n \tprintf(\"key=%s\\n\", var);\n \tprintf(\"value=%s\\n\", value ? value : \"(null)\");\n-\tprintf(\"origin=%s\\n\", current_config_origin_type());\n-\tprintf(\"name=%s\\n\", current_config_name());\n-\tprintf(\"lno=%d\\n\", current_config_line());\n+\tprintf(\"origin=%s\\n\", config_origin_type_name(kvi->origin_type));\n+\tprintf(\"name=%s\\n\", kvi->filename ? kvi->filename : \"\");\n+\tprintf(\"lno=%d\\n\", kvi->linenr);\n \tprintf(\"scope=%s\\n\", config_scope_name(current_config_scope()));\n \n \treturn 0;\n-- \ngitgitgadget\n\n"},{"id":"475810","messageId":"33e59152293136e6430cc47e311c1167da167e37.1682104399.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 14/14] config: remove config_reader from configset_add_value","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:18Z","receivedAt":"2023-04-21T19:14:04Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nSince we now get \"kvi\" from the config callback, we can stop passing it\nvia \"*data\". Now \"struct config_reader\" has no more references, so get\nrid of it.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 81 ++++++++++----------------------------------------------\n 1 file changed, 14 insertions(+), 67 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex e78de67c452..fbb4aab46cf 100644\n--- a/config.c\n+++ b/config.c\n@@ -59,33 +59,6 @@ struct config_source {\n };\n #define CONFIG_SOURCE_INIT { 0 }\n \n-struct config_reader {\n-\tstruct key_value_info *config_kvi;\n-};\n-/*\n- * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n- * \"the_reader\". \"the_reader\" should only be used if that is infeasible, e.g. in\n- * a public function.\n- */\n-static struct config_reader the_reader;\n-\n-static inline void config_reader_push_kvi(struct config_reader *reader,\n-\t\t\t\t\t  struct key_value_info *kvi)\n-{\n-\tkvi->prev = reader->config_kvi;\n-\treader->config_kvi = kvi;\n-}\n-\n-static inline struct key_value_info *config_reader_pop_kvi(struct config_reader *reader)\n-{\n-\tstruct key_value_info *ret;\n-\tif (!reader->config_kvi)\n-\t\tBUG(\"tried to pop config_kvi, but we weren't reading config\");\n-\tret = reader->config_kvi;\n-\treader->config_kvi = reader->config_kvi->prev;\n-\treturn ret;\n-}\n-\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -429,8 +402,6 @@ static int include_condition_is_true(struct key_value_info *kvi,\n \treturn 0;\n }\n \n-static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n-\t\t  struct key_value_info *kvi, void *data);\n static int git_config_include(const char *var, const char *value,\n \t\t\t      struct key_value_info *kvi, void *data)\n {\n@@ -443,7 +414,7 @@ static int git_config_include(const char *var, const char *value,\n \t * Pass along all values, including \"include\" directives; this makes it\n \t * possible to query information on the includes themselves.\n \t */\n-\tret = kvi_fn(inc->fn, var, value, kvi, inc->data);\n+\tret = inc->fn(var, value, kvi, inc->data);\n \tif (ret < 0)\n \t\treturn ret;\n \n@@ -614,16 +585,6 @@ out_free_ret_1:\n \treturn -CONFIG_INVALID_KEY;\n }\n \n-static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n-\t\t  struct key_value_info *kvi, void *data)\n-{\n-\tint ret;\n-\tconfig_reader_push_kvi(&the_reader, kvi);\n-\tret = fn(key, value, kvi, data);\n-\tconfig_reader_pop_kvi(&the_reader);\n-\treturn ret;\n-}\n-\n static int config_parse_pair(const char *key, const char *value,\n \t\t\t     struct key_value_info *kvi,\n \t\t\t     config_fn_t fn, void *data)\n@@ -636,7 +597,7 @@ static int config_parse_pair(const char *key, const char *value,\n \tif (git_config_parse_key(key, &canonical_name, NULL))\n \t\treturn -1;\n \n-\tret = (kvi_fn(fn, canonical_name, value, kvi, data) < 0) ? -1 : 0;\n+\tret = (fn(canonical_name, value, kvi, data) < 0) ? -1 : 0;\n \tfree(canonical_name);\n \treturn ret;\n }\n@@ -937,7 +898,7 @@ static int get_value(struct config_source *cs, struct key_value_info *kvi,\n \t */\n \tcs->linenr--;\n \tkvi->linenr = cs->linenr;\n-\tret = kvi_fn(fn, name->buf, value, kvi, data);\n+\tret = fn(name->buf, value, kvi, data);\n \tif (ret >= 0)\n \t\tcs->linenr++;\n \treturn ret;\n@@ -2289,8 +2250,8 @@ static void configset_iter(struct config_set *set, config_fn_t fn, void *data)\n \t\tvalues = &entry->value_list;\n \t\tkvi = values->items[value_index].util;\n \n-\t\tif (kvi_fn(fn, entry->key, values->items[value_index].string,\n-\t\t\t   kvi, data) < 0)\n+\t\tif (fn(entry->key, values->items[value_index].string, kvi,\n+\t\t       data) < 0)\n \t\t\tgit_die_config_linenr(entry->key, kvi->filename,\n \t\t\t\t\t      kvi->linenr);\n \t}\n@@ -2368,9 +2329,8 @@ static int configset_find_element(struct config_set *set, const char *key,\n \treturn 0;\n }\n \n-static int configset_add_value(struct config_reader *reader,\n-\t\t\t       struct config_set *set, const char *key,\n-\t\t\t       const char *value)\n+static int configset_add_value(struct config_set *set, const char *key,\n+\t\t\t       const char *value, struct key_value_info *kvi_p)\n {\n \tstruct config_set_element *e;\n \tstruct string_list_item *si;\n@@ -2399,7 +2359,7 @@ static int configset_add_value(struct config_reader *reader,\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tmemcpy(kv_info, reader->config_kvi, sizeof(struct key_value_info));\n+\tmemcpy(kv_info, kvi_p, sizeof(struct key_value_info));\n \tsi->util = kv_info;\n \n \treturn 0;\n@@ -2447,26 +2407,19 @@ void git_configset_clear(struct config_set *set)\n \tset->list.items = NULL;\n }\n \n-struct configset_add_data {\n-\tstruct config_set *config_set;\n-\tstruct config_reader *config_reader;\n-};\n #define CONFIGSET_ADD_INIT { 0 }\n \n static int config_set_callback(const char *key, const char *value,\n-\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n+\t\t\t       struct key_value_info *kvi, void *cb)\n {\n-\tstruct configset_add_data *data = cb;\n-\tconfigset_add_value(data->config_reader, data->config_set, key, value);\n+\tstruct config_set *set = cb;\n+\tconfigset_add_value(set, key, value, kvi);\n \treturn 0;\n }\n \n int git_configset_add_file(struct config_set *set, const char *filename)\n {\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n-\tdata.config_reader = &the_reader;\n-\tdata.config_set = set;\n-\treturn git_config_from_file(config_set_callback, filename, &data);\n+\treturn git_config_from_file(config_set_callback, filename, set);\n }\n \n int git_configset_get_value(struct config_set *set, const char *key,\n@@ -2632,7 +2585,6 @@ int git_configset_get_pathname(struct config_set *set, const char *key, const ch\n static void repo_read_config(struct repository *repo)\n {\n \tstruct config_options opts = { 0 };\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n \n \topts.respect_includes = 1;\n \topts.commondir = repo->commondir;\n@@ -2644,10 +2596,8 @@ static void repo_read_config(struct repository *repo)\n \t\tgit_configset_clear(repo->config);\n \n \tgit_configset_init(repo->config);\n-\tdata.config_set = repo->config;\n-\tdata.config_reader = &the_reader;\n \n-\tif (config_with_options(config_set_callback, &data, NULL, &opts) < 0)\n+\tif (config_with_options(config_set_callback, repo->config, NULL, &opts) < 0)\n \t\t/*\n \t\t * config_with_options() normally returns only\n \t\t * zero, as most errors are fatal, and\n@@ -2786,12 +2736,9 @@ static void read_protected_config(void)\n \t\t.ignore_worktree = 1,\n \t\t.system_gently = 1,\n \t};\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n \n \tgit_configset_init(&protected_config);\n-\tdata.config_set = &protected_config;\n-\tdata.config_reader = &the_reader;\n-\tconfig_with_options(config_set_callback, &data, NULL, &opts);\n+\tconfig_with_options(config_set_callback, &protected_config, NULL, &opts);\n }\n \n void git_protected_config(config_fn_t fn, void *data)\n-- \ngitgitgadget\n"},{"id":"475811","messageId":"4723ae3bde6b8bacc4ed7b11902c65a3a72bc5e9.1682104399.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 12/14] config: remove current_config_scope()","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:16Z","receivedAt":"2023-04-21T19:14:07Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nReplace current_config_scope() by reading the corresponding value off\n\"struct key_value_info\".\n\nMost instances of this are trivial, except for the trace2/* files. There\nis a code path starting from trace2_def_param_fl() that eventually calls\ncurrent_config_scope(), and thus it needs to have \"kvi\" plumbed through\nit. Additional plumbing is also needed to get \"kvi\" to\ntrace2_def_param_fl(), which gets called by two code paths:\n\n- Through tr2_cfg_cb(), which is a config callback, so it trivially\n  receives \"kvi\".\n\n- Through tr2_list_env_vars_fl(), which is a high level function that\n  lists environment variables for tracing. This has been secretly\n  behaving like git_config_from_parameters() (in that it parses config\n  from environment variables/the CLI), but does not set config source\n  information.\n\n  Teach tr2_list_env_vars_fl() to be well-behaved by using\n  kvi_from_param(), which is already used internally by config.c for\n  CLI/environment variable-based config.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/config.c        |  8 ++++----\n builtin/remote.c        |  2 +-\n config.c                | 16 +---------------\n config.h                |  3 ++-\n remote.c                |  6 +++---\n t/helper/test-config.c  |  2 +-\n trace2.c                |  4 ++--\n trace2.h                |  3 ++-\n trace2/tr2_cfg.c        |  9 ++++++---\n trace2/tr2_tgt.h        |  4 +++-\n trace2/tr2_tgt_event.c  |  4 ++--\n trace2/tr2_tgt_normal.c |  4 ++--\n trace2/tr2_tgt_perf.c   |  4 ++--\n 13 files changed, 31 insertions(+), 38 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 2ffa25139c6..7899add68fb 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -204,10 +204,10 @@ static void show_config_origin(struct key_value_info *kvi, struct strbuf *buf)\n \tstrbuf_addch(buf, term);\n }\n \n-static void show_config_scope(struct strbuf *buf)\n+static void show_config_scope(struct key_value_info *kvi, struct strbuf *buf)\n {\n \tconst char term = end_nul ? '\\0' : '\\t';\n-\tconst char *scope = config_scope_name(current_config_scope());\n+\tconst char *scope = config_scope_name(kvi->scope);\n \n \tstrbuf_addstr(buf, N_(scope));\n \tstrbuf_addch(buf, term);\n@@ -219,7 +219,7 @@ static int show_all_config(const char *key_, const char *value_,\n \tif (show_origin || show_scope) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \t\tif (show_scope)\n-\t\t\tshow_config_scope(&buf);\n+\t\t\tshow_config_scope(kvi, &buf);\n \t\tif (show_origin)\n \t\t\tshow_config_origin(kvi, &buf);\n \t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n@@ -243,7 +243,7 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value\n \t\t\t struct key_value_info *kvi)\n {\n \tif (show_scope)\n-\t\tshow_config_scope(buf);\n+\t\tshow_config_scope(kvi, buf);\n \tif (show_origin)\n \t\tshow_config_origin(kvi, buf);\n \tif (show_keys)\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex fc4ea993ebb..034998a1205 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -653,7 +653,7 @@ static int config_read_push_default(const char *key, const char *value,\n \t    !value || strcmp(value, info->old_name))\n \t\treturn 0;\n \n-\tinfo->scope = current_config_scope();\n+\tinfo->scope = kvi->scope;\n \tstrbuf_reset(&info->origin);\n \tstrbuf_addstr(&info->origin, kvi->filename);\n \tinfo->linenr = kvi->linenr;\ndiff --git a/config.c b/config.c\nindex 0a5443243dc..68c9b507a4d 100644\n--- a/config.c\n+++ b/config.c\n@@ -643,7 +643,7 @@ static int config_parse_pair(const char *key, const char *value,\n \n \n /* for values read from `git_config_from_parameters()` */\n-static void kvi_from_param(struct key_value_info *out)\n+void kvi_from_param(struct key_value_info *out)\n {\n \tout->filename = NULL;\n \tout->linenr = -1;\n@@ -3964,20 +3964,6 @@ static int reader_config_name(struct config_reader *reader, const char **out)\n \treturn 0;\n }\n \n-enum config_scope current_config_scope(void)\n-{\n-\tif (the_reader.config_kvi)\n-\t\treturn the_reader.config_kvi->scope;\n-\telse\n-\t\t/*\n-\t\t * FIXME This should be a BUG, but tr2_list_env_vars_fl is\n-\t\t * calling this outside of a config callback. This will be\n-\t\t * easier to fix when we plumb kvi through the config callbacks,\n-\t\t * so leave this untouched for now.\n-\t\t */\n-\t\treturn CONFIG_SCOPE_UNKNOWN;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex fb16c4aca77..f3b1a8c38a6 100644\n--- a/config.h\n+++ b/config.h\n@@ -374,7 +374,6 @@ void git_global_config(char **user, char **xdg);\n \n int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n \n-enum config_scope current_config_scope(void);\n const char *config_origin_type_name(enum config_origin_type type);\n \n /*\n@@ -702,4 +701,6 @@ NORETURN void git_die_config_linenr(const char *key, const char *filename, int l\n \tlookup_config(mapping, ARRAY_SIZE(mapping), var)\n int lookup_config(const char **mapping, int nr_mapping, const char *var);\n \n+void kvi_from_param(struct key_value_info *out);\n+\n #endif /* CONFIG_H */\ndiff --git a/remote.c b/remote.c\nindex 10868a963f2..266601157e3 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -349,7 +349,7 @@ static void read_branches_file(struct remote_state *remote_state,\n }\n \n static int handle_config(const char *key, const char *value,\n-\t\t\t struct key_value_info *kvi UNUSED, void *cb)\n+\t\t\t struct key_value_info *kvi, void *cb)\n {\n \tconst char *name;\n \tsize_t namelen;\n@@ -414,8 +414,8 @@ static int handle_config(const char *key, const char *value,\n \t}\n \tremote = make_remote(remote_state, name, namelen);\n \tremote->origin = REMOTE_CONFIG;\n-\tif (current_config_scope() == CONFIG_SCOPE_LOCAL ||\n-\t    current_config_scope() == CONFIG_SCOPE_WORKTREE)\n+\tif (kvi->scope == CONFIG_SCOPE_LOCAL ||\n+\t    kvi->scope == CONFIG_SCOPE_WORKTREE)\n \t\tremote->configured_in_repo = 1;\n \tif (!strcmp(subkey, \"mirror\"))\n \t\tremote->mirror = git_config_bool(key, value);\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 337587df41d..7027ffa187f 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -55,7 +55,7 @@ static int iterate_cb(const char *var, const char *value,\n \tprintf(\"origin=%s\\n\", config_origin_type_name(kvi->origin_type));\n \tprintf(\"name=%s\\n\", kvi->filename ? kvi->filename : \"\");\n \tprintf(\"lno=%d\\n\", kvi->linenr);\n-\tprintf(\"scope=%s\\n\", config_scope_name(current_config_scope()));\n+\tprintf(\"scope=%s\\n\", config_scope_name(kvi->scope));\n \n \treturn 0;\n }\ndiff --git a/trace2.c b/trace2.c\nindex e8ba62c0c3d..f519a3514b6 100644\n--- a/trace2.c\n+++ b/trace2.c\n@@ -632,7 +632,7 @@ void trace2_thread_exit_fl(const char *file, int line)\n }\n \n void trace2_def_param_fl(const char *file, int line, const char *param,\n-\t\t\t const char *value)\n+\t\t\t const char *value, struct key_value_info *kvi)\n {\n \tstruct tr2_tgt *tgt_j;\n \tint j;\n@@ -642,7 +642,7 @@ void trace2_def_param_fl(const char *file, int line, const char *param,\n \n \tfor_each_wanted_builtin (j, tgt_j)\n \t\tif (tgt_j->pfn_param_fl)\n-\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n+\t\t\ttgt_j->pfn_param_fl(file, line, param, value, kvi);\n }\n \n void trace2_def_repo_fl(const char *file, int line, struct repository *repo)\ndiff --git a/trace2.h b/trace2.h\nindex 4ced30c0db3..af06f66739e 100644\n--- a/trace2.h\n+++ b/trace2.h\n@@ -325,6 +325,7 @@ void trace2_thread_exit_fl(const char *file, int line);\n \n #define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)\n \n+struct key_value_info;\n /*\n  * Emits a \"def_param\" message containing a key/value pair.\n  *\n@@ -334,7 +335,7 @@ void trace2_thread_exit_fl(const char *file, int line);\n  * `core.abbrev`, `status.showUntrackedFiles`, or `--no-ahead-behind`.\n  */\n void trace2_def_param_fl(const char *file, int line, const char *param,\n-\t\t\t const char *value);\n+\t\t\t const char *value, struct key_value_info *kvi);\n \n #define trace2_def_param(param, value) \\\n \ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 8ed139c69f4..1450c9bec71 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -100,7 +100,7 @@ struct tr2_cfg_data {\n  * See if the given config key matches any of our patterns of interest.\n  */\n static int tr2_cfg_cb(const char *key, const char *value,\n-\t\t      struct key_value_info *kvi UNUSED, void *d)\n+\t\t      struct key_value_info *kvi, void *d)\n {\n \tstruct strbuf **s;\n \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n@@ -109,7 +109,8 @@ static int tr2_cfg_cb(const char *key, const char *value,\n \t\tstruct strbuf *buf = *s;\n \t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n \t\tif (wm == WM_MATCH) {\n-\t\t\ttrace2_def_param_fl(data->file, data->line, key, value);\n+\t\t\ttrace2_def_param_fl(data->file, data->line, key, value,\n+\t\t\t\t\t    kvi);\n \t\t\treturn 0;\n \t\t}\n \t}\n@@ -127,8 +128,10 @@ void tr2_cfg_list_config_fl(const char *file, int line)\n \n void tr2_list_env_vars_fl(const char *file, int line)\n {\n+\tstruct key_value_info kvi = { 0 };\n \tstruct strbuf **s;\n \n+\tkvi_from_param(&kvi);\n \tif (tr2_load_env_vars() <= 0)\n \t\treturn;\n \n@@ -136,7 +139,7 @@ void tr2_list_env_vars_fl(const char *file, int line)\n \t\tstruct strbuf *buf = *s;\n \t\tconst char *val = getenv(buf->buf);\n \t\tif (val && *val)\n-\t\t\ttrace2_def_param_fl(file, line, buf->buf, val);\n+\t\t\ttrace2_def_param_fl(file, line, buf->buf, val, &kvi);\n \t}\n }\n \ndiff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h\nindex bf8745c4f05..9c88ca9beed 100644\n--- a/trace2/tr2_tgt.h\n+++ b/trace2/tr2_tgt.h\n@@ -69,8 +69,10 @@ typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, int line,\n \t\t\t\t\t   uint64_t us_elapsed_absolute,\n \t\t\t\t\t   int exec_id, int code);\n \n+struct key_value_info;\n typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,\n-\t\t\t\t     const char *param, const char *value);\n+\t\t\t\t     const char *param, const char *value,\n+\t\t\t\t     struct key_value_info *kvi);\n \n typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,\n \t\t\t\t    const struct repository *repo);\ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nindex 9e7aab6d510..83db3c755bd 100644\n--- a/trace2/tr2_tgt_event.c\n+++ b/trace2/tr2_tgt_event.c\n@@ -476,11 +476,11 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, struct key_value_info *kvi)\n {\n \tconst char *event_name = \"def_param\";\n \tstruct json_writer jw = JSON_WRITER_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tjw_object_begin(&jw, 0);\ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nindex 8672c2c2d04..65e9be9c5a4 100644\n--- a/trace2/tr2_tgt_normal.c\n+++ b/trace2/tr2_tgt_normal.c\n@@ -296,10 +296,10 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, struct key_value_info *kvi)\n {\n \tstruct strbuf buf_payload = STRBUF_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tstrbuf_addf(&buf_payload, \"def_param scope:%s %s=%s\", scope_name, param,\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nindex 3f2b2d53118..f402f6e3813 100644\n--- a/trace2/tr2_tgt_perf.c\n+++ b/trace2/tr2_tgt_perf.c\n@@ -438,12 +438,12 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, struct key_value_info *kvi)\n {\n \tconst char *event_name = \"def_param\";\n \tstruct strbuf buf_payload = STRBUF_INIT;\n \tstruct strbuf scope_payload = STRBUF_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tstrbuf_addf(&buf_payload, \"%s:%s\", param, value);\n-- \ngitgitgadget\n\n"},{"id":"475812","messageId":"5eb3874b49431e0d204735fe959f628367159735.1682104399.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 09/14] (RFC-only) config: apply cocci to config_fn_t implementations","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:13Z","receivedAt":"2023-04-21T19:14:19Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPass \"struct key_value_info\" to *most* functions that are invoked as\n\"config_fn_t\" callbacks by applying\ncontrib/coccinelle/config_fn_kvi.pending.cocci. None of the functions\nactually use the \"kvi\" arg yet (besides propagating it to a function\nthat now expects \"kvi\"), but this will be addressed in a later commit.\nWhen deciding whether or not to propagate \"kvi\" to an inner function,\nonly propagate the \"kvi\" arg if the inner function is actually invoked\nelsewhere as a config callback; it does not matter whether the function\nhappens have the same signature as config_fn_t.\n\nThis commit does not build and has several style issues (e.g. a lack of\nspacing around the \"kvi\" arg), but I've split this out for the RFC so\nthat it's more obvious which changes are automatic vs manual. Post-RFC I\nwill squash this with the rest of the refactor + cocci changes.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n alias.c                     |  3 ++-\n archive-tar.c               |  3 ++-\n archive-zip.c               |  1 +\n builtin/add.c               |  5 +++--\n builtin/blame.c             |  5 +++--\n builtin/branch.c            |  5 +++--\n builtin/cat-file.c          |  5 +++--\n builtin/checkout.c          |  5 +++--\n builtin/clean.c             |  5 +++--\n builtin/clone.c             |  8 +++++---\n builtin/column.c            |  3 ++-\n builtin/commit-graph.c      |  1 +\n builtin/commit.c            | 10 ++++++----\n builtin/config.c            | 10 +++++++---\n builtin/difftool.c          |  5 +++--\n builtin/fetch.c             |  8 +++++---\n builtin/fsmonitor--daemon.c |  5 +++--\n builtin/grep.c              |  5 +++--\n builtin/help.c              |  5 +++--\n builtin/index-pack.c        |  5 +++--\n builtin/log.c               | 10 ++++++----\n builtin/merge.c             |  7 ++++---\n builtin/multi-pack-index.c  |  1 +\n builtin/pack-objects.c      |  5 +++--\n builtin/patch-id.c          |  5 +++--\n builtin/pull.c              |  5 +++--\n builtin/push.c              |  5 +++--\n builtin/read-tree.c         |  5 +++--\n builtin/rebase.c            |  5 +++--\n builtin/receive-pack.c      |  5 +++--\n builtin/reflog.c            |  7 ++++---\n builtin/remote.c            |  6 ++++--\n builtin/repack.c            |  5 +++--\n builtin/reset.c             |  5 +++--\n builtin/send-pack.c         |  5 +++--\n builtin/show-branch.c       |  5 +++--\n builtin/stash.c             |  5 +++--\n builtin/submodule--helper.c |  1 +\n builtin/tag.c               |  5 +++--\n builtin/var.c               |  5 +++--\n builtin/worktree.c          |  5 +++--\n bundle-uri.c                |  8 ++++++--\n config.c                    | 28 ++++++++++++++++++----------\n config.h                    |  3 ++-\n connect.c                   |  4 ++--\n convert.c                   |  4 +++-\n credential.c                |  1 +\n delta-islands.c             |  3 ++-\n diff.c                      | 10 ++++++----\n diff.h                      |  6 ++++--\n fetch-pack.c                |  5 +++--\n fmt-merge-msg.c             |  5 +++--\n fmt-merge-msg.h             |  3 ++-\n fsck.c                      |  8 +++++---\n fsck.h                      |  3 ++-\n gpg-interface.c             |  6 ++++--\n grep.c                      |  3 ++-\n help.c                      |  7 +++++--\n ident.c                     |  3 ++-\n ident.h                     |  3 ++-\n imap-send.c                 |  5 +++--\n ll-merge.c                  |  1 +\n ls-refs.c                   |  2 +-\n mailinfo.c                  |  5 +++--\n notes-utils.c               |  3 ++-\n notes.c                     |  3 ++-\n pager.c                     |  5 ++++-\n pretty.c                    |  1 +\n promisor-remote.c           |  4 +++-\n remote.c                    |  3 ++-\n revision.c                  |  3 ++-\n scalar.c                    |  3 ++-\n sequencer.c                 |  8 +++++---\n setup.c                     | 15 ++++++++++-----\n submodule-config.c          | 15 +++++++++++----\n t/helper/test-config.c      |  9 ++++++---\n t/helper/test-userdiff.c    |  4 +++-\n trace2/tr2_cfg.c            |  3 ++-\n trace2/tr2_sysenv.c         |  3 ++-\n trailer.c                   |  2 ++\n upload-pack.c               |  8 ++++++--\n urlmatch.c                  |  3 ++-\n urlmatch.h                  |  3 ++-\n xdiff-interface.c           |  5 +++--\n xdiff-interface.h           |  3 ++-\n 85 files changed, 285 insertions(+), 156 deletions(-)\n\ndiff --git a/alias.c b/alias.c\nindex e814948ced3..38c51038a13 100644\n--- a/alias.c\n+++ b/alias.c\n@@ -11,7 +11,8 @@ struct config_alias_data {\n \tstruct string_list *list;\n };\n \n-static int config_alias_cb(const char *key, const char *value, void *d)\n+static int config_alias_cb(const char *key, const char *value,\n+\t\t\t   struct key_value_info *kvi UNUSED, void *d)\n {\n \tstruct config_alias_data *data = d;\n \tconst char *p;\ndiff --git a/archive-tar.c b/archive-tar.c\nindex 497dad0b3af..dcfbce5225a 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -410,7 +410,8 @@ static int tar_filter_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-static int git_tar_config(const char *var, const char *value, void *cb)\n+static int git_tar_config(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n {\n \tif (!strcmp(var, \"tar.umask\")) {\n \t\tif (value && !strcmp(value, \"user\")) {\ndiff --git a/archive-zip.c b/archive-zip.c\nindex e6f5c10a14f..0b028246689 100644\n--- a/archive-zip.c\n+++ b/archive-zip.c\n@@ -616,6 +616,7 @@ static void dos_time(timestamp_t *timestamp, int *dos_date, int *dos_time)\n }\n \n static int archive_zip_config(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi UNUSED,\n \t\t\t      void *data UNUSED)\n {\n \treturn userdiff_config(var, value);\ndiff --git a/builtin/add.c b/builtin/add.c\nindex f12054d9be1..f8e42e05b07 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -355,7 +355,8 @@ static struct option builtin_add_options[] = {\n \tOPT_END(),\n };\n \n-static int add_config(const char *var, const char *value, void *cb)\n+static int add_config(const char *var, const char *value,\n+\t\t      struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"add.ignoreerrors\") ||\n \t    !strcmp(var, \"add.ignore-errors\")) {\n@@ -363,7 +364,7 @@ static int add_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static const char embedded_advice[] = N_(\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex a8d2114adc9..0aafc8172e2 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -692,7 +692,8 @@ static const char *add_prefix(const char *prefix, const char *path)\n \treturn prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n }\n \n-static int git_blame_config(const char *var, const char *value, void *cb)\n+static int git_blame_config(const char *var, const char *value,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"blame.showroot\")) {\n \t\tshow_root = git_config_bool(var, value);\n@@ -765,7 +766,7 @@ static int git_blame_config(const char *var, const char *value, void *cb)\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static int blame_copy_callback(const struct option *option, const char *arg, int unset)\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex c6982181fd5..26091a036d2 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -80,7 +80,8 @@ static unsigned int colopts;\n \n define_list_config_array(color_branch_slots);\n \n-static int git_branch_config(const char *var, const char *value, void *cb)\n+static int git_branch_config(const char *var, const char *value,\n+\t\t\t     struct key_value_info *kvi, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -117,7 +118,7 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static const char *branch_get_color(enum color_branch ix)\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 04d4bb6c777..b1e0e95d631 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -882,12 +882,13 @@ static int batch_objects(struct batch_options *opt)\n \treturn retval;\n }\n \n-static int git_cat_file_config(const char *var, const char *value, void *cb)\n+static int git_cat_file_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi, void *cb)\n {\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static int batch_option_callback(const struct option *opt,\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 38a8cd6a965..92017ba6696 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1182,7 +1182,8 @@ static int switch_branches(const struct checkout_opts *opts,\n \treturn ret || writeout_error;\n }\n \n-static int git_checkout_config(const char *var, const char *value, void *cb)\n+static int git_checkout_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi, void *cb)\n {\n \tstruct checkout_opts *opts = cb;\n \n@@ -1198,7 +1199,7 @@ static int git_checkout_config(const char *var, const char *value, void *cb)\n \tif (starts_with(var, \"submodule.\"))\n \t\treturn git_default_submodule_config(var, value, NULL);\n \n-\treturn git_xmerge_config(var, value, NULL);\n+\treturn git_xmerge_config(var, value,kvi, NULL);\n }\n \n static void setup_new_branch_info_and_source_tree(\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex a06df48a269..1c648276ebf 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -102,7 +102,8 @@ struct menu_stuff {\n \n define_list_config_array(color_interactive_slots);\n \n-static int git_clean_config(const char *var, const char *value, void *cb)\n+static int git_clean_config(const char *var, const char *value,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -132,7 +133,7 @@ static int git_clean_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static const char *clean_get_color(enum color_clean ix)\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 6dc89f1058b..1e1cf104194 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -775,7 +775,8 @@ static int checkout(int submodule_progress, int filter_submodules)\n \treturn err;\n }\n \n-static int git_clone_config(const char *k, const char *v, void *cb)\n+static int git_clone_config(const char *k, const char *v,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(k, \"clone.defaultremotename\")) {\n \t\tfree(remote_name);\n@@ -786,10 +787,11 @@ static int git_clone_config(const char *k, const char *v, void *cb)\n \tif (!strcmp(k, \"clone.filtersubmodules\"))\n \t\tconfig_filter_submodules = git_config_bool(k, v);\n \n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v,kvi, cb);\n }\n \n-static int write_one_config(const char *key, const char *value, void *data)\n+static int write_one_config(const char *key, const char *value,\n+\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n {\n \t/*\n \t * give git_clone_config a chance to write config values back to the\ndiff --git a/builtin/column.c b/builtin/column.c\nindex de623a16c2d..30cfbed62ec 100644\n--- a/builtin/column.c\n+++ b/builtin/column.c\n@@ -13,7 +13,8 @@ static const char * const builtin_column_usage[] = {\n };\n static unsigned int colopts;\n \n-static int column_config(const char *var, const char *value, void *cb)\n+static int column_config(const char *var, const char *value,\n+\t\t\t struct key_value_info *kvi UNUSED, void *cb)\n {\n \treturn git_column_config(var, value, cb, &colopts);\n }\ndiff --git a/builtin/commit-graph.c b/builtin/commit-graph.c\nindex 90114269761..e811866b5dd 100644\n--- a/builtin/commit-graph.c\n+++ b/builtin/commit-graph.c\n@@ -185,6 +185,7 @@ static int write_option_max_new_filters(const struct option *opt,\n }\n \n static int git_commit_graph_write_config(const char *var, const char *value,\n+\t\t\t\t\t struct key_value_info *kvi UNUSED,\n \t\t\t\t\t void *cb UNUSED)\n {\n \tif (!strcmp(var, \"commitgraph.maxnewfilters\"))\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 9d8e1ea91a3..ec468e87039 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1402,7 +1402,8 @@ static int parse_status_slot(const char *slot)\n \treturn LOOKUP_CONFIG(color_status_slots, slot);\n }\n \n-static int git_status_config(const char *k, const char *v, void *cb)\n+static int git_status_config(const char *k, const char *v,\n+\t\t\t     struct key_value_info *kvi, void *cb)\n {\n \tstruct wt_status *s = cb;\n \tconst char *slot_name;\n@@ -1487,7 +1488,7 @@ static int git_status_config(const char *k, const char *v, void *cb)\n \t\ts->detect_rename = git_config_rename(k, v);\n \t\treturn 0;\n \t}\n-\treturn git_diff_ui_config(k, v, NULL);\n+\treturn git_diff_ui_config(k, v,kvi, NULL);\n }\n \n int cmd_status(int argc, const char **argv, const char *prefix)\n@@ -1602,7 +1603,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)\n \treturn 0;\n }\n \n-static int git_commit_config(const char *k, const char *v, void *cb)\n+static int git_commit_config(const char *k, const char *v,\n+\t\t\t     struct key_value_info *kvi, void *cb)\n {\n \tstruct wt_status *s = cb;\n \n@@ -1624,7 +1626,7 @@ static int git_commit_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_status_config(k, v, s);\n+\treturn git_status_config(k, v,kvi, s);\n }\n \n int cmd_commit(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/config.c b/builtin/config.c\nindex fe79fb60c43..b2ad7351d0a 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -214,7 +214,7 @@ static void show_config_scope(struct strbuf *buf)\n }\n \n static int show_all_config(const char *key_, const char *value_,\n-\t\t\t   void *cb UNUSED)\n+\t\t\t   struct key_value_info *kvi UNUSED, void *cb UNUSED)\n {\n \tif (show_origin || show_scope) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n@@ -298,7 +298,8 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value\n \treturn 0;\n }\n \n-static int collect_config(const char *key_, const char *value_, void *cb)\n+static int collect_config(const char *key_, const char *value_,\n+\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct strbuf_list *values = cb;\n \n@@ -466,6 +467,7 @@ static const char *get_colorbool_slot;\n static char parsed_color[COLOR_MAXLEN];\n \n static int git_get_color_config(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi UNUSED,\n \t\t\t\tvoid *cb UNUSED)\n {\n \tif (!strcmp(var, get_color_slot)) {\n@@ -498,6 +500,7 @@ static int get_colorbool_found;\n static int get_diff_color_found;\n static int get_color_ui_found;\n static int git_get_colorbool_config(const char *var, const char *value,\n+\t\t\t\t    struct key_value_info *kvi UNUSED,\n \t\t\t\t    void *data UNUSED)\n {\n \tif (!strcmp(var, get_colorbool_slot))\n@@ -555,7 +558,8 @@ struct urlmatch_current_candidate_value {\n \tstruct strbuf value;\n };\n \n-static int urlmatch_collect_fn(const char *var, const char *value, void *cb)\n+static int urlmatch_collect_fn(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct string_list *values = cb;\n \tstruct string_list_item *item = string_list_insert(values, var);\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex e010a21bfbc..d4d149bcf6b 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -38,14 +38,15 @@ static const char *const builtin_difftool_usage[] = {\n \tNULL\n };\n \n-static int difftool_config(const char *var, const char *value, void *cb)\n+static int difftool_config(const char *var, const char *value,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"difftool.trustexitcode\")) {\n \t\ttrust_exit_code = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static int print_tool_help(void)\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 85bd2801036..aa688291613 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -100,7 +100,8 @@ static int fetch_write_commit_graph = -1;\n static int stdin_refspecs = 0;\n static int negotiate_only;\n \n-static int git_fetch_config(const char *k, const char *v, void *cb)\n+static int git_fetch_config(const char *k, const char *v,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(k, \"fetch.prune\")) {\n \t\tfetch_prune_config = git_config_bool(k, v);\n@@ -140,7 +141,7 @@ static int git_fetch_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v,kvi, cb);\n }\n \n static int parse_refmap_arg(const struct option *opt, const char *arg, int unset)\n@@ -1828,7 +1829,8 @@ struct remote_group_data {\n \tstruct string_list *list;\n };\n \n-static int get_remote_group(const char *key, const char *value, void *priv)\n+static int get_remote_group(const char *key, const char *value,\n+\t\t\t    struct key_value_info *kvi UNUSED, void *priv)\n {\n \tstruct remote_group_data *g = priv;\n \ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex 42af6a2cc7e..a7375d61d02 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -36,7 +36,8 @@ static int fsmonitor__start_timeout_sec = 60;\n #define FSMONITOR__ANNOUNCE_STARTUP \"fsmonitor.announcestartup\"\n static int fsmonitor__announce_startup = 0;\n \n-static int fsmonitor_config(const char *var, const char *value, void *cb)\n+static int fsmonitor_config(const char *var, const char *value,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, FSMONITOR__IPC_THREADS)) {\n \t\tint i = git_config_int(var, value);\n@@ -66,7 +67,7 @@ static int fsmonitor_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n /*\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex c880c9538d6..177befc3ed4 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -287,13 +287,14 @@ static int wait_all(void)\n \treturn hit;\n }\n \n-static int grep_cmd_config(const char *var, const char *value, void *cb)\n+static int grep_cmd_config(const char *var, const char *value,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tint st = grep_config(var, value, cb);\n \n \tif (git_color_config(var, value, cb) < 0)\n \t\tst = -1;\n-\telse if (git_default_config(var, value, cb) < 0)\n+\telse if (git_default_config(var, value,kvi, cb) < 0)\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\ndiff --git a/builtin/help.c b/builtin/help.c\nindex 87333a02ec4..5d4a86c4b41 100644\n--- a/builtin/help.c\n+++ b/builtin/help.c\n@@ -396,7 +396,8 @@ static int add_man_viewer_info(const char *var, const char *value)\n \treturn 0;\n }\n \n-static int git_help_config(const char *var, const char *value, void *cb)\n+static int git_help_config(const char *var, const char *value,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"help.format\")) {\n \t\tif (!value)\n@@ -419,7 +420,7 @@ static int git_help_config(const char *var, const char *value, void *cb)\n \tif (starts_with(var, \"man.\"))\n \t\treturn add_man_viewer_info(var, value);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static struct cmdnames main_cmds, other_cmds;\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex b17e79cd40f..4450510ddfc 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1578,7 +1578,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name,\n \tstrbuf_release(&pack_name);\n }\n \n-static int git_index_pack_config(const char *k, const char *v, void *cb)\n+static int git_index_pack_config(const char *k, const char *v,\n+\t\t\t\t struct key_value_info *kvi, void *cb)\n {\n \tstruct pack_idx_option *opts = cb;\n \n@@ -1605,7 +1606,7 @@ static int git_index_pack_config(const char *k, const char *v, void *cb)\n \t\telse\n \t\t\topts->flags &= ~WRITE_REV;\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v,kvi, cb);\n }\n \n static int cmp_uint32(const void *a_, const void *b_)\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 7d195789633..f8e61330491 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -559,7 +559,8 @@ static int cmd_log_walk(struct rev_info *rev)\n \treturn retval;\n }\n \n-static int git_log_config(const char *var, const char *value, void *cb)\n+static int git_log_config(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -608,7 +609,7 @@ static int git_log_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_diff_ui_config(var, value, cb);\n+\treturn git_diff_ui_config(var, value,kvi, cb);\n }\n \n int cmd_whatchanged(int argc, const char **argv, const char *prefix)\n@@ -974,7 +975,8 @@ static enum cover_from_description parse_cover_from_description(const char *arg)\n \t\tdie(_(\"%s: invalid cover from description mode\"), arg);\n }\n \n-static int git_format_config(const char *var, const char *value, void *cb)\n+static int git_format_config(const char *var, const char *value,\n+\t\t\t     struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"format.headers\")) {\n \t\tif (!value)\n@@ -1103,7 +1105,7 @@ static int git_format_config(const char *var, const char *value, void *cb)\n \tif (!strcmp(var, \"diff.noprefix\"))\n \t\treturn 0;\n \n-\treturn git_log_config(var, value, cb);\n+\treturn git_log_config(var, value,kvi, cb);\n }\n \n static const char *output_directory = NULL;\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex a99be9610e9..492a83a900c 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -620,7 +620,8 @@ static void parse_branch_merge_options(char *bmo)\n \tfree(argv);\n }\n \n-static int git_merge_config(const char *k, const char *v, void *cb)\n+static int git_merge_config(const char *k, const char *v,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tint status;\n \tconst char *str;\n@@ -665,10 +666,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\tstatus = fmt_merge_msg_config(k, v, cb);\n+\tstatus = fmt_merge_msg_config(k, v,kvi, cb);\n \tif (status)\n \t\treturn status;\n-\treturn git_diff_ui_config(k, v, cb);\n+\treturn git_diff_ui_config(k, v,kvi, cb);\n }\n \n static int read_tree_trivial(struct object_id *common, struct object_id *head,\ndiff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\nindex 1b5083f8b26..c3cd7163c84 100644\n--- a/builtin/multi-pack-index.c\n+++ b/builtin/multi-pack-index.c\n@@ -82,6 +82,7 @@ static struct option *add_common_options(struct option *prev)\n }\n \n static int git_multi_pack_index_write_config(const char *var, const char *value,\n+\t\t\t\t\t     struct key_value_info *kvi UNUSED,\n \t\t\t\t\t     void *cb UNUSED)\n {\n \tif (!strcmp(var, \"pack.writebitmaphashcache\")) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 77d88f85b04..ca023000cc0 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3134,7 +3134,8 @@ static void prepare_pack(int window, int depth)\n \tfree(delta_list);\n }\n \n-static int git_pack_config(const char *k, const char *v, void *cb)\n+static int git_pack_config(const char *k, const char *v,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(k, \"pack.window\")) {\n \t\twindow = git_config_int(k, v);\n@@ -3226,7 +3227,7 @@ static int git_pack_config(const char *k, const char *v, void *cb)\n \t\tex->uri = xstrdup(pack_end + 1);\n \t\toidmap_put(&configured_exclusions, ex);\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v,kvi, cb);\n }\n \n /* Counters for trace2 output when in --stdin-packs mode. */\ndiff --git a/builtin/patch-id.c b/builtin/patch-id.c\nindex 9d5585d3a72..9b4f5a71b87 100644\n--- a/builtin/patch-id.c\n+++ b/builtin/patch-id.c\n@@ -196,7 +196,8 @@ struct patch_id_opts {\n \tint verbatim;\n };\n \n-static int git_patch_id_config(const char *var, const char *value, void *cb)\n+static int git_patch_id_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi, void *cb)\n {\n \tstruct patch_id_opts *opts = cb;\n \n@@ -209,7 +210,7 @@ static int git_patch_id_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n int cmd_patch_id(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 5405d09f22f..1b244eee67c 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -359,7 +359,8 @@ static enum rebase_type config_get_rebase(int *rebase_unspecified)\n /**\n  * Read config variables.\n  */\n-static int git_pull_config(const char *var, const char *value, void *cb)\n+static int git_pull_config(const char *var, const char *value,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"rebase.autostash\")) {\n \t\tconfig_autostash = git_config_bool(var, value);\n@@ -372,7 +373,7 @@ static int git_pull_config(const char *var, const char *value, void *cb)\n \t\tcheck_trust_level = 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n /**\ndiff --git a/builtin/push.c b/builtin/push.c\nindex fa550b8f80a..65b79378b92 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -506,7 +506,8 @@ static void set_push_cert_flags(int *flags, int v)\n }\n \n \n-static int git_push_config(const char *k, const char *v, void *cb)\n+static int git_push_config(const char *k, const char *v,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tconst char *slot_name;\n \tint *flags = cb;\n@@ -573,7 +574,7 @@ static int git_push_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(k, v, NULL);\n+\treturn git_default_config(k, v,kvi, NULL);\n }\n \n int cmd_push(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/read-tree.c b/builtin/read-tree.c\nindex 600d4f748fc..e9859b6157e 100644\n--- a/builtin/read-tree.c\n+++ b/builtin/read-tree.c\n@@ -100,12 +100,13 @@ static int debug_merge(const struct cache_entry * const *stages,\n \treturn 0;\n }\n \n-static int git_read_tree_config(const char *var, const char *value, void *cb)\n+static int git_read_tree_config(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"submodule.recurse\"))\n \t\treturn git_default_submodule_config(var, value, cb);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 680fe3c1453..1fd05d708b8 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -791,7 +791,8 @@ static void parse_rebase_merges_value(struct rebase_options *options, const char\n \t\tdie(_(\"Unknown rebase-merges mode: %s\"), value);\n }\n \n-static int rebase_config(const char *var, const char *value, void *data)\n+static int rebase_config(const char *var, const char *value,\n+\t\t\t struct key_value_info *kvi, void *data)\n {\n \tstruct rebase_options *opts = data;\n \n@@ -850,7 +851,7 @@ static int rebase_config(const char *var, const char *value, void *data)\n \t\treturn git_config_string(&opts->default_backend, var, value);\n \t}\n \n-\treturn git_default_config(var, value, data);\n+\treturn git_default_config(var, value,kvi, data);\n }\n \n static int checkout_up_to_date(struct rebase_options *options)\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 9109552533d..2f5fd2abbc3 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -135,7 +135,8 @@ static enum deny_action parse_deny_action(const char *var, const char *value)\n \treturn DENY_IGNORE;\n }\n \n-static int receive_pack_config(const char *var, const char *value, void *cb)\n+static int receive_pack_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi, void *cb)\n {\n \tint status = parse_hide_refs_config(var, value, \"receive\", &hidden_refs);\n \n@@ -262,7 +263,7 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static void show_ref(const char *path, const struct object_id *oid)\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex a1fa0c855f4..ecf21ac9c6e 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -108,7 +108,8 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)\n #define EXPIRE_TOTAL   01\n #define EXPIRE_UNREACH 02\n \n-static int reflog_expire_config(const char *var, const char *value, void *cb)\n+static int reflog_expire_config(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi, void *cb)\n {\n \tconst char *pattern, *key;\n \tsize_t pattern_len;\n@@ -117,7 +118,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)\n \tstruct reflog_expire_cfg *ent;\n \n \tif (parse_config_key(var, \"gc\", &pattern, &pattern_len, &key) < 0)\n-\t\treturn git_default_config(var, value, cb);\n+\t\treturn git_default_config(var, value,kvi, cb);\n \n \tif (!strcmp(key, \"reflogexpire\")) {\n \t\tslot = EXPIRE_TOTAL;\n@@ -128,7 +129,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)\n \t\tif (git_config_expiry_date(&expire, var, value))\n \t\t\treturn -1;\n \t} else\n-\t\treturn git_default_config(var, value, cb);\n+\t\treturn git_default_config(var, value,kvi, cb);\n \n \tif (!pattern) {\n \t\tswitch (slot) {\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 1e0b137d977..edb4a9ddd7f 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -268,6 +268,7 @@ static const char *abbrev_ref(const char *name, const char *prefix)\n #define abbrev_branch(name) abbrev_ref((name), \"refs/heads/\")\n \n static int config_read_branches(const char *key, const char *value,\n+\t\t\t\tstruct key_value_info *kvi UNUSED,\n \t\t\t\tvoid *data UNUSED)\n {\n \tconst char *orig_key = key;\n@@ -645,7 +646,7 @@ struct push_default_info\n };\n \n static int config_read_push_default(const char *key, const char *value,\n-\tvoid *cb)\n+\tstruct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct push_default_info* info = cb;\n \tif (strcmp(key, \"remote.pushdefault\") ||\n@@ -1494,7 +1495,8 @@ static int prune(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n-static int get_remote_default(const char *key, const char *value UNUSED, void *priv)\n+static int get_remote_default(const char *key, const char *value UNUSED,\n+\t\t\t      struct key_value_info *kvi UNUSED, void *priv)\n {\n \tif (strcmp(key, \"remotes.default\") == 0) {\n \t\tint *found = priv;\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex df4d8e0f0ba..7c8401b2227 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -58,7 +58,8 @@ struct pack_objects_args {\n \tint local;\n };\n \n-static int repack_config(const char *var, const char *value, void *cb)\n+static int repack_config(const char *var, const char *value,\n+\t\t\t struct key_value_info *kvi, void *cb)\n {\n \tstruct pack_objects_args *cruft_po_args = cb;\n \tif (!strcmp(var, \"repack.usedeltabaseoffset\")) {\n@@ -90,7 +91,7 @@ static int repack_config(const char *var, const char *value, void *cb)\n \t\treturn git_config_string(&cruft_po_args->depth, var, value);\n \tif (!strcmp(var, \"repack.cruftthreads\"))\n \t\treturn git_config_string(&cruft_po_args->threads, var, value);\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n /*\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 0ed329236c8..a04d46d7fdd 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -308,12 +308,13 @@ static int reset_refs(const char *rev, const struct object_id *oid)\n \treturn update_ref_status;\n }\n \n-static int git_reset_config(const char *var, const char *value, void *cb)\n+static int git_reset_config(const char *var, const char *value,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"submodule.recurse\"))\n \t\treturn git_default_submodule_config(var, value, cb);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n int cmd_reset(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 4784143004d..b0c90e549b8 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -131,7 +131,8 @@ static void print_helper_status(struct ref *ref)\n \tstrbuf_release(&buf);\n }\n \n-static int send_pack_config(const char *k, const char *v, void *cb)\n+static int send_pack_config(const char *k, const char *v,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(k, \"push.gpgsign\")) {\n \t\tconst char *value;\n@@ -151,7 +152,7 @@ static int send_pack_config(const char *k, const char *v, void *cb)\n \t\t\t}\n \t\t}\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v,kvi, cb);\n }\n \n int cmd_send_pack(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex 82ae2a7e475..ad8a391904e 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -556,7 +556,8 @@ static void append_one_rev(const char *av)\n \tdie(\"bad sha1 reference %s\", av);\n }\n \n-static int git_show_branch_config(const char *var, const char *value, void *cb)\n+static int git_show_branch_config(const char *var, const char *value,\n+\t\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"showbranch.default\")) {\n \t\tif (!value)\n@@ -579,7 +580,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static int omit_in_dense(struct commit *commit, struct commit **rev, int n)\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 735d27039e1..689087d6240 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -836,7 +836,8 @@ static int show_stat = 1;\n static int show_patch;\n static int show_include_untracked;\n \n-static int git_stash_config(const char *var, const char *value, void *cb)\n+static int git_stash_config(const char *var, const char *value,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"stash.showstat\")) {\n \t\tshow_stat = git_config_bool(var, value);\n@@ -850,7 +851,7 @@ static int git_stash_config(const char *var, const char *value, void *cb)\n \t\tshow_include_untracked = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\treturn git_diff_basic_config(var, value, cb);\n+\treturn git_diff_basic_config(var, value,kvi, cb);\n }\n \n static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 569068e6a2c..8570effbf0d 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2187,6 +2187,7 @@ static int update_clone_task_finished(int result,\n }\n \n static int git_update_clone_config(const char *var, const char *value,\n+\t\t\t\t   struct key_value_info *kvi UNUSED,\n \t\t\t\t   void *cb)\n {\n \tint *max_jobs = cb;\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 7245a4d30e6..626fe8c641e 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -183,7 +183,8 @@ static const char tag_template_nocleanup[] =\n \t\"Lines starting with '%c' will be kept; you may remove them\"\n \t\" yourself if you want to.\\n\");\n \n-static int git_tag_config(const char *var, const char *value, void *cb)\n+static int git_tag_config(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"tag.gpgsign\")) {\n \t\tconfig_sign_tag = git_config_bool(var, value);\n@@ -208,7 +209,7 @@ static int git_tag_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static void write_tag_body(int fd, const struct object_id *oid)\ndiff --git a/builtin/var.c b/builtin/var.c\nindex acb988d2d56..440add19bb5 100644\n--- a/builtin/var.c\n+++ b/builtin/var.c\n@@ -69,13 +69,14 @@ static const struct git_var *get_git_var(const char *var)\n \treturn NULL;\n }\n \n-static int show_config(const char *var, const char *value, void *cb)\n+static int show_config(const char *var, const char *value,\n+\t\t       struct key_value_info *kvi, void *cb)\n {\n \tif (value)\n \t\tprintf(\"%s=%s\\n\", var, value);\n \telse\n \t\tprintf(\"%s\\n\", var);\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n int cmd_var(int argc, const char **argv, const char *prefix UNUSED)\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 39e9e5c9ce8..d5ccd741e87 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -103,14 +103,15 @@ static int verbose;\n static int guess_remote;\n static timestamp_t expire;\n \n-static int git_worktree_config(const char *var, const char *value, void *cb)\n+static int git_worktree_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"worktree.guessremote\")) {\n \t\tguess_remote = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static int delete_git_dir(const char *id)\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex 8c4e2b70b89..bb88ccbca4b 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -223,7 +223,9 @@ static int bundle_list_update(const char *key, const char *value,\n \treturn 0;\n }\n \n-static int config_to_bundle_list(const char *key, const char *value, void *data)\n+static int config_to_bundle_list(const char *key, const char *value,\n+\t\t\t\t struct key_value_info *kvi UNUSED,\n+\t\t\t\t void *data)\n {\n \tstruct bundle_list *list = data;\n \treturn bundle_list_update(key, value, list);\n@@ -871,7 +873,9 @@ cached:\n \treturn advertise_bundle_uri;\n }\n \n-static int config_to_packet_line(const char *key, const char *value, void *data)\n+static int config_to_packet_line(const char *key, const char *value,\n+\t\t\t\t struct key_value_info *kvi UNUSED,\n+\t\t\t\t void *data)\n {\n \tstruct packet_reader *writer = data;\n \ndiff --git a/config.c b/config.c\nindex 758d6a5cc3b..60f8c0c666b 100644\n--- a/config.c\n+++ b/config.c\n@@ -156,7 +156,8 @@ struct config_include_data {\n };\n #define CONFIG_INCLUDE_INIT { 0 }\n \n-static int git_config_include(const char *var, const char *value, void *data);\n+static int git_config_include(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi, void *data);\n \n #define MAX_INCLUDE_DEPTH 10\n static const char include_depth_advice[] = N_(\n@@ -336,7 +337,8 @@ static int include_by_branch(const char *cond, size_t cond_len)\n \treturn ret;\n }\n \n-static int add_remote_url(const char *var, const char *value, void *data)\n+static int add_remote_url(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct string_list *remote_urls = data;\n \tconst char *remote_name;\n@@ -364,6 +366,7 @@ static void populate_remote_urls(struct config_include_data *inc)\n }\n \n static int forbid_remote_url(const char *var, const char *value UNUSED,\n+\t\t\t     struct key_value_info *kvi UNUSED,\n \t\t\t     void *data UNUSED)\n {\n \tconst char *remote_name;\n@@ -429,7 +432,8 @@ static int include_condition_is_true(struct key_value_info *kvi,\n \n static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n \t\t  struct key_value_info *kvi, void *data);\n-static int git_config_include(const char *var, const char *value, void *data)\n+static int git_config_include(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct config_include_data *inc = data;\n \tstruct key_value_info *kvi = inc->config_reader->config_kvi;\n@@ -1541,7 +1545,8 @@ int git_config_color(char *dest, const char *var, const char *value)\n \treturn 0;\n }\n \n-static int git_default_core_config(const char *var, const char *value, void *cb)\n+static int git_default_core_config(const char *var, const char *value,\n+\t\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \t/* This needs a better name */\n \tif (!strcmp(var, \"core.filemode\")) {\n@@ -1826,7 +1831,7 @@ static int git_default_core_config(const char *var, const char *value, void *cb)\n \t}\n \n \t/* Add other config variables here and to Documentation/config.txt. */\n-\treturn platform_core_config(var, value, cb);\n+\treturn platform_core_config(var, value,kvi, cb);\n }\n \n static int git_default_sparse_config(const char *var, const char *value)\n@@ -1928,15 +1933,16 @@ static int git_default_mailmap_config(const char *var, const char *value)\n \treturn 0;\n }\n \n-int git_default_config(const char *var, const char *value, void *cb)\n+int git_default_config(const char *var, const char *value,\n+\t\t       struct key_value_info *kvi, void *cb)\n {\n \tif (starts_with(var, \"core.\"))\n-\t\treturn git_default_core_config(var, value, cb);\n+\t\treturn git_default_core_config(var, value,kvi, cb);\n \n \tif (starts_with(var, \"user.\") ||\n \t    starts_with(var, \"author.\") ||\n \t    starts_with(var, \"committer.\"))\n-\t\treturn git_ident_config(var, value, cb);\n+\t\treturn git_ident_config(var, value,kvi, cb);\n \n \tif (starts_with(var, \"i18n.\"))\n \t\treturn git_default_i18n_config(var, value);\n@@ -2455,7 +2461,8 @@ struct configset_add_data {\n };\n #define CONFIGSET_ADD_INIT { 0 }\n \n-static int config_set_callback(const char *key, const char *value, void *cb)\n+static int config_set_callback(const char *key, const char *value,\n+\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct configset_add_data *data = cb;\n \tconfigset_add_value(data->config_reader, data->config_set, key, value);\n@@ -3063,7 +3070,8 @@ static int store_aux_event(enum config_event_t type, size_t begin, size_t end,\n \treturn 0;\n }\n \n-static int store_aux(const char *key, const char *value, void *cb)\n+static int store_aux(const char *key, const char *value,\n+\t\t     struct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct config_store_data *store = cb;\n \ndiff --git a/config.h b/config.h\nindex 2c7b7399691..e4893e237e1 100644\n--- a/config.h\n+++ b/config.h\n@@ -146,7 +146,8 @@ struct key_value_info {\n  */\n typedef int (*config_fn_t)(const char *, const char *, struct key_value_info *, void *);\n \n-int git_default_config(const char *, const char *, void *);\n+int git_default_config(const char *, const char *, struct key_value_info *,\n+\t\t       void *);\n \n /**\n  * Read a specific file in git-config format.\ndiff --git a/connect.c b/connect.c\nindex c0c8a38178c..72aad67a288 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -962,7 +962,7 @@ static struct child_process *git_tcp_connect(int fd[2], char *host, int flags)\n static char *git_proxy_command;\n \n static int git_proxy_command_options(const char *var, const char *value,\n-\t\tvoid *cb)\n+\t\tstruct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"core.gitproxy\")) {\n \t\tconst char *for_pos;\n@@ -1008,7 +1008,7 @@ static int git_proxy_command_options(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static int git_use_proxy(const char *host)\ndiff --git a/convert.c b/convert.c\nindex da06e2f51cb..a563380e7e7 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1011,7 +1011,9 @@ static int apply_filter(const char *path, const char *src, size_t len,\n \treturn 0;\n }\n \n-static int read_convert_config(const char *var, const char *value, void *cb UNUSED)\n+static int read_convert_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi UNUSED,\n+\t\t\t       void *cb UNUSED)\n {\n \tconst char *key, *name;\n \tsize_t namelen;\ndiff --git a/credential.c b/credential.c\nindex e6417bf8804..6b9b8bfbb79 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -46,6 +46,7 @@ static int credential_from_potentially_partial_url(struct credential *c,\n \t\t\t\t\t\t   const char *url);\n \n static int credential_config_callback(const char *var, const char *value,\n+\t\t\t\t      struct key_value_info *kvi UNUSED,\n \t\t\t\t      void *data)\n {\n \tstruct credential *c = data;\ndiff --git a/delta-islands.c b/delta-islands.c\nindex 40f2ccfb550..404f9c07a42 100644\n--- a/delta-islands.c\n+++ b/delta-islands.c\n@@ -341,7 +341,8 @@ static void free_remote_islands(kh_str_t *remote_islands)\n \tkh_destroy_str(remote_islands);\n }\n \n-static int island_config_callback(const char *k, const char *v, void *cb)\n+static int island_config_callback(const char *k, const char *v,\n+\t\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct island_load_data *ild = cb;\n \ndiff --git a/diff.c b/diff.c\nindex 78b0fdd8caa..d7ed2dc900b 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -350,7 +350,8 @@ static unsigned parse_color_moved_ws(const char *arg)\n \treturn ret;\n }\n \n-int git_diff_ui_config(const char *var, const char *value, void *cb)\n+int git_diff_ui_config(const char *var, const char *value,\n+\t\t       struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"diff.color\") || !strcmp(var, \"color.diff\")) {\n \t\tdiff_use_color_default = git_config_colorbool(var, value);\n@@ -433,10 +434,11 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_diff_basic_config(var, value, cb);\n+\treturn git_diff_basic_config(var, value,kvi, cb);\n }\n \n-int git_diff_basic_config(const char *var, const char *value, void *cb)\n+int git_diff_basic_config(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tconst char *name;\n \n@@ -488,7 +490,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)\n \tif (git_diff_heuristic_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static char *quote_two(const char *one, const char *two)\ndiff --git a/diff.h b/diff.h\nindex 6a0737b9c34..6a3efa63753 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -532,10 +532,12 @@ void free_diffstat_info(struct diffstat_t *diffstat);\n int parse_long_opt(const char *opt, const char **argv,\n \t\t   const char **optarg);\n \n-int git_diff_basic_config(const char *var, const char *value, void *cb);\n+int git_diff_basic_config(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi, void *cb);\n int git_diff_heuristic_config(const char *var, const char *value, void *cb);\n void init_diff_ui_defaults(void);\n-int git_diff_ui_config(const char *var, const char *value, void *cb);\n+int git_diff_ui_config(const char *var, const char *value,\n+\t\t       struct key_value_info *kvi, void *cb);\n void repo_diff_setup(struct repository *, struct diff_options *);\n struct option *add_diff_options(const struct option *, struct diff_options *);\n int diff_opt_parse(struct diff_options *, const char **, int, const char *);\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 368f2ed25a1..c1072aa6cac 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1858,7 +1858,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \treturn ref;\n }\n \n-static int fetch_pack_config_cb(const char *var, const char *value, void *cb)\n+static int fetch_pack_config_cb(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi, void *cb)\n {\n \tif (strcmp(var, \"fetch.fsck.skiplist\") == 0) {\n \t\tconst char *path;\n@@ -1880,7 +1881,7 @@ static int fetch_pack_config_cb(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static void fetch_pack_config(void)\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 1886c92ddb9..97358034fa0 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -19,7 +19,8 @@ static int use_branch_desc;\n static int suppress_dest_pattern_seen;\n static struct string_list suppress_dest_patterns = STRING_LIST_INIT_DUP;\n \n-int fmt_merge_msg_config(const char *key, const char *value, void *cb)\n+int fmt_merge_msg_config(const char *key, const char *value,\n+\t\t\t struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(key, \"merge.log\") || !strcmp(key, \"merge.summary\")) {\n \t\tint is_bool;\n@@ -39,7 +40,7 @@ int fmt_merge_msg_config(const char *key, const char *value, void *cb)\n \t\t\tstring_list_append(&suppress_dest_patterns, value);\n \t\tsuppress_dest_pattern_seen = 1;\n \t} else {\n-\t\treturn git_default_config(key, value, cb);\n+\t\treturn git_default_config(key, value,kvi, cb);\n \t}\n \treturn 0;\n }\ndiff --git a/fmt-merge-msg.h b/fmt-merge-msg.h\nindex 99054042dc5..5262c39268a 100644\n--- a/fmt-merge-msg.h\n+++ b/fmt-merge-msg.h\n@@ -13,7 +13,8 @@ struct fmt_merge_msg_opts {\n };\n \n extern int merge_log_config;\n-int fmt_merge_msg_config(const char *key, const char *value, void *cb);\n+int fmt_merge_msg_config(const char *key, const char *value,\n+\t\t\t struct key_value_info *kvi, void *cb);\n int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \t\t  struct fmt_merge_msg_opts *);\n \ndiff --git a/fsck.c b/fsck.c\nindex 4238344ed82..ec26857c79d 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1162,7 +1162,8 @@ struct fsck_gitmodules_data {\n \tint ret;\n };\n \n-static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)\n+static int fsck_gitmodules_fn(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi UNUSED, void *vdata)\n {\n \tstruct fsck_gitmodules_data *data = vdata;\n \tconst char *subsection, *key;\n@@ -1373,7 +1374,8 @@ int fsck_finish(struct fsck_options *options)\n \treturn ret;\n }\n \n-int git_fsck_config(const char *var, const char *value, void *cb)\n+int git_fsck_config(const char *var, const char *value,\n+\t\t    struct key_value_info *kvi, void *cb)\n {\n \tstruct fsck_options *options = cb;\n \tif (strcmp(var, \"fsck.skiplist\") == 0) {\n@@ -1394,7 +1396,7 @@ int git_fsck_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n /*\ndiff --git a/fsck.h b/fsck.h\nindex e17730e9da9..a06f202576c 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -237,6 +237,7 @@ const char *fsck_describe_object(struct fsck_options *options,\n  * git_config() callback for use by fsck-y tools that want to support\n  * fsck.<msg> fsck.skipList etc.\n  */\n-int git_fsck_config(const char *var, const char *value, void *cb);\n+int git_fsck_config(const char *var, const char *value,\n+\t\t    struct key_value_info *kvi, void *cb);\n \n #endif\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex aceeb083367..5ed89ef69ed 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -12,7 +12,8 @@\n #include \"alias.h\"\n #include \"wrapper.h\"\n \n-static int git_gpg_config(const char *, const char *, void *);\n+static int git_gpg_config(const char *, const char *, struct key_value_info *,\n+\t\t\t  void *);\n \n static void gpg_interface_lazy_init(void)\n {\n@@ -718,7 +719,8 @@ void set_signing_key(const char *key)\n \tconfigured_signing_key = xstrdup(key);\n }\n \n-static int git_gpg_config(const char *var, const char *value, void *cb UNUSED)\n+static int git_gpg_config(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi UNUSED, void *cb UNUSED)\n {\n \tstruct gpg_format *fmt = NULL;\n \tchar *fmtname = NULL;\ndiff --git a/grep.c b/grep.c\nindex b86462a12a9..1516b0689d0 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -54,7 +54,8 @@ define_list_config_array_extra(color_grep_slots, {\"match\"});\n  * Read the configuration file once and store it in\n  * the grep_defaults template.\n  */\n-int grep_config(const char *var, const char *value, void *cb)\n+int grep_config(const char *var, const char *value,\n+\t\tstruct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct grep_opt *opt = cb;\n \tconst char *slot;\ndiff --git a/help.c b/help.c\nindex 5d7637dce92..43d1eb702cd 100644\n--- a/help.c\n+++ b/help.c\n@@ -309,7 +309,8 @@ void load_command_list(const char *prefix,\n \texclude_cmds(other_cmds, main_cmds);\n }\n \n-static int get_colopts(const char *var, const char *value, void *data)\n+static int get_colopts(const char *var, const char *value,\n+\t\t       struct key_value_info *kvi UNUSED, void *data)\n {\n \tunsigned int *colopts = data;\n \n@@ -459,7 +460,8 @@ void list_developer_interfaces_help(void)\n \tputchar('\\n');\n }\n \n-static int get_alias(const char *var, const char *value, void *data)\n+static int get_alias(const char *var, const char *value,\n+\t\t     struct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct string_list *list = data;\n \n@@ -543,6 +545,7 @@ static struct cmdnames aliases;\n #define AUTOCORRECT_IMMEDIATELY (-1)\n \n static int git_unknown_cmd_config(const char *var, const char *value,\n+\t\t\t\t  struct key_value_info *kvi UNUSED,\n \t\t\t\t  void *cb UNUSED)\n {\n \tconst char *p;\ndiff --git a/ident.c b/ident.c\nindex 8fad92d7007..21b7b3ff35b 100644\n--- a/ident.c\n+++ b/ident.c\n@@ -671,7 +671,8 @@ static int set_ident(const char *var, const char *value)\n \treturn 0;\n }\n \n-int git_ident_config(const char *var, const char *value, void *data UNUSED)\n+int git_ident_config(const char *var, const char *value,\n+\t\t     struct key_value_info *kvi UNUSED, void *data UNUSED)\n {\n \tif (!strcmp(var, \"user.useconfigonly\")) {\n \t\tident_use_config_only = git_config_bool(var, value);\ndiff --git a/ident.h b/ident.h\nindex 96a64896a01..f55db41ff99 100644\n--- a/ident.h\n+++ b/ident.h\n@@ -62,6 +62,7 @@ const char *fmt_name(enum want_ident);\n int committer_ident_sufficiently_given(void);\n int author_ident_sufficiently_given(void);\n \n-int git_ident_config(const char *, const char *, void *);\n+int git_ident_config(const char *, const char *,\n+\t\t     struct key_value_info *UNUSED, void *);\n \n #endif\ndiff --git a/imap-send.c b/imap-send.c\nindex a62424e90a4..3cc98f1a0a5 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1322,7 +1322,8 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)\n \treturn 1;\n }\n \n-static int git_imap_config(const char *var, const char *val, void *cb)\n+static int git_imap_config(const char *var, const char *val,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \n \tif (!strcmp(\"imap.sslverify\", var))\n@@ -1356,7 +1357,7 @@ static int git_imap_config(const char *var, const char *val, void *cb)\n \t\t\tserver.host = xstrdup(val);\n \t\t}\n \t} else\n-\t\treturn git_default_config(var, val, cb);\n+\t\treturn git_default_config(var, val,kvi, cb);\n \n \treturn 0;\n }\ndiff --git a/ll-merge.c b/ll-merge.c\nindex 8be38d3bd41..2d240609ddf 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -252,6 +252,7 @@ static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;\n static const char *default_ll_merge;\n \n static int read_merge_config(const char *var, const char *value,\n+\t\t\t     struct key_value_info *kvi UNUSED,\n \t\t\t     void *cb UNUSED)\n {\n \tstruct ll_merge_driver *fn;\ndiff --git a/ls-refs.c b/ls-refs.c\nindex b9f3e08ec3d..90b902c84f8 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -136,7 +136,7 @@ static void send_possibly_unborn_head(struct ls_refs_data *data)\n }\n \n static int ls_refs_config(const char *var, const char *value,\n-\t\t\t  void *cb_data)\n+\t\t\t  struct key_value_info *kvi UNUSED, void *cb_data)\n {\n \tstruct ls_refs_data *data = cb_data;\n \t/*\ndiff --git a/mailinfo.c b/mailinfo.c\nindex 2aeb20e5e62..e2f469c96f7 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -1241,12 +1241,13 @@ int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)\n \treturn 0;\n }\n \n-static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n+static int git_mailinfo_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi, void *mi_)\n {\n \tstruct mailinfo *mi = mi_;\n \n \tif (!starts_with(var, \"mailinfo.\"))\n-\t\treturn git_default_config(var, value, NULL);\n+\t\treturn git_default_config(var, value,kvi, NULL);\n \tif (!strcmp(var, \"mailinfo.scissors\")) {\n \t\tmi->use_scissors = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/notes-utils.c b/notes-utils.c\nindex cb88171b7bb..f7a0ec60731 100644\n--- a/notes-utils.c\n+++ b/notes-utils.c\n@@ -93,7 +93,8 @@ static combine_notes_fn parse_combine_notes_fn(const char *v)\n \t\treturn NULL;\n }\n \n-static int notes_rewrite_config(const char *k, const char *v, void *cb)\n+static int notes_rewrite_config(const char *k, const char *v,\n+\t\t\t\tstruct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct notes_rewrite_cfg *c = cb;\n \tif (starts_with(k, \"notes.rewrite.\") && !strcmp(k+14, c->cmd)) {\ndiff --git a/notes.c b/notes.c\nindex 45fb7f22d1d..89b84ea0869 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -973,7 +973,8 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,\n \tfree(globs_copy);\n }\n \n-static int notes_display_config(const char *k, const char *v, void *cb)\n+static int notes_display_config(const char *k, const char *v,\n+\t\t\t\tstruct key_value_info *kvi UNUSED, void *cb)\n {\n \tint *load_refs = cb;\n \ndiff --git a/pager.c b/pager.c\nindex b66bbff2785..4b77e0d2e57 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -39,6 +39,7 @@ static void wait_for_pager_signal(int signo)\n }\n \n static int core_pager_config(const char *var, const char *value,\n+\t\t\t     struct key_value_info *kvi UNUSED,\n \t\t\t     void *data UNUSED)\n {\n \tif (!strcmp(var, \"core.pager\"))\n@@ -224,7 +225,9 @@ struct pager_command_config_data {\n \tchar *value;\n };\n \n-static int pager_command_config(const char *var, const char *value, void *vdata)\n+static int pager_command_config(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi UNUSED,\n+\t\t\t\tvoid *vdata)\n {\n \tstruct pager_command_config_data *data = vdata;\n \tconst char *cmd;\ndiff --git a/pretty.c b/pretty.c\nindex 76fc4f61e40..7f57379c44e 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -55,6 +55,7 @@ static void save_user_format(struct rev_info *rev, const char *cp, int is_tforma\n }\n \n static int git_pretty_formats_config(const char *var, const char *value,\n+\t\t\t\t     struct key_value_info *kvi UNUSED,\n \t\t\t\t     void *cb UNUSED)\n {\n \tstruct cmt_fmt_map *commit_format = NULL;\ndiff --git a/promisor-remote.c b/promisor-remote.c\nindex a8dbb788e8f..ea68df91209 100644\n--- a/promisor-remote.c\n+++ b/promisor-remote.c\n@@ -99,7 +99,9 @@ static void promisor_remote_move_to_tail(struct promisor_remote_config *config,\n \tconfig->promisors_tail = &r->next;\n }\n \n-static int promisor_remote_config(const char *var, const char *value, void *data)\n+static int promisor_remote_config(const char *var, const char *value,\n+\t\t\t\t  struct key_value_info *kvi UNUSED,\n+\t\t\t\t  void *data)\n {\n \tstruct promisor_remote_config *config = data;\n \tconst char *name;\ndiff --git a/remote.c b/remote.c\nindex 3a831cb5304..10868a963f2 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -348,7 +348,8 @@ static void read_branches_file(struct remote_state *remote_state,\n \tremote->fetch_tags = 1; /* always auto-follow */\n }\n \n-static int handle_config(const char *key, const char *value, void *cb)\n+static int handle_config(const char *key, const char *value,\n+\t\t\t struct key_value_info *kvi UNUSED, void *cb)\n {\n \tconst char *name;\n \tsize_t namelen;\ndiff --git a/revision.c b/revision.c\nindex 106ca1ce6c9..6de0132d719 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1569,7 +1569,8 @@ struct exclude_hidden_refs_cb {\n \tconst char *section;\n };\n \n-static int hide_refs_config(const char *var, const char *value, void *cb_data)\n+static int hide_refs_config(const char *var, const char *value,\n+\t\t\t    struct key_value_info *kvi UNUSED, void *cb_data)\n {\n \tstruct exclude_hidden_refs_cb *cb = cb_data;\n \tcb->exclusions->hidden_refs_configured = 1;\ndiff --git a/scalar.c b/scalar.c\nindex de07c37d210..1c44df2ec7a 100644\n--- a/scalar.c\n+++ b/scalar.c\n@@ -593,7 +593,8 @@ static int cmd_register(int argc, const char **argv)\n \treturn register_dir();\n }\n \n-static int get_scalar_repos(const char *key, const char *value, void *data)\n+static int get_scalar_repos(const char *key, const char *value,\n+\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct string_list *list = data;\n \ndiff --git a/sequencer.c b/sequencer.c\nindex 6985ca526ae..171561c2cdb 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -214,7 +214,8 @@ static struct update_ref_record *init_update_ref_record(const char *ref)\n \treturn rec;\n }\n \n-static int git_sequencer_config(const char *k, const char *v, void *cb)\n+static int git_sequencer_config(const char *k, const char *v,\n+\t\t\t\tstruct key_value_info *kvi, void *cb)\n {\n \tstruct replay_opts *opts = cb;\n \tint status;\n@@ -269,7 +270,7 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)\n \tif (opts->action == REPLAY_REVERT && !strcmp(k, \"revert.reference\"))\n \t\topts->commit_use_reference = git_config_bool(k, v);\n \n-\treturn git_diff_basic_config(k, v, NULL);\n+\treturn git_diff_basic_config(k, v,kvi, NULL);\n }\n \n void sequencer_init_config(struct replay_opts *opts)\n@@ -2876,7 +2877,8 @@ static int git_config_string_dup(char **dest,\n \treturn 0;\n }\n \n-static int populate_opts_cb(const char *key, const char *value, void *data)\n+static int populate_opts_cb(const char *key, const char *value,\n+\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct replay_opts *opts = data;\n \tint error_flag = 1;\ndiff --git a/setup.c b/setup.c\nindex 6c5b85e96c1..a461dd15233 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -514,7 +514,9 @@ no_prevention_needed:\n \tstartup_info->original_cwd = NULL;\n }\n \n-static int read_worktree_config(const char *var, const char *value, void *vdata)\n+static int read_worktree_config(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi UNUSED,\n+\t\t\t\tvoid *vdata)\n {\n \tstruct repository_format *data = vdata;\n \n@@ -585,7 +587,8 @@ static enum extension_result handle_extension(const char *var,\n \treturn EXTENSION_UNKNOWN;\n }\n \n-static int check_repo_format(const char *var, const char *value, void *vdata)\n+static int check_repo_format(const char *var, const char *value,\n+\t\t\t     struct key_value_info *kvi, void *vdata)\n {\n \tstruct repository_format *data = vdata;\n \tconst char *ext;\n@@ -614,7 +617,7 @@ static int check_repo_format(const char *var, const char *value, void *vdata)\n \t\t}\n \t}\n \n-\treturn read_worktree_config(var, value, vdata);\n+\treturn read_worktree_config(var, value,kvi, vdata);\n }\n \n static int check_repository_format_gently(const char *gitdir, struct repository_format *candidate, int *nongit_ok)\n@@ -1113,7 +1116,8 @@ struct safe_directory_data {\n \tint is_safe;\n };\n \n-static int safe_directory_cb(const char *key, const char *value, void *d)\n+static int safe_directory_cb(const char *key, const char *value,\n+\t\t\t     struct key_value_info *kvi UNUSED, void *d)\n {\n \tstruct safe_directory_data *data = d;\n \n@@ -1169,7 +1173,8 @@ static int ensure_valid_ownership(const char *gitfile,\n \treturn data.is_safe;\n }\n \n-static int allowed_bare_repo_cb(const char *key, const char *value, void *d)\n+static int allowed_bare_repo_cb(const char *key, const char *value,\n+\t\t\t\tstruct key_value_info *kvi UNUSED, void *d)\n {\n \tenum allowed_bare_repo *allowed_bare_repo = d;\n \ndiff --git a/submodule-config.c b/submodule-config.c\nindex c2f71f0b2e3..522c9cd3213 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -424,7 +424,8 @@ struct parse_config_parameter {\n  * config store (.git/config, etc).  Callers are responsible for\n  * checking for overrides in the main config store when appropriate.\n  */\n-static int parse_config(const char *var, const char *value, void *data)\n+static int parse_config(const char *var, const char *value,\n+\t\t\tstruct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct parse_config_parameter *me = data;\n \tstruct submodule *submodule;\n@@ -673,7 +674,8 @@ out:\n \t}\n }\n \n-static int gitmodules_cb(const char *var, const char *value, void *data)\n+static int gitmodules_cb(const char *var, const char *value,\n+\t\t\t struct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct repository *repo = data;\n \tstruct parse_config_parameter parameter;\n@@ -801,7 +803,9 @@ void submodule_free(struct repository *r)\n \t\tsubmodule_cache_clear(r->submodule_cache);\n }\n \n-static int config_print_callback(const char *var, const char *value, void *cb_data)\n+static int config_print_callback(const char *var, const char *value,\n+\t\t\t\t struct key_value_info *kvi UNUSED,\n+\t\t\t\t void *cb_data)\n {\n \tchar *wanted_key = cb_data;\n \n@@ -843,7 +847,9 @@ struct fetch_config {\n \tint *recurse_submodules;\n };\n \n-static int gitmodules_fetch_config(const char *var, const char *value, void *cb)\n+static int gitmodules_fetch_config(const char *var, const char *value,\n+\t\t\t\t   struct key_value_info *kvi UNUSED,\n+\t\t\t\t   void *cb)\n {\n \tstruct fetch_config *config = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\")) {\n@@ -871,6 +877,7 @@ void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)\n }\n \n static int gitmodules_update_clone_config(const char *var, const char *value,\n+\t\t\t\t\t  struct key_value_info *kvi UNUSED,\n \t\t\t\t\t  void *cb)\n {\n \tint *max_jobs = cb;\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex ad78fc17683..00cd49e5145 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -42,7 +42,8 @@\n  *\n  */\n \n-static int iterate_cb(const char *var, const char *value, void *data UNUSED)\n+static int iterate_cb(const char *var, const char *value,\n+\t\t      struct key_value_info *kvi UNUSED, void *data UNUSED)\n {\n \tstatic int nr;\n \n@@ -59,7 +60,8 @@ static int iterate_cb(const char *var, const char *value, void *data UNUSED)\n \treturn 0;\n }\n \n-static int parse_int_cb(const char *var, const char *value, void *data)\n+static int parse_int_cb(const char *var, const char *value,\n+\t\t\tstruct key_value_info *kvi UNUSED, void *data)\n {\n \tconst char *key_to_match = data;\n \n@@ -70,7 +72,8 @@ static int parse_int_cb(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n-static int early_config_cb(const char *var, const char *value, void *vdata)\n+static int early_config_cb(const char *var, const char *value,\n+\t\t\t   struct key_value_info *kvi UNUSED, void *vdata)\n {\n \tconst char *key = vdata;\n \ndiff --git a/t/helper/test-userdiff.c b/t/helper/test-userdiff.c\nindex 680124a6760..33dd3a65241 100644\n--- a/t/helper/test-userdiff.c\n+++ b/t/helper/test-userdiff.c\n@@ -12,7 +12,9 @@ static int driver_cb(struct userdiff_driver *driver,\n \treturn 0;\n }\n \n-static int cmd__userdiff_config(const char *var, const char *value, void *cb UNUSED)\n+static int cmd__userdiff_config(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi UNUSED,\n+\t\t\t\tvoid *cb UNUSED)\n {\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 78cfc15d52d..6871258d468 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -99,7 +99,8 @@ struct tr2_cfg_data {\n /*\n  * See if the given config key matches any of our patterns of interest.\n  */\n-static int tr2_cfg_cb(const char *key, const char *value, void *d)\n+static int tr2_cfg_cb(const char *key, const char *value,\n+\t\t      struct key_value_info *kvi UNUSED, void *d)\n {\n \tstruct strbuf **s;\n \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\ndiff --git a/trace2/tr2_sysenv.c b/trace2/tr2_sysenv.c\nindex 069786cb927..d17a08b0f50 100644\n--- a/trace2/tr2_sysenv.c\n+++ b/trace2/tr2_sysenv.c\n@@ -57,7 +57,8 @@ static struct tr2_sysenv_entry tr2_sysenv_settings[] = {\n };\n /* clang-format on */\n \n-static int tr2_sysenv_cb(const char *key, const char *value, void *d)\n+static int tr2_sysenv_cb(const char *key, const char *value,\n+\t\t\t struct key_value_info *kvi UNUSED, void *d)\n {\n \tint k;\n \ndiff --git a/trailer.c b/trailer.c\nindex a2c3ed6f28c..fac7e2cfe6e 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -482,6 +482,7 @@ static struct {\n };\n \n static int git_trailer_default_config(const char *conf_key, const char *value,\n+\t\t\t\t      struct key_value_info *kvi UNUSED,\n \t\t\t\t      void *cb UNUSED)\n {\n \tconst char *trailer_item, *variable_name;\n@@ -514,6 +515,7 @@ static int git_trailer_default_config(const char *conf_key, const char *value,\n }\n \n static int git_trailer_config(const char *conf_key, const char *value,\n+\t\t\t      struct key_value_info *kvi UNUSED,\n \t\t\t      void *cb UNUSED)\n {\n \tconst char *trailer_item, *variable_name;\ndiff --git a/upload-pack.c b/upload-pack.c\nindex e23f16dfdd2..5f8232ff078 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1298,7 +1298,9 @@ static int parse_object_filter_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-static int upload_pack_config(const char *var, const char *value, void *cb_data)\n+static int upload_pack_config(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi UNUSED,\n+\t\t\t      void *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n \n@@ -1339,7 +1341,9 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data)\n \treturn parse_hide_refs_config(var, value, \"uploadpack\", &data->hidden_refs);\n }\n \n-static int upload_pack_protected_config(const char *var, const char *value, void *cb_data)\n+static int upload_pack_protected_config(const char *var, const char *value,\n+\t\t\t\t\tstruct key_value_info *kvi UNUSED,\n+\t\t\t\t\tvoid *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n \ndiff --git a/urlmatch.c b/urlmatch.c\nindex eba0bdd77fe..47683974d8c 100644\n--- a/urlmatch.c\n+++ b/urlmatch.c\n@@ -551,7 +551,8 @@ static int cmp_matches(const struct urlmatch_item *a,\n \treturn 0;\n }\n \n-int urlmatch_config_entry(const char *var, const char *value, void *cb)\n+int urlmatch_config_entry(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct string_list_item *item;\n \tstruct urlmatch_config *collect = cb;\ndiff --git a/urlmatch.h b/urlmatch.h\nindex bee374a642c..f6eac4af9ea 100644\n--- a/urlmatch.h\n+++ b/urlmatch.h\n@@ -71,7 +71,8 @@ struct urlmatch_config {\n \t.vars = STRING_LIST_INIT_DUP, \\\n }\n \n-int urlmatch_config_entry(const char *var, const char *value, void *cb);\n+int urlmatch_config_entry(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi, void *cb);\n void urlmatch_config_release(struct urlmatch_config *config);\n \n #endif /* URL_MATCH_H */\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex 0460e03f5ed..f1aac104285 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -307,7 +307,8 @@ int xdiff_compare_lines(const char *l1, long s1,\n \n int git_xmerge_style = -1;\n \n-int git_xmerge_config(const char *var, const char *value, void *cb)\n+int git_xmerge_config(const char *var, const char *value,\n+\t\t      struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"merge.conflictstyle\")) {\n \t\tif (!value)\n@@ -327,5 +328,5 @@ int git_xmerge_config(const char *var, const char *value, void *cb)\n \t\t\t    value, var);\n \t\treturn 0;\n \t}\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex 3750794afe9..c1676b11702 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -50,7 +50,8 @@ int buffer_is_binary(const char *ptr, unsigned long size);\n \n void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n-int git_xmerge_config(const char *var, const char *value, void *cb);\n+int git_xmerge_config(const char *var, const char *value,\n+\t\t      struct key_value_info *kvi, void *cb);\n extern int git_xmerge_style;\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"475813","messageId":"11a32c86203648700cb43cd1ea1c664fd9b21c88.1682104399.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH 13/14] config: pass kvi to die_bad_number()","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-04-21T19:13:17Z","receivedAt":"2023-04-21T19:14:25Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPlumb \"struct key_value_info\" through all code paths that end in\ndie_bad_number(), which lets us remove the helper functions that read\nanalogous values from \"struct config_reader\".\n\nIn config.c, this requires changing the signature of\ngit_configset_get_value() to 'return' \"kvi\" in an out parameter so that\ngit_configset_get_<type>() can pass it to git_config_<type>().\n\nOutside of config.c, config callbacks now need to pass \"kvi\" to any of\nthe git_config_<type>() functions that parse a config string into a\nnumber type. Included is a .cocci patch to make that refactor. In cases\nwhere \"kvi\" would never be used, pass NULL, e.g.:\n\n- In config.c, when we are parsing a boolean instead of a number\n- In builtin/config.c, when calling normalize_value() before setting\n  config to something the user gave us.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n archive-tar.c                              |   4 +-\n builtin/commit-graph.c                     |   4 +-\n builtin/commit.c                           |  10 +-\n builtin/config.c                           |  20 ++--\n builtin/fetch.c                            |   4 +-\n builtin/fsmonitor--daemon.c                |   6 +-\n builtin/grep.c                             |   2 +-\n builtin/index-pack.c                       |   4 +-\n builtin/log.c                              |   2 +-\n builtin/pack-objects.c                     |  14 +--\n builtin/receive-pack.c                     |  10 +-\n builtin/submodule--helper.c                |   4 +-\n config.c                                   | 128 +++++++++------------\n config.h                                   |  14 ++-\n contrib/coccinelle/git_config_number.cocci |  27 +++++\n diff.c                                     |   9 +-\n fmt-merge-msg.c                            |   2 +-\n help.c                                     |   5 +-\n http.c                                     |  10 +-\n imap-send.c                                |   2 +-\n sequencer.c                                |  22 ++--\n setup.c                                    |   2 +-\n submodule-config.c                         |  15 ++-\n submodule-config.h                         |   3 +-\n t/helper/test-config.c                     |   6 +-\n upload-pack.c                              |  12 +-\n worktree.c                                 |   2 +-\n 27 files changed, 182 insertions(+), 161 deletions(-)\n create mode 100644 contrib/coccinelle/git_config_number.cocci\n\ndiff --git a/archive-tar.c b/archive-tar.c\nindex dcfbce5225a..1cd6d72d21e 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -411,14 +411,14 @@ static int tar_filter_config(const char *var, const char *value,\n }\n \n static int git_tar_config(const char *var, const char *value,\n-\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n+\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"tar.umask\")) {\n \t\tif (value && !strcmp(value, \"user\")) {\n \t\t\ttar_umask = umask(0);\n \t\t\tumask(tar_umask);\n \t\t} else {\n-\t\t\ttar_umask = git_config_int(var, value);\n+\t\t\ttar_umask = git_config_int(var, value, kvi);\n \t\t}\n \t\treturn 0;\n \t}\ndiff --git a/builtin/commit-graph.c b/builtin/commit-graph.c\nindex e811866b5dd..c99804abc7e 100644\n--- a/builtin/commit-graph.c\n+++ b/builtin/commit-graph.c\n@@ -185,11 +185,11 @@ static int write_option_max_new_filters(const struct option *opt,\n }\n \n static int git_commit_graph_write_config(const char *var, const char *value,\n-\t\t\t\t\t struct key_value_info *kvi UNUSED,\n+\t\t\t\t\t struct key_value_info *kvi,\n \t\t\t\t\t void *cb UNUSED)\n {\n \tif (!strcmp(var, \"commitgraph.maxnewfilters\"))\n-\t\twrite_opts.max_new_filters = git_config_int(var, value);\n+\t\twrite_opts.max_new_filters = git_config_int(var, value, kvi);\n \t/*\n \t * No need to fall-back to 'git_default_config', since this was already\n \t * called in 'cmd_commit_graph()'.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex ec468e87039..e846355ec39 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1412,7 +1412,8 @@ static int git_status_config(const char *k, const char *v,\n \t\treturn git_column_config(k, v, \"status\", &s->colopts);\n \tif (!strcmp(k, \"status.submodulesummary\")) {\n \t\tint is_bool;\n-\t\ts->submodule_summary = git_config_bool_or_int(k, v, &is_bool);\n+\t\ts->submodule_summary = git_config_bool_or_int(k, v, kvi,\n+\t\t\t\t\t\t\t      &is_bool);\n \t\tif (is_bool && s->submodule_summary)\n \t\t\ts->submodule_summary = -1;\n \t\treturn 0;\n@@ -1472,11 +1473,11 @@ static int git_status_config(const char *k, const char *v,\n \t}\n \tif (!strcmp(k, \"diff.renamelimit\")) {\n \t\tif (s->rename_limit == -1)\n-\t\t\ts->rename_limit = git_config_int(k, v);\n+\t\t\ts->rename_limit = git_config_int(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"status.renamelimit\")) {\n-\t\ts->rename_limit = git_config_int(k, v);\n+\t\ts->rename_limit = git_config_int(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"diff.renames\")) {\n@@ -1622,7 +1623,8 @@ static int git_commit_config(const char *k, const char *v,\n \t}\n \tif (!strcmp(k, \"commit.verbose\")) {\n \t\tint is_bool;\n-\t\tconfig_commit_verbose = git_config_bool_or_int(k, v, &is_bool);\n+\t\tconfig_commit_verbose = git_config_bool_or_int(k, v, kvi,\n+\t\t\t\t\t\t\t       &is_bool);\n \t\treturn 0;\n \t}\n \ndiff --git a/builtin/config.c b/builtin/config.c\nindex 7899add68fb..fcfceb8e156 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -254,13 +254,14 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value\n \n \t\tif (type == TYPE_INT)\n \t\t\tstrbuf_addf(buf, \"%\"PRId64,\n-\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\"));\n+\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\", kvi));\n \t\telse if (type == TYPE_BOOL)\n \t\t\tstrbuf_addstr(buf, git_config_bool(key_, value_) ?\n \t\t\t\t      \"true\" : \"false\");\n \t\telse if (type == TYPE_BOOL_OR_INT) {\n \t\t\tint is_bool, v;\n-\t\t\tv = git_config_bool_or_int(key_, value_, &is_bool);\n+\t\t\tv = git_config_bool_or_int(key_, value_, kvi,\n+\t\t\t\t\t\t   &is_bool);\n \t\t\tif (is_bool)\n \t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n \t\t\telse\n@@ -411,7 +412,8 @@ free_strings:\n \treturn ret;\n }\n \n-static char *normalize_value(const char *key, const char *value)\n+static char *normalize_value(const char *key, const char *value,\n+\t\t\t     struct key_value_info *kvi)\n {\n \tif (!value)\n \t\treturn NULL;\n@@ -426,12 +428,12 @@ static char *normalize_value(const char *key, const char *value)\n \t\t */\n \t\treturn xstrdup(value);\n \tif (type == TYPE_INT)\n-\t\treturn xstrfmt(\"%\"PRId64, git_config_int64(key, value));\n+\t\treturn xstrfmt(\"%\"PRId64, git_config_int64(key, value, kvi));\n \tif (type == TYPE_BOOL)\n \t\treturn xstrdup(git_config_bool(key, value) ?  \"true\" : \"false\");\n \tif (type == TYPE_BOOL_OR_INT) {\n \t\tint is_bool, v;\n-\t\tv = git_config_bool_or_int(key, value, &is_bool);\n+\t\tv = git_config_bool_or_int(key, value, kvi, &is_bool);\n \t\tif (!is_bool)\n \t\t\treturn xstrfmt(\"%d\", v);\n \t\telse\n@@ -870,7 +872,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_SET) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 2);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], NULL);\n \t\tret = git_config_set_in_file_gently(given_config_source.file, argv[0], value);\n \t\tif (ret == CONFIG_NOTHING_SET)\n \t\t\terror(_(\"cannot overwrite multiple values with a single value\\n\"\n@@ -879,7 +881,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_SET_ALL) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 3);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], NULL);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value, argv[2],\n \t\t\t\t\t\t\t     flags);\n@@ -887,7 +889,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_ADD) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 2);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], NULL);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value,\n \t\t\t\t\t\t\t     CONFIG_REGEX_NONE,\n@@ -896,7 +898,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_REPLACE_ALL) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 3);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], NULL);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value, argv[2],\n \t\t\t\t\t\t\t     flags | CONFIG_FLAGS_MULTI_REPLACE);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex aa688291613..04cf5518d2c 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -125,7 +125,7 @@ static int git_fetch_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"submodule.fetchjobs\")) {\n-\t\tsubmodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v);\n+\t\tsubmodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v, kvi);\n \t\treturn 0;\n \t} else if (!strcmp(k, \"fetch.recursesubmodules\")) {\n \t\trecurse_submodules = parse_fetch_recurse_submodules_arg(k, v);\n@@ -133,7 +133,7 @@ static int git_fetch_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"fetch.parallel\")) {\n-\t\tfetch_parallel_config = git_config_int(k, v);\n+\t\tfetch_parallel_config = git_config_int(k, v, kvi);\n \t\tif (fetch_parallel_config < 0)\n \t\t\tdie(_(\"fetch.parallel cannot be negative\"));\n \t\tif (!fetch_parallel_config)\ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex a7375d61d02..cde4a575836 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -40,7 +40,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, FSMONITOR__IPC_THREADS)) {\n-\t\tint i = git_config_int(var, value);\n+\t\tint i = git_config_int(var, value, kvi);\n \t\tif (i < 1)\n \t\t\treturn error(_(\"value of '%s' out of range: %d\"),\n \t\t\t\t     FSMONITOR__IPC_THREADS, i);\n@@ -49,7 +49,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, FSMONITOR__START_TIMEOUT)) {\n-\t\tint i = git_config_int(var, value);\n+\t\tint i = git_config_int(var, value, kvi);\n \t\tif (i < 0)\n \t\t\treturn error(_(\"value of '%s' out of range: %d\"),\n \t\t\t\t     FSMONITOR__START_TIMEOUT, i);\n@@ -59,7 +59,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \n \tif (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) {\n \t\tint is_bool;\n-\t\tint i = git_config_bool_or_int(var, value, &is_bool);\n+\t\tint i = git_config_bool_or_int(var, value, kvi, &is_bool);\n \t\tif (i < 0)\n \t\t\treturn error(_(\"value of '%s' not bool or int: %d\"),\n \t\t\t\t     var, i);\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 6e795f9f3a2..edb57f048ef 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -298,7 +298,7 @@ static int grep_cmd_config(const char *var, const char *value,\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\n-\t\tnum_threads = git_config_int(var, value);\n+\t\tnum_threads = git_config_int(var, value, kvi);\n \t\tif (num_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d) for %s\"),\n \t\t\t    num_threads, var);\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 4450510ddfc..e7685fa9a6f 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1584,13 +1584,13 @@ static int git_index_pack_config(const char *k, const char *v,\n \tstruct pack_idx_option *opts = cb;\n \n \tif (!strcmp(k, \"pack.indexversion\")) {\n-\t\topts->version = git_config_int(k, v);\n+\t\topts->version = git_config_int(k, v, kvi);\n \t\tif (opts->version > 2)\n \t\t\tdie(_(\"bad pack.indexVersion=%\"PRIu32), opts->version);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.threads\")) {\n-\t\tnr_threads = git_config_int(k, v);\n+\t\tnr_threads = git_config_int(k, v, kvi);\n \t\tif (nr_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d)\"),\n \t\t\t    nr_threads);\ndiff --git a/builtin/log.c b/builtin/log.c\nindex f8e61330491..805320a1abf 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -569,7 +569,7 @@ static int git_log_config(const char *var, const char *value,\n \tif (!strcmp(var, \"format.subjectprefix\"))\n \t\treturn git_config_string(&fmt_patch_subject_prefix, var, value);\n \tif (!strcmp(var, \"format.filenamemaxlength\")) {\n-\t\tfmt_patch_name_max = git_config_int(var, value);\n+\t\tfmt_patch_name_max = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"format.encodeemailheaders\")) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex ca023000cc0..cde11f83f81 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3138,23 +3138,23 @@ static int git_pack_config(const char *k, const char *v,\n \t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(k, \"pack.window\")) {\n-\t\twindow = git_config_int(k, v);\n+\t\twindow = git_config_int(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.windowmemory\")) {\n-\t\twindow_memory_limit = git_config_ulong(k, v);\n+\t\twindow_memory_limit = git_config_ulong(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.depth\")) {\n-\t\tdepth = git_config_int(k, v);\n+\t\tdepth = git_config_int(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.deltacachesize\")) {\n-\t\tmax_delta_cache_size = git_config_int(k, v);\n+\t\tmax_delta_cache_size = git_config_int(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.deltacachelimit\")) {\n-\t\tcache_max_small_delta_size = git_config_int(k, v);\n+\t\tcache_max_small_delta_size = git_config_int(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.writebitmaphashcache\")) {\n@@ -3180,7 +3180,7 @@ static int git_pack_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.threads\")) {\n-\t\tdelta_search_threads = git_config_int(k, v);\n+\t\tdelta_search_threads = git_config_int(k, v, kvi);\n \t\tif (delta_search_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d)\"),\n \t\t\t    delta_search_threads);\n@@ -3191,7 +3191,7 @@ static int git_pack_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.indexversion\")) {\n-\t\tpack_idx_opts.version = git_config_int(k, v);\n+\t\tpack_idx_opts.version = git_config_int(k, v, kvi);\n \t\tif (pack_idx_opts.version > 2)\n \t\t\tdie(_(\"bad pack.indexVersion=%\"PRIu32),\n \t\t\t    pack_idx_opts.version);\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2f5fd2abbc3..d2bc0fead9f 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -154,12 +154,12 @@ static int receive_pack_config(const char *var, const char *value,\n \t}\n \n \tif (strcmp(var, \"receive.unpacklimit\") == 0) {\n-\t\treceive_unpack_limit = git_config_int(var, value);\n+\t\treceive_unpack_limit = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \n \tif (strcmp(var, \"transfer.unpacklimit\") == 0) {\n-\t\ttransfer_unpack_limit = git_config_int(var, value);\n+\t\ttransfer_unpack_limit = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \n@@ -227,7 +227,7 @@ static int receive_pack_config(const char *var, const char *value,\n \t\treturn git_config_string(&cert_nonce_seed, var, value);\n \n \tif (strcmp(var, \"receive.certnonceslop\") == 0) {\n-\t\tnonce_stamp_slop_limit = git_config_ulong(var, value);\n+\t\tnonce_stamp_slop_limit = git_config_ulong(var, value, kvi);\n \t\treturn 0;\n \t}\n \n@@ -242,12 +242,12 @@ static int receive_pack_config(const char *var, const char *value,\n \t}\n \n \tif (strcmp(var, \"receive.keepalive\") == 0) {\n-\t\tkeepalive_in_sec = git_config_int(var, value);\n+\t\tkeepalive_in_sec = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \n \tif (strcmp(var, \"receive.maxinputsize\") == 0) {\n-\t\tmax_input_size = git_config_int64(var, value);\n+\t\tmax_input_size = git_config_int64(var, value, kvi);\n \t\treturn 0;\n \t}\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 8570effbf0d..bda10764db5 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2187,13 +2187,13 @@ static int update_clone_task_finished(int result,\n }\n \n static int git_update_clone_config(const char *var, const char *value,\n-\t\t\t\t   struct key_value_info *kvi UNUSED,\n+\t\t\t\t   struct key_value_info *kvi,\n \t\t\t\t   void *cb)\n {\n \tint *max_jobs = cb;\n \n \tif (!strcmp(var, \"submodule.fetchjobs\"))\n-\t\t*max_jobs = parse_submodule_fetchjobs(var, value);\n+\t\t*max_jobs = parse_submodule_fetchjobs(var, value, kvi);\n \treturn 0;\n }\n \ndiff --git a/config.c b/config.c\nindex 68c9b507a4d..e78de67c452 100644\n--- a/config.c\n+++ b/config.c\n@@ -1309,80 +1309,74 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)\n \treturn 1;\n }\n \n-static int reader_config_name(struct config_reader *reader, const char **out);\n-static int reader_origin_type(struct config_reader *reader,\n-\t\t\t      enum config_origin_type *type);\n NORETURN\n-static void die_bad_number(struct config_reader *reader, const char *name,\n-\t\t\t   const char *value)\n+static void die_bad_number(const char *name, const char *value,\n+\t\t\t   struct key_value_info *kvi)\n {\n \tconst char *error_type = (errno == ERANGE) ?\n \t\tN_(\"out of range\") : N_(\"invalid unit\");\n \tconst char *bad_numeric = N_(\"bad numeric config value '%s' for '%s': %s\");\n-\tconst char *config_name = NULL;\n-\tenum config_origin_type config_origin = CONFIG_ORIGIN_UNKNOWN;\n \n \tif (!value)\n \t\tvalue = \"\";\n \n-\t/* Ignoring the return value is okay since we handle missing values. */\n-\treader_config_name(reader, &config_name);\n-\treader_origin_type(reader, &config_origin);\n-\n-\tif (!config_name)\n+\tif (!kvi || !kvi->filename)\n \t\tdie(_(bad_numeric), value, name, _(error_type));\n \n-\tswitch (config_origin) {\n+\tswitch (kvi->origin_type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in blob %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_FILE:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in file %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_STDIN:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in standard input: %s\"),\n \t\t    value, name, _(error_type));\n \tcase CONFIG_ORIGIN_SUBMODULE_BLOB:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in submodule-blob %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_CMDLINE:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in command line %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tdefault:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \t}\n }\n \n-int git_config_int(const char *name, const char *value)\n+int git_config_int(const char *name, const char *value,\n+\t\t   struct key_value_info *kvi)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-int64_t git_config_int64(const char *name, const char *value)\n+int64_t git_config_int64(const char *name, const char *value, struct key_value_info *kvi)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-unsigned long git_config_ulong(const char *name, const char *value)\n+unsigned long git_config_ulong(const char *name, const char *value,\n+\t\t\t       struct key_value_info *kvi)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-ssize_t git_config_ssize_t(const char *name, const char *value)\n+ssize_t git_config_ssize_t(const char *name, const char *value,\n+\t\t\t   struct key_value_info *kvi)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n@@ -1487,7 +1481,8 @@ int git_parse_maybe_bool(const char *value)\n \treturn -1;\n }\n \n-int git_config_bool_or_int(const char *name, const char *value, int *is_bool)\n+int git_config_bool_or_int(const char *name, const char *value,\n+\t\t\t   struct key_value_info *kvi, int *is_bool)\n {\n \tint v = git_parse_maybe_bool_text(value);\n \tif (0 <= v) {\n@@ -1495,7 +1490,7 @@ int git_config_bool_or_int(const char *name, const char *value, int *is_bool)\n \t\treturn v;\n \t}\n \t*is_bool = 0;\n-\treturn git_config_int(name, value);\n+\treturn git_config_int(name, value, kvi);\n }\n \n int git_config_bool(const char *name, const char *value)\n@@ -1621,7 +1616,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t\telse if (!git_parse_maybe_bool_text(value))\n \t\t\tdefault_abbrev = the_hash_algo->hexsz;\n \t\telse {\n-\t\t\tint abbrev = git_config_int(var, value);\n+\t\t\tint abbrev = git_config_int(var, value, kvi);\n \t\t\tif (abbrev < minimum_abbrev || abbrev > the_hash_algo->hexsz)\n \t\t\t\treturn error(_(\"abbrev length out of range: %d\"), abbrev);\n \t\t\tdefault_abbrev = abbrev;\n@@ -1633,7 +1628,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t\treturn set_disambiguate_hint_config(var, value);\n \n \tif (!strcmp(var, \"core.loosecompression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -1644,7 +1639,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.compression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -1658,7 +1653,7 @@ static int git_default_core_config(const char *var, const char *value,\n \n \tif (!strcmp(var, \"core.packedgitwindowsize\")) {\n \t\tint pgsz_x2 = getpagesize() * 2;\n-\t\tpacked_git_window_size = git_config_ulong(var, value);\n+\t\tpacked_git_window_size = git_config_ulong(var, value, kvi);\n \n \t\t/* This value must be multiple of (pagesize * 2) */\n \t\tpacked_git_window_size /= pgsz_x2;\n@@ -1669,17 +1664,17 @@ static int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.bigfilethreshold\")) {\n-\t\tbig_file_threshold = git_config_ulong(var, value);\n+\t\tbig_file_threshold = git_config_ulong(var, value, kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"core.packedgitlimit\")) {\n-\t\tpacked_git_limit = git_config_ulong(var, value);\n+\t\tpacked_git_limit = git_config_ulong(var, value, kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"core.deltabasecachelimit\")) {\n-\t\tdelta_base_cache_limit = git_config_ulong(var, value);\n+\t\tdelta_base_cache_limit = git_config_ulong(var, value, kvi);\n \t\treturn 0;\n \t}\n \n@@ -1963,12 +1958,12 @@ int git_default_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"pack.packsizelimit\")) {\n-\t\tpack_size_limit_cfg = git_config_ulong(var, value);\n+\t\tpack_size_limit_cfg = git_config_ulong(var, value, kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"pack.compression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -2474,11 +2469,12 @@ int git_configset_add_file(struct config_set *set, const char *filename)\n \treturn git_config_from_file(config_set_callback, filename, &data);\n }\n \n-int git_configset_get_value(struct config_set *set, const char *key, const char **value)\n+int git_configset_get_value(struct config_set *set, const char *key,\n+\t\t\t    const char **value, struct key_value_info *kvi)\n {\n \tconst struct string_list *values = NULL;\n \tint ret;\n-\n+\tstruct string_list_item item;\n \t/*\n \t * Follows \"last one wins\" semantic, i.e., if there are multiple matches for the\n \t * queried key in the files of the configset, the value returned will be the last\n@@ -2488,7 +2484,10 @@ int git_configset_get_value(struct config_set *set, const char *key, const char\n \t\treturn ret;\n \n \tassert(values->nr > 0);\n-\t*value = values->items[values->nr - 1].string;\n+\titem = values->items[values->nr - 1];\n+\t*value = item.string;\n+\tif (kvi)\n+\t\t*kvi = *((struct key_value_info *)item.util);\n \treturn 0;\n }\n \n@@ -2541,7 +2540,7 @@ int git_configset_get(struct config_set *set, const char *key)\n int git_configset_get_string(struct config_set *set, const char *key, char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value))\n+\tif (!git_configset_get_value(set, key, &value, NULL))\n \t\treturn git_config_string((const char **)dest, key, value);\n \telse\n \t\treturn 1;\n@@ -2551,7 +2550,7 @@ static int git_configset_get_string_tmp(struct config_set *set, const char *key,\n \t\t\t\t\tconst char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(key);\n \t\t*dest = value;\n@@ -2564,8 +2563,10 @@ static int git_configset_get_string_tmp(struct config_set *set, const char *key,\n int git_configset_get_int(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_int(key, value);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_int(key, value, &kvi);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2574,8 +2575,10 @@ int git_configset_get_int(struct config_set *set, const char *key, int *dest)\n int git_configset_get_ulong(struct config_set *set, const char *key, unsigned long *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_ulong(key, value);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_ulong(key, value, &kvi);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2584,7 +2587,7 @@ int git_configset_get_ulong(struct config_set *set, const char *key, unsigned lo\n int git_configset_get_bool(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\t*dest = git_config_bool(key, value);\n \t\treturn 0;\n \t} else\n@@ -2595,8 +2598,10 @@ int git_configset_get_bool_or_int(struct config_set *set, const char *key,\n \t\t\t\tint *is_bool, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_bool_or_int(key, value, is_bool);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_bool_or_int(key, value, &kvi, is_bool);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2605,7 +2610,7 @@ int git_configset_get_bool_or_int(struct config_set *set, const char *key,\n int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\t*dest = git_parse_maybe_bool(value);\n \t\tif (*dest == -1)\n \t\t\treturn -1;\n@@ -2617,7 +2622,7 @@ int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *d\n int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value))\n+\tif (!git_configset_get_value(set, key, &value, NULL))\n \t\treturn git_config_pathname(dest, key, value);\n \telse\n \t\treturn 1;\n@@ -2687,7 +2692,7 @@ int repo_config_get_value(struct repository *repo,\n \t\t\t  const char *key, const char **value)\n {\n \tgit_config_check_init(repo);\n-\treturn git_configset_get_value(repo->config, key, value);\n+\treturn git_configset_get_value(repo->config, key, value, NULL);\n }\n \n int repo_config_get_value_multi(struct repository *repo, const char *key,\n@@ -3907,16 +3912,6 @@ int parse_config_key(const char *var,\n \treturn 0;\n }\n \n-static int reader_origin_type(struct config_reader *reader,\n-\t\t\t      enum config_origin_type *type)\n-{\n-\tif (the_reader.config_kvi)\n-\t\t*type = reader->config_kvi->origin_type;\n-\telse\n-\t\treturn 1;\n-\treturn 0;\n-}\n-\n const char *config_origin_type_name(enum config_origin_type type)\n {\n \tswitch (type) {\n@@ -3955,15 +3950,6 @@ const char *config_scope_name(enum config_scope scope)\n \t}\n }\n \n-static int reader_config_name(struct config_reader *reader, const char **out)\n-{\n-\tif (the_reader.config_kvi)\n-\t\t*out = reader->config_kvi->filename;\n-\telse\n-\t\treturn 1;\n-\treturn 0;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex f3b1a8c38a6..ffb2d76823c 100644\n--- a/config.h\n+++ b/config.h\n@@ -237,22 +237,23 @@ int git_parse_maybe_bool(const char *);\n  * Parse the string to an integer, including unit factors. Dies on error;\n  * otherwise, returns the parsed result.\n  */\n-int git_config_int(const char *, const char *);\n+int git_config_int(const char *, const char *, struct key_value_info *);\n \n-int64_t git_config_int64(const char *, const char *);\n+int64_t git_config_int64(const char *, const char *, struct key_value_info *);\n \n /**\n  * Identical to `git_config_int`, but for unsigned longs.\n  */\n-unsigned long git_config_ulong(const char *, const char *);\n+unsigned long git_config_ulong(const char *, const char *, struct key_value_info *);\n \n-ssize_t git_config_ssize_t(const char *, const char *);\n+ssize_t git_config_ssize_t(const char *, const char *, struct key_value_info *);\n \n /**\n  * Same as `git_config_bool`, except that integers are returned as-is, and\n  * an `is_bool` flag is unset.\n  */\n-int git_config_bool_or_int(const char *, const char *, int *);\n+int git_config_bool_or_int(const char *, const char *, struct key_value_info *,\n+\t\t\t   int *);\n \n /**\n  * Parse a string into a boolean value, respecting keywords like \"true\" and\n@@ -516,7 +517,8 @@ int git_configset_get(struct config_set *cs, const char *key);\n  * touching `value`. The caller should not free or modify `value`, as it\n  * is owned by the cache.\n  */\n-int git_configset_get_value(struct config_set *cs, const char *key, const char **dest);\n+int git_configset_get_value(struct config_set *cs, const char *key,\n+\t\t\t    const char **dest, struct key_value_info *kvi);\n \n int git_configset_get_string(struct config_set *cs, const char *key, char **dest);\n int git_configset_get_int(struct config_set *cs, const char *key, int *dest);\ndiff --git a/contrib/coccinelle/git_config_number.cocci b/contrib/coccinelle/git_config_number.cocci\nnew file mode 100644\nindex 00000000000..f46c74dd23c\n--- /dev/null\n+++ b/contrib/coccinelle/git_config_number.cocci\n@@ -0,0 +1,27 @@\n+@@\n+identifier C1, C2, C3;\n+@@\n+(\n+(\n+git_config_int\n+|\n+git_config_int64\n+|\n+git_config_ulong\n+|\n+git_config_ssize_t\n+)\n+  (C1, C2\n++ , kvi\n+  )\n+|\n+(\n+git_configset_get_value\n+|\n+git_config_bool_or_int\n+)\n+  (C1, C2,\n++ kvi,\n+  C3\n+  )\n+)\ndiff --git a/diff.c b/diff.c\nindex d7ed2dc900b..da7cd353a6d 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -372,13 +372,14 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.context\")) {\n-\t\tdiff_context_default = git_config_int(var, value);\n+\t\tdiff_context_default = git_config_int(var, value, kvi);\n \t\tif (diff_context_default < 0)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.interhunkcontext\")) {\n-\t\tdiff_interhunk_context_default = git_config_int(var, value);\n+\t\tdiff_interhunk_context_default = git_config_int(var, value,\n+\t\t\t\t\t\t\t\tkvi);\n \t\tif (diff_interhunk_context_default < 0)\n \t\t\treturn -1;\n \t\treturn 0;\n@@ -404,7 +405,7 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.statgraphwidth\")) {\n-\t\tdiff_stat_graph_width = git_config_int(var, value);\n+\t\tdiff_stat_graph_width = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.external\"))\n@@ -443,7 +444,7 @@ int git_diff_basic_config(const char *var, const char *value,\n \tconst char *name;\n \n \tif (!strcmp(var, \"diff.renamelimit\")) {\n-\t\tdiff_rename_limit_default = git_config_int(var, value);\n+\t\tdiff_rename_limit_default = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 97358034fa0..d1b59af44bb 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -24,7 +24,7 @@ int fmt_merge_msg_config(const char *key, const char *value,\n {\n \tif (!strcmp(key, \"merge.log\") || !strcmp(key, \"merge.summary\")) {\n \t\tint is_bool;\n-\t\tmerge_log_config = git_config_bool_or_int(key, value, &is_bool);\n+\t\tmerge_log_config = git_config_bool_or_int(key, value, kvi, &is_bool);\n \t\tif (!is_bool && merge_log_config < 0)\n \t\t\treturn error(\"%s: negative length %s\", key, value);\n \t\tif (is_bool && merge_log_config)\ndiff --git a/help.c b/help.c\nindex 43d1eb702cd..08f0b953736 100644\n--- a/help.c\n+++ b/help.c\n@@ -545,8 +545,7 @@ static struct cmdnames aliases;\n #define AUTOCORRECT_IMMEDIATELY (-1)\n \n static int git_unknown_cmd_config(const char *var, const char *value,\n-\t\t\t\t  struct key_value_info *kvi UNUSED,\n-\t\t\t\t  void *cb UNUSED)\n+\t\t\t\t  struct key_value_info *kvi, void *cb UNUSED)\n {\n \tconst char *p;\n \n@@ -560,7 +559,7 @@ static int git_unknown_cmd_config(const char *var, const char *value,\n \t\t} else if (!strcmp(value, \"prompt\")) {\n \t\t\tautocorrect = AUTOCORRECT_PROMPT;\n \t\t} else {\n-\t\t\tint v = git_config_int(var, value);\n+\t\t\tint v = git_config_int(var, value, kvi);\n \t\t\tautocorrect = (v < 0)\n \t\t\t\t? AUTOCORRECT_IMMEDIATELY : v;\n \t\t}\ndiff --git a/http.c b/http.c\nindex 3d4292eba6a..a26c3dff827 100644\n--- a/http.c\n+++ b/http.c\n@@ -412,21 +412,21 @@ static int http_options(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(\"http.minsessions\", var)) {\n-\t\tmin_curl_sessions = git_config_int(var, value);\n+\t\tmin_curl_sessions = git_config_int(var, value, kvi);\n \t\tif (min_curl_sessions > 1)\n \t\t\tmin_curl_sessions = 1;\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.maxrequests\", var)) {\n-\t\tmax_requests = git_config_int(var, value);\n+\t\tmax_requests = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.lowspeedlimit\", var)) {\n-\t\tcurl_low_speed_limit = (long)git_config_int(var, value);\n+\t\tcurl_low_speed_limit = (long)git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.lowspeedtime\", var)) {\n-\t\tcurl_low_speed_time = (long)git_config_int(var, value);\n+\t\tcurl_low_speed_time = (long)git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \n@@ -462,7 +462,7 @@ static int http_options(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(\"http.postbuffer\", var)) {\n-\t\thttp_post_buffer = git_config_ssize_t(var, value);\n+\t\thttp_post_buffer = git_config_ssize_t(var, value, kvi);\n \t\tif (http_post_buffer < 0)\n \t\t\twarning(_(\"negative value for http.postBuffer; defaulting to %d\"), LARGE_PACKET_MAX);\n \t\tif (http_post_buffer < LARGE_PACKET_MAX)\ndiff --git a/imap-send.c b/imap-send.c\nindex 3cc98f1a0a5..3c391a52c5a 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1341,7 +1341,7 @@ static int git_imap_config(const char *var, const char *val,\n \telse if (!strcmp(\"imap.authmethod\", var))\n \t\treturn git_config_string(&server.auth_method, var, val);\n \telse if (!strcmp(\"imap.port\", var))\n-\t\tserver.port = git_config_int(var, val);\n+\t\tserver.port = git_config_int(var, val, kvi);\n \telse if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\tgit_die_config(\"imap.host\", \"Missing value for 'imap.host'\");\ndiff --git a/sequencer.c b/sequencer.c\nindex 171561c2cdb..76b4750b4bd 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2878,7 +2878,7 @@ static int git_config_string_dup(char **dest,\n }\n \n static int populate_opts_cb(const char *key, const char *value,\n-\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n+\t\t\t    struct key_value_info *kvi, void *data)\n {\n \tstruct replay_opts *opts = data;\n \tint error_flag = 1;\n@@ -2886,26 +2886,26 @@ static int populate_opts_cb(const char *key, const char *value,\n \tif (!value)\n \t\terror_flag = 0;\n \telse if (!strcmp(key, \"options.no-commit\"))\n-\t\topts->no_commit = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->no_commit = git_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.edit\"))\n-\t\topts->edit = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->edit = git_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-empty\"))\n \t\topts->allow_empty =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-empty-message\"))\n \t\topts->allow_empty_message =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.keep-redundant-commits\"))\n \t\topts->keep_redundant_commits =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.signoff\"))\n-\t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->signoff = git_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.record-origin\"))\n-\t\topts->record_origin = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->record_origin = git_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-ff\"))\n-\t\topts->allow_ff = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->allow_ff = git_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.mainline\"))\n-\t\topts->mainline = git_config_int(key, value);\n+\t\topts->mainline = git_config_int(key, value, kvi);\n \telse if (!strcmp(key, \"options.strategy\"))\n \t\tgit_config_string_dup(&opts->strategy, key, value);\n \telse if (!strcmp(key, \"options.gpg-sign\"))\n@@ -2915,7 +2915,7 @@ static int populate_opts_cb(const char *key, const char *value,\n \t\topts->xopts[opts->xopts_nr++] = xstrdup(value);\n \t} else if (!strcmp(key, \"options.allow-rerere-auto\"))\n \t\topts->allow_rerere_auto =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag) ?\n+\t\t\tgit_config_bool_or_int(key, value, kvi, &error_flag) ?\n \t\t\t\tRERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;\n \telse if (!strcmp(key, \"options.default-msg-cleanup\")) {\n \t\topts->explicit_cleanup = 1;\ndiff --git a/setup.c b/setup.c\nindex a461dd15233..75934d7438f 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -594,7 +594,7 @@ static int check_repo_format(const char *var, const char *value,\n \tconst char *ext;\n \n \tif (strcmp(var, \"core.repositoryformatversion\") == 0)\n-\t\tdata->version = git_config_int(var, value);\n+\t\tdata->version = git_config_int(var, value, kvi);\n \telse if (skip_prefix(var, \"extensions.\", &ext)) {\n \t\tswitch (handle_extension_v0(var, value, ext, data)) {\n \t\tcase EXTENSION_ERROR:\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 7d773f33621..b86547fd1ee 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -302,9 +302,10 @@ static int parse_fetch_recurse(const char *opt, const char *arg,\n \t}\n }\n \n-int parse_submodule_fetchjobs(const char *var, const char *value)\n+int parse_submodule_fetchjobs(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi)\n {\n-\tint fetchjobs = git_config_int(var, value);\n+\tint fetchjobs = git_config_int(var, value, kvi);\n \tif (fetchjobs < 0)\n \t\tdie(_(\"negative values not allowed for submodule.fetchJobs\"));\n \tif (!fetchjobs)\n@@ -848,14 +849,13 @@ struct fetch_config {\n };\n \n static int gitmodules_fetch_config(const char *var, const char *value,\n-\t\t\t\t   struct key_value_info *kvi UNUSED,\n-\t\t\t\t   void *cb)\n+\t\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tstruct fetch_config *config = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\")) {\n \t\tif (config->max_children)\n \t\t\t*(config->max_children) =\n-\t\t\t\tparse_submodule_fetchjobs(var, value);\n+\t\t\t\tparse_submodule_fetchjobs(var, value, kvi);\n \t\treturn 0;\n \t} else if (!strcmp(var, \"fetch.recursesubmodules\")) {\n \t\tif (config->recurse_submodules)\n@@ -877,12 +877,11 @@ void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)\n }\n \n static int gitmodules_update_clone_config(const char *var, const char *value,\n-\t\t\t\t\t  struct key_value_info *kvi UNUSED,\n-\t\t\t\t\t  void *cb)\n+\t\t\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tint *max_jobs = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\"))\n-\t\t*max_jobs = parse_submodule_fetchjobs(var, value);\n+\t\t*max_jobs = parse_submodule_fetchjobs(var, value, kvi);\n \treturn 0;\n }\n \ndiff --git a/submodule-config.h b/submodule-config.h\nindex c2045875bbb..944cae75cc9 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -50,7 +50,8 @@ struct repository;\n \n void submodule_cache_free(struct submodule_cache *cache);\n \n-int parse_submodule_fetchjobs(const char *var, const char *value);\n+int parse_submodule_fetchjobs(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi);\n int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);\n struct option;\n int option_fetch_parse_recurse_submodules(const struct option *opt,\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 7027ffa187f..737505583d4 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -61,12 +61,12 @@ static int iterate_cb(const char *var, const char *value,\n }\n \n static int parse_int_cb(const char *var, const char *value,\n-\t\t\tstruct key_value_info *kvi UNUSED, void *data)\n+\t\t\tstruct key_value_info *kvi, void *data)\n {\n \tconst char *key_to_match = data;\n \n \tif (!strcmp(key_to_match, var)) {\n-\t\tint parsed = git_config_int(value, value);\n+\t\tint parsed = git_config_int(value, value, kvi);\n \t\tprintf(\"%d\\n\", parsed);\n \t}\n \treturn 0;\n@@ -179,7 +179,7 @@ int cmd__config(int argc, const char **argv)\n \t\t\t\tgoto exit2;\n \t\t\t}\n \t\t}\n-\t\tif (!git_configset_get_value(&cs, argv[2], &v)) {\n+\t\tif (!git_configset_get_value(&cs, argv[2], &v, NULL)) {\n \t\t\tif (!v)\n \t\t\t\tprintf(\"(NULL)\\n\");\n \t\t\telse\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 5f8232ff078..7cf776cde91 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1264,7 +1264,8 @@ static int find_symref(const char *refname,\n }\n \n static int parse_object_filter_config(const char *var, const char *value,\n-\t\t\t\t       struct upload_pack_data *data)\n+\t\t\t\t      struct key_value_info *kvi,\n+\t\t\t\t      struct upload_pack_data *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *sub, *key;\n@@ -1291,7 +1292,8 @@ static int parse_object_filter_config(const char *var, const char *value,\n \t\t}\n \t\tstring_list_insert(&data->allowed_filters, buf.buf)->util =\n \t\t\t(void *)(intptr_t)1;\n-\t\tdata->tree_filter_max_depth = git_config_ulong(var, value);\n+\t\tdata->tree_filter_max_depth = git_config_ulong(var, value,\n+\t\t\t\t\t\t\t       kvi);\n \t}\n \n \tstrbuf_release(&buf);\n@@ -1299,7 +1301,7 @@ static int parse_object_filter_config(const char *var, const char *value,\n }\n \n static int upload_pack_config(const char *var, const char *value,\n-\t\t\t      struct key_value_info *kvi UNUSED,\n+\t\t\t      struct key_value_info *kvi,\n \t\t\t      void *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n@@ -1320,7 +1322,7 @@ static int upload_pack_config(const char *var, const char *value,\n \t\telse\n \t\t\tdata->allow_uor &= ~ALLOW_ANY_SHA1;\n \t} else if (!strcmp(\"uploadpack.keepalive\", var)) {\n-\t\tdata->keepalive = git_config_int(var, value);\n+\t\tdata->keepalive = git_config_int(var, value, kvi);\n \t\tif (!data->keepalive)\n \t\t\tdata->keepalive = -1;\n \t} else if (!strcmp(\"uploadpack.allowfilter\", var)) {\n@@ -1335,7 +1337,7 @@ static int upload_pack_config(const char *var, const char *value,\n \t\tdata->advertise_sid = git_config_bool(var, value);\n \t}\n \n-\tif (parse_object_filter_config(var, value, data) < 0)\n+\tif (parse_object_filter_config(var, value, kvi, data) < 0)\n \t\treturn -1;\n \n \treturn parse_hide_refs_config(var, value, \"uploadpack\", &data->hidden_refs);\ndiff --git a/worktree.c b/worktree.c\nindex b5ee71c5ebd..1fbdbd745fb 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -835,7 +835,7 @@ int init_worktree_config(struct repository *r)\n \t * Relocate that value to avoid breaking all worktrees with this\n \t * upgrade to worktree config.\n \t */\n-\tif (!git_configset_get_value(&cs, \"core.worktree\", &core_worktree)) {\n+\tif (!git_configset_get_value(&cs, \"core.worktree\", &core_worktree, NULL)) {\n \t\tif ((res = move_config_setting(\"core.worktree\", core_worktree,\n \t\t\t\t\t       common_config_file,\n \t\t\t\t\t       main_worktree_file)))\n-- \ngitgitgadget\n\n"},{"id":"476328","messageId":"230501.86cz3kjok7.gmgdl@evledraar.gmail.com","threadId":"59629","inReplyTo":"a682612cff23449e682023303f17a8be1d73fc31.1682104398.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 02/14] config.c: use kvi for CLI config","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-05-01T11:06:33Z","receivedAt":"2023-05-01T11:09:36Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Apr 21 2023, Glen Choo via GitGitGadget wrote:\n\n> @@ -682,17 +677,30 @@ static int config_parse_pair(const char *key, const char *value,\n>  \tif (git_config_parse_key(key, &canonical_name, NULL))\n>  \t\treturn -1;\n>  \n> -\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n> +\tret = (kvi_fn(fn, canonical_name, value, kvi, data) < 0) ? -1 : 0;\n>  \tfree(canonical_name);\n>  \treturn ret;\n>  }\n\nThis is pre-existing, but I'd much prefer as we're doing more\nformalization of this interface if this were just:\n\n\tret = kvi_fn(...);\n\t...;\n\treturn ret;\n\nI.e. a look at the current code shows us that the API users of\ngit_config_parse_parameter() are already doing this coercion themselves,\ni.e. they only care about \"ret < 0\".\n\nSo let's just hand them the actual return value, rather than doing the\nneedless coercion.\n\n> @@ -2423,19 +2429,13 @@ static int configset_add_value(struct config_reader *reader,\n>  \tl_item->e = e;\n>  \tl_item->value_index = e->value_list.nr - 1;\n>  \n> -\tif (!reader->source)\n> -\t\tBUG(\"configset_add_value has no source\");\n> -\tif (reader->source->name) {\n> +\tif (reader->source && reader->source->name) {\n>  \t\tkv_info->filename = strintern(reader->source->name);\n>  \t\tkv_info->linenr = reader->source->linenr;\n>  \t\tkv_info->origin_type = reader->source->origin_type;\n> -\t} else {\n> -\t\t/* for values read from `git_config_from_parameters()` */\n> -\t\tkv_info->filename = NULL;\n> -\t\tkv_info->linenr = -1;\n> -\t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n> -\t}\n> -\tkv_info->scope = reader->parsing_scope;\n> +\t\tkv_info->scope = reader->parsing_scope;\n> +\t} else\n> +\t\tkvi_from_param(kv_info);\n\nMissing a {} here.\n"},{"id":"476329","messageId":"230501.868re8jna4.gmgdl@evledraar.gmail.com","threadId":"59629","inReplyTo":"1071e70c92892166e1ed2cf22bcd7eb49bdf3b20.1682104399.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 10/14] (RFC-only) config: finish config_fn_t refactor","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-05-01T11:19:48Z","receivedAt":"2023-05-01T11:37:14Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Apr 21 2023, Glen Choo via GitGitGadget wrote:\n\n> From: Glen Choo <chooglen@google.com>\n\nI like the general goal of this series, i.e. to get rid of the \"reader\"\ncallback, and pass stuff more explicitly.\n\nBut as I pointed out in\nhttps://lore.kernel.org/git/RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com/\nI think this part (and the preceding two commits) are really taking is\ndown the wrong path.\n\nTo demonstrate why, here's a patch-on-top of this topic as a whole where\nI renamed the \"kvi\" struct members. I've excluded config.c itself (as\nits internals aren't interesting for the purposes of this discussion):\n\t\n\tdiff --git a/builtin/config.c b/builtin/config.c\n\tindex c9e80a4b500..00cf8ffd791 100644\n\t--- a/builtin/config.c\n\t+++ b/builtin/config.c\n\t@@ -198,8 +198,8 @@ static void show_config_origin(struct key_value_info *kvi, struct strbuf *buf)\n\t \n\t-\tstrbuf_addstr(buf, config_origin_type_name(kvi->origin_type));\n\t+\tstrbuf_addstr(buf, config_origin_type_name(kvi->origin_type2));\n\t \tstrbuf_addch(buf, ':');\n\t \tif (end_nul)\n\t-\t\tstrbuf_addstr(buf, kvi->filename ? kvi->filename : \"\");\n\t+\t\tstrbuf_addstr(buf, kvi->filename2 ? kvi->filename2 : \"\");\n\t \telse\n\t-\t\tquote_c_style(kvi->filename ? kvi->filename : \"\", buf, NULL, 0);\n\t+\t\tquote_c_style(kvi->filename2 ? kvi->filename2 : \"\", buf, NULL, 0);\n\t \tstrbuf_addch(buf, term);\n\t@@ -210,3 +210,3 @@ static void show_config_scope(struct key_value_info *kvi, struct strbuf *buf)\n\t \tconst char term = end_nul ? '\\0' : '\\t';\n\t-\tconst char *scope = config_scope_name(kvi->scope);\n\t+\tconst char *scope = config_scope_name(kvi->scope2);\n\t \n\tdiff --git a/builtin/remote.c b/builtin/remote.c\n\tindex 034998a1205..81922af3f58 100644\n\t--- a/builtin/remote.c\n\t+++ b/builtin/remote.c\n\t@@ -655,6 +655,6 @@ static int config_read_push_default(const char *key, const char *value,\n\t \n\t-\tinfo->scope = kvi->scope;\n\t+\tinfo->scope = kvi->scope2;\n\t \tstrbuf_reset(&info->origin);\n\t-\tstrbuf_addstr(&info->origin, kvi->filename);\n\t-\tinfo->linenr = kvi->linenr;\n\t+\tstrbuf_addstr(&info->origin, kvi->filename2);\n\t+\tinfo->linenr = kvi->linenr2;\n\t \n\tdiff --git a/config.h b/config.h\n\tindex ffb2d76823c..d9b0470e7b7 100644\n\t--- a/config.h\n\t+++ b/config.h\n\t@@ -120,8 +120,8 @@ struct config_options {\n\t struct key_value_info {\n\t-\tconst char *filename;\n\t-\tint linenr;\n\t-\tenum config_origin_type origin_type;\n\t-\tenum config_scope scope;\n\t-\tconst char *path;\n\t-\tstruct key_value_info *prev;\n\t+\tconst char *filename2;\n\t+\tint linenr2;\n\t+\tenum config_origin_type origin_type2;\n\t+\tenum config_scope scope2;\n\t+\tconst char *path2;\n\t+\tstruct key_value_info *prev2;\n\t };\n\tdiff --git a/remote.c b/remote.c\n\tindex 5239dfeab55..1cb465c6c17 100644\n\t--- a/remote.c\n\t+++ b/remote.c\n\t@@ -417,4 +417,4 @@ static int handle_config(const char *key, const char *value,\n\t \tremote->origin = REMOTE_CONFIG;\n\t-\tif (kvi->scope == CONFIG_SCOPE_LOCAL ||\n\t-\t    kvi->scope == CONFIG_SCOPE_WORKTREE)\n\t+\tif (kvi->scope2 == CONFIG_SCOPE_LOCAL ||\n\t+\t    kvi->scope2 == CONFIG_SCOPE_WORKTREE)\n\t \t\tremote->configured_in_repo = 1;\n\tdiff --git a/t/helper/test-config.c b/t/helper/test-config.c\n\tindex 737505583d4..fa89cdd084c 100644\n\t--- a/t/helper/test-config.c\n\t+++ b/t/helper/test-config.c\n\t@@ -54,6 +54,6 @@ static int iterate_cb(const char *var, const char *value,\n\t \tprintf(\"value=%s\\n\", value ? value : \"(null)\");\n\t-\tprintf(\"origin=%s\\n\", config_origin_type_name(kvi->origin_type));\n\t-\tprintf(\"name=%s\\n\", kvi->filename ? kvi->filename : \"\");\n\t-\tprintf(\"lno=%d\\n\", kvi->linenr);\n\t-\tprintf(\"scope=%s\\n\", config_scope_name(kvi->scope));\n\t+\tprintf(\"origin=%s\\n\", config_origin_type_name(kvi->origin_type2));\n\t+\tprintf(\"name=%s\\n\", kvi->filename2 ? kvi->filename2 : \"\");\n\t+\tprintf(\"lno=%d\\n\", kvi->linenr2);\n\t+\tprintf(\"scope=%s\\n\", config_scope_name(kvi->scope2));\n\t \n\tdiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\n\tindex 83db3c755bd..338c5a58bd9 100644\n\t--- a/trace2/tr2_tgt_event.c\n\t+++ b/trace2/tr2_tgt_event.c\n\t@@ -482,3 +482,3 @@ static void fn_param_fl(const char *file, int line, const char *param,\n\t \tstruct json_writer jw = JSON_WRITER_INIT;\n\t-\tenum config_scope scope = kvi->scope;\n\t+\tenum config_scope scope = kvi->scope2;\n\t \tconst char *scope_name = config_scope_name(scope);\n\tdiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\n\tindex 65e9be9c5a4..5a06042e7c3 100644\n\t--- a/trace2/tr2_tgt_normal.c\n\t+++ b/trace2/tr2_tgt_normal.c\n\t@@ -301,3 +301,3 @@ static void fn_param_fl(const char *file, int line, const char *param,\n\t \tstruct strbuf buf_payload = STRBUF_INIT;\n\t-\tenum config_scope scope = kvi->scope;\n\t+\tenum config_scope scope = kvi->scope2;\n\t \tconst char *scope_name = config_scope_name(scope);\n\tdiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\n\tindex f402f6e3813..96fa359183d 100644\n\t--- a/trace2/tr2_tgt_perf.c\n\t+++ b/trace2/tr2_tgt_perf.c\n\t@@ -445,3 +445,3 @@ static void fn_param_fl(const char *file, int line, const char *param,\n\t \tstruct strbuf scope_payload = STRBUF_INIT;\n\t-\tenum config_scope scope = kvi->scope;\n\t+\tenum config_scope scope = kvi->scope2;\n\t \tconst char *scope_name = config_scope_name(scope);\n\nSo, as this shows us your 08/14 has gone through the effort of passing\nthis \"kvi\" info to every single callback, but it's only this handful\nthat actually needs this information.\n\nSo, even if we *can* get this to work I don't think it's worth it,\nespecially as this would preclude giving these config callbacks some\n\"lighter\" API that doesn't take the trouble of recording and ferrying\nthis information to them.\n\nOf course *now* we need to always prepare this information anyway, as\nanyone could access it via a global, but as the work you've done here\nshows we're always doing that, but only need it for these few cases.\n\nSo I really think we could leave the vast majority of the current\ncallbacks alone, and just supply a new \"kvi\" callback. My\nhttps://lore.kernel.org/git/RFC-patch-5.5-2b80d293c83-20230317T042408Z-avarab@gmail.com/\nshowed one way forward with that.\n\nI think this should also neatly answer some of your outstanding\nquestions. Especially as the above shows that the only non-test caller\nthat needs \"linenr\" is the builtin/config.c caller that my proposed RFC\n(linked above) tackled directly. Most of these callbacks just need the\nmore basic \"scope\".\n\nSo, in particular:\n\n> Here's an exhaustive list of all of the changes:\n>\n> * Cases that need a judgement call\n>\n>   - trace2/tr2_cfg.c:tr2_cfg_set_fl()\n>\n>     This function needs to account for tr2_cfg_cb() now using \"kvi\".\n>     Since this is only called (indirectly) by git_config_set(), config\n>     source information has never been available here, so just pass NULL.\n>     It will be tr2_cfg_cb()'s responsibility to not use \"kvi\".\n\nJust adding a \"CONFIG_SCOPE_IN_PROCESS\", \"CONFIG_SCOPE_SET\" or whatever\nyou'd want to call it seems to make much more sense here, no? \n\n>   - builtin/checkout.c:checkout_main()\n>\n>     This calls git_xmerge_config() as a shorthand for parsing a CLI arg.\n>     \"kvi\" doesn't apply, so just pass NULL. This might be worth\n>     refactoring away, since git_xmerge_config() can call\n>     git_default_config().\n\nAnother example of a caller which never actually cares about this data,\nso if it doesn't need to have it passed to it, it doesn't need to fake\nit up either.\n\n>   - config.c:git_config_include()\n>\n>     Replace the local \"kvi\" variable with the \"kvi\" parameter. This\n>     makes config_include_data.config_reader obsolete, so remove it.\n\nNo comment (internal to config.c, as noted above).\n\n> * Hard for cocci to catch\n>\n>   - urlmatch.c\n>\n>     Manually refactor the custom config callbacks in \"struct\n>     urlmatch_config\".\n>\n>   - diff.h, fsck.h, grep.h, ident.h, xdiff-interface.h\n>\n>     \"struct key_value_info\" hasn't been defined yet, so forward declare\n>     it. Alternatively, maybe these files should \"#include config.h\".\n\nAll of these problems go away if you don't insist on changing every\nsingle caller, you'll just have a step where you remove the current\nglobal in favor of some \"config callback with kvi\" info, and \"make\" will\nspot those callers that aren't converted yet.\n\nThose changes will be trivial enough (just the callers I noted above) to\nnot require the tricky cocci patch in 08/14.\n\n> * Likely deficiencies in .cocci patch\n>\n>   - submodule-config.c:gitmodules_cb()\n>\n>     Manually refactor a parse_config() call that gets missed because it\n>     uses a different \"*data\" arg.\n>\n>   - grep.h, various\n>\n>     Manually refactor grep_config() calls. Not sure why these don't get\n>     picked up.\n>\n>   - config.c:git_config_include(), http.c:http_options()\n>\n>     Manually add \"kvi\" where it was missed. Not sure why they get missed.\n>\n>   - builtin/clone.c:write_one_config()\n>\n>     Manually refactor a git_clone_config() call. Probably got missed\n>     because I didn't include git_config_parse_parameter().\n>\n>   - ident.h\n>\n>     Remove the UNUSED attribute. Not sure why this is the only instance\n>     of this.\n>\n>   - git-compat-util.h, compat/mingw.[h|c]\n>\n>     Manually refactor noop_core_config(), platform_core_config() and\n>     mingw_core_config(). I can probably add these as \"manual fixups\" in\n>     cocci.\n\nditto.\n"},{"id":"476647","messageId":"20230505210702.3359841-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"230501.868re8jna4.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 10/14] (RFC-only) config: finish config_fn_t refactor","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-05-05T21:07:02Z","receivedAt":"2023-05-05T21:07:08Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n> But as I pointed out in\n> https://lore.kernel.org/git/RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com/\n> I think this part (and the preceding two commits) are really taking is\n> down the wrong path.\n> \n> To demonstrate why, here's a patch-on-top of this topic as a whole where\n> I renamed the \"kvi\" struct members. I've excluded config.c itself (as\n> its internals aren't interesting for the purposes of this discussion):\n\n[snip patch showing where kvi is used]\n\nAh, thanks for this patch. One of my objections to your proposal in the\naforementioned thread is that we would end up with a lot of callback-\ntaking config functions that have 2 variants, one for the kvi callback\nand one for the non-kvi callback, but here your patch shows that it\nwon't be the case.\n\nYour approach does look like a reasonable one.\n\nAs for my own opinions, (before Ævar sent this email) I took a look\nat these patches myself and had some issues with at least the first\n2: in patch 1, kvi_fn() replaces fn() for some but not all invocations\nof fn() (in patch 2, you can see one such invocation that was not\nchanged), and I was having difficulty thinking of what kind of bugs\nI should watch out for since not all invocations were changed; and\nin patch 2, the safeguard of not setting kvi and source together was\nremoved and likewise I was having difficulty thinking of what kind of\nbugs could occur from both being set at once inadvertently. I was going\nto suggest reordering the patches such that the large-scale refactoring\n(and any supporting patches like [PATCH 06/14] config: inline\ngit_color_default_config) should occur first (or waiting for a reviewer\nwho is convinced that patches 1 and 2 are OK, I guess), but having now\nseen that sidestepping a large part of this makes sense, sidestepping\nseems like a good idea to me.\n"},{"id":"476812","messageId":"kl6lr0rq1qt8.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59629","inReplyTo":"230501.868re8jna4.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 10/14] (RFC-only) config: finish config_fn_t refactor","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-05-08T21:00:51Z","receivedAt":"2023-05-08T21:01:21Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> On Fri, Apr 21 2023, Glen Choo via GitGitGadget wrote:\n>\n>> From: Glen Choo <chooglen@google.com>\n>\n> I like the general goal of this series, i.e. to get rid of the \"reader\"\n> callback, and pass stuff more explicitly.\n>\n> But as I pointed out in\n> https://lore.kernel.org/git/RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com/\n> I think this part (and the preceding two commits) are really taking is\n> down the wrong path.\n>\n> To demonstrate why, here's a patch-on-top of this topic as a whole where\n> I renamed the \"kvi\" struct members. I've excluded config.c itself (as\n> its internals aren't interesting for the purposes of this discussion):\n>\n> [Patch showing used kvi members...]\n>\n> So, as this shows us your 08/14 has gone through the effort of passing\n> this \"kvi\" info to every single callback, but it's only this handful\n> that actually needs this information.\n\nThe attached patch misses the majority of \"kvi\" users, which use it in\ndie_bad_number(). This change happens in Patch 13/14, where the whole\n\"kvi\" is passed into config.c machinery. It's still a much smaller\nnumber of config callbacks than the ones changed in these few patches,\nbut it's more than a handful, I'd think.\n\n> So, even if we *can* get this to work I don't think it's worth it,\n> especially as this would preclude giving these config callbacks some\n> \"lighter\" API that doesn't take the trouble of recording and ferrying\n> this information to them.\n\nI assume the \"lighter API\" refers to the current API?\n\nIf I'm reading this correctly, your concern isn't primarily about\nreducing churn, it's YAGNI - since most callers don't need this info,\nthey should be able to reap the benefits of an API that doesn't provide\nthat info. Thus, you're proposing to having both the current API and a\nnew kvi-based one?\n\nIf so, I don't think that's a good route to take:\n\n- Having both *_kvi() and \"old\" variants will add bloat to an already\n  bloated config API. Even without further changes, we'd need to add\n  *_kvi() to at least config_with_options() and repo_config(), as well\n  as all of the functions that config_with_options() uses under the\n  hood (some of which are public).\n\n  To some extent, this can be managed by shrinking the config API (e.g.\n  like your suggestion to get rid of git_config_from_file() [1]), but\n  IMO that needs more discussion.\n\n- What benefit is the old API giving us vs the new one? It won't be any\n  faster since the machinery will need to support the new API, and even\n  if it were, the benefit would be tiny. In some cases it might be nice\n  to use the config callback in a non-config setting, but this patch\n  shows that those are rare and can be easily worked around.\n\n- I strongly suspect that we'd already like \"kvi\" in more places, and if\n  we made it readily available, we would add these additional callers.\n  E.g. we already use it to give additional diagnostics when failing to\n  parse a number, and there's no reason why we shouldn't do this when\n  doing other kinds of parsing (e.g. date in git_config_expiry_date() or\n  color in color_parse_mem()).\n\nIf your concern really is primarily about churn and we actually want to\nmove everything to the new API eventually, wouldn't it be better to bite\nthe bullet and take a one time, well-scoped churn cost instead of a\nlonger migration?\n\n[1] https://lore.kernel.org/git/230307.86wn3szrzu.gmgdl@evledraar.gmail.com/\n\n> I think this should also neatly answer some of your outstanding\n> questions. Especially as the above shows that the only non-test caller\n> that needs \"linenr\" is the builtin/config.c caller that my proposed RFC\n> (linked above) tackled directly. Most of these callbacks just need the\n> more basic \"scope\".\n\nI didn't see where builtin/config.c was handled in the above link.\nPerhaps you meant a different RFC,\nhttps://github.com/avar/git/commit/0233297a359bbda43a902dd0213aacdca82faa34?\n\n> So, in particular:\n>\n>> Here's an exhaustive list of all of the changes:\n>>\n>> * Cases that need a judgement call\n>>\n>>   - trace2/tr2_cfg.c:tr2_cfg_set_fl()\n>>\n>>     This function needs to account for tr2_cfg_cb() now using \"kvi\".\n>>     Since this is only called (indirectly) by git_config_set(), config\n>>     source information has never been available here, so just pass NULL.\n>>     It will be tr2_cfg_cb()'s responsibility to not use \"kvi\".\n>\n> Just adding a \"CONFIG_SCOPE_IN_PROCESS\", \"CONFIG_SCOPE_SET\" or whatever\n> you'd want to call it seems to make much more sense here, no? \n\nCONFIG_SCOPE_SET makes sense.\n\n>>   - builtin/checkout.c:checkout_main()\n>>\n>>     This calls git_xmerge_config() as a shorthand for parsing a CLI arg.\n>>     \"kvi\" doesn't apply, so just pass NULL. This might be worth\n>>     refactoring away, since git_xmerge_config() can call\n>>     git_default_config().\n>\n> Another example of a caller which never actually cares about this data,\n> so if it doesn't need to have it passed to it, it doesn't need to fake\n> it up either.\n\nHere's a case of YAGNI I mentioned above and I agree it would be nice to\nnot have to fake a \"kvi\". However, git_xmerge_config() can call\ngit_defualt_config() so this seems more like it's abusing the config\ncallback to parse a CLI arg. A better resolution would be to have a\nfunction dedicated to parsing \"merge.conflictstyle\".\n\n>> * Hard for cocci to catch\n>>\n>>   - urlmatch.c\n>>\n>>     Manually refactor the custom config callbacks in \"struct\n>>     urlmatch_config\".\n>>\n>>   - diff.h, fsck.h, grep.h, ident.h, xdiff-interface.h\n>>\n>>     \"struct key_value_info\" hasn't been defined yet, so forward declare\n>>     it. Alternatively, maybe these files should \"#include config.h\".\n>\n> All of these problems go away if you don't insist on changing every\n> single caller, you'll just have a step where you remove the current\n> global in favor of some \"config callback with kvi\" info, and \"make\" will\n> spot those callers that aren't converted yet.\n>\n> Those changes will be trivial enough (just the callers I noted above) to\n> not require the tricky cocci patch in 08/14.\n>\n>> * Likely deficiencies in .cocci patch\n>>\n>>   - submodule-config.c:gitmodules_cb()\n>>\n>>     Manually refactor a parse_config() call that gets missed because it\n>>     uses a different \"*data\" arg.\n>>\n>>   - grep.h, various\n>>\n>>     Manually refactor grep_config() calls. Not sure why these don't get\n>>     picked up.\n>>\n>>   - config.c:git_config_include(), http.c:http_options()\n>>\n>>     Manually add \"kvi\" where it was missed. Not sure why they get missed.\n>>\n>>   - builtin/clone.c:write_one_config()\n>>\n>>     Manually refactor a git_clone_config() call. Probably got missed\n>>     because I didn't include git_config_parse_parameter().\n>>\n>>   - ident.h\n>>\n>>     Remove the UNUSED attribute. Not sure why this is the only instance\n>>     of this.\n>>\n>>   - git-compat-util.h, compat/mingw.[h|c]\n>>\n>>     Manually refactor noop_core_config(), platform_core_config() and\n>>     mingw_core_config(). I can probably add these as \"manual fixups\" in\n>>     cocci.\n>\n> ditto.\n\nYeah, there was definitely more cocci trickery than I'd like. Btw, if\nyou comments on the .cocci file itself, feel free to share. I obviously\njust hacked together something based on a very rudimentary understanding\nof cocci and I'd love suggestions on how to improve.\n"},{"id":"476935","messageId":"kl6ly1lxyvgr.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59629","inReplyTo":"20230505210702.3359841-1-jonathantanmy@google.com","subject":"Re: [PATCH 10/14] (RFC-only) config: finish config_fn_t refactor","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-05-09T22:46:12Z","receivedAt":"2023-05-09T22:46:19Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"I've covered most your response to Ævar upthread, so I'll omit that.\n\nJonathan Tan <jonathantanmy@google.com> writes:\n\n> As for my own opinions, (before Ævar sent this email) I took a look\n> at these patches myself and had some issues with at least the first\n> 2: in patch 1, kvi_fn() replaces fn() for some but not all invocations\n> of fn() (in patch 2, you can see one such invocation that was not\n> changed), and I was having difficulty thinking of what kind of bugs\n> I should watch out for since not all invocations were changed; and\n> in patch 2, the safeguard of not setting kvi and source together was\n> removed and likewise I was having difficulty thinking of what kind of\n> bugs could occur from both being set at once inadvertently. I was going\n> to suggest reordering the patches such that the large-scale refactoring\n> (and any supporting patches like [PATCH 06/14] config: inline\n> git_color_default_config) should occur first (or waiting for a reviewer\n> who is convinced that patches 1 and 2 are OK, I guess), but having now\n> seen that sidestepping a large part of this makes sense, sidestepping\n> seems like a good idea to me.\n\nIn an off-list discussion, we described some plausible ways to organize\nthe refactor that would make it easier for a reviewer to confirm safety.\n\nI haven't tried that yet because it sounds like you'd prefer the\nsidestepping approach. Do you prefer that primarily for safety reasons,\nor is it largely motivated by other concerns too (e.g. reducing churn or\nsidestepping produces a better API)? If the primary concern is just\nsafety, I'm somewhat confident that we can find some way to organize\nthis that makes it easier to review and I should just do it.\n"},{"id":"477041","messageId":"20230511162149.897554-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"kl6ly1lxyvgr.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH 10/14] (RFC-only) config: finish config_fn_t refactor","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-05-11T16:21:49Z","receivedAt":"2023-05-11T16:21:57Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> I've covered most your response to Ævar upthread, so I'll omit that.\n\nThanks. Indeed, I missed the situation in which a caller used kvi not\nby accessing its fields directly but by passing kvi to a function in\nconfig.c.\n\n> In an off-list discussion, we described some plausible ways to organize\n> the refactor that would make it easier for a reviewer to confirm safety.\n> \n> I haven't tried that yet because it sounds like you'd prefer the\n> sidestepping approach. Do you prefer that primarily for safety reasons,\n> or is it largely motivated by other concerns too (e.g. reducing churn or\n> sidestepping produces a better API)? If the primary concern is just\n> safety, I'm somewhat confident that we can find some way to organize\n> this that makes it easier to review and I should just do it.\n\nMy preference for the sidestepping approach was to reduce churn, but as\nyou have pointed out, it doesn't actually reduce churn. So now I think\nthat the patches should be reordered (but am open to being convinced\notherwise, of course).\n\nAs for safety and better API, I think both approaches (bulk modification\nof all functions to take the new config_fn_t and two sets of functions\neach taking a different function type) are equally safe, and it is\nbulk modification that results in a better API (as you've demonstrated,\nhaving kvi information is needed for good error messages, and I expect\nthat to be more and more needed).\n"},{"id":"477806","messageId":"d5edf7e3fddc2eb91a17e7aa215da7e67881a191.1685472133.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 01/14] config: inline git_color_default_config","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:41:59Z","receivedAt":"2023-05-30T18:42:29Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\ngit_color_default_config() is a shorthand for calling two other config\ncallbacks. There are no other non-static functions that do this and it\nwill complicate our refactoring of config_fn_t so inline it instead.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/branch.c      | 5 ++++-\n builtin/clean.c       | 6 ++++--\n builtin/grep.c        | 5 ++++-\n builtin/show-branch.c | 5 ++++-\n builtin/tag.c         | 6 +++++-\n color.c               | 8 --------\n color.h               | 6 +-----\n 7 files changed, 22 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 6413a016c57..c6982181fd5 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -114,7 +114,10 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static const char *branch_get_color(enum color_branch ix)\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 14c0d555eac..a06df48a269 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -129,8 +129,10 @@ static int git_clean_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\t/* inspect the color.ui config variable and others */\n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static const char *clean_get_color(enum color_clean ix)\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex a1b68d90bdb..c880c9538d6 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -290,7 +290,10 @@ static int wait_all(void)\n static int grep_cmd_config(const char *var, const char *value, void *cb)\n {\n \tint st = grep_config(var, value, cb);\n-\tif (git_color_default_config(var, value, NULL) < 0)\n+\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\tst = -1;\n+\telse if (git_default_config(var, value, cb) < 0)\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex 463a8d11c31..82ae2a7e475 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -576,7 +576,10 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static int omit_in_dense(struct commit *commit, struct commit **rev, int n)\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 782bb3aa2ff..7245a4d30e6 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -204,7 +204,11 @@ static int git_tag_config(const char *var, const char *value, void *cb)\n \n \tif (starts_with(var, \"column.\"))\n \t\treturn git_column_config(var, value, \"tag\", &colopts);\n-\treturn git_color_default_config(var, value, cb);\n+\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static void write_tag_body(int fd, const struct object_id *oid)\ndiff --git a/color.c b/color.c\nindex 672dcbb73a6..9bdbffe928d 100644\n--- a/color.c\n+++ b/color.c\n@@ -427,14 +427,6 @@ int git_color_config(const char *var, const char *value, void *cb UNUSED)\n \treturn 0;\n }\n \n-int git_color_default_config(const char *var, const char *value, void *cb)\n-{\n-\tif (git_color_config(var, value, cb) < 0)\n-\t\treturn -1;\n-\n-\treturn git_default_config(var, value, cb);\n-}\n-\n void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb)\n {\n \tif (*color)\ndiff --git a/color.h b/color.h\nindex cfc8f841b23..bb28343be21 100644\n--- a/color.h\n+++ b/color.h\n@@ -88,12 +88,8 @@ extern const int column_colors_ansi_max;\n  */\n extern int color_stdout_is_tty;\n \n-/*\n- * Use the first one if you need only color config; the second is a convenience\n- * if you are just going to change to git_default_config, too.\n- */\n+/* Parse color config. */\n int git_color_config(const char *var, const char *value, void *cb);\n-int git_color_default_config(const char *var, const char *value, void *cb);\n \n /*\n  * Parse a config option, which can be a boolean or one of\n-- \ngitgitgadget\n\n"},{"id":"477807","messageId":"821f0b905803fe4f693a64e424a61119e97c3dce.1685472133.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 02/14] urlmatch.h: use config_fn_t type","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:42:00Z","receivedAt":"2023-05-30T18:42:31Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThese are actually used as config callbacks, so use the typedef-ed type\nand make future refactors easier.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n urlmatch.h | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/urlmatch.h b/urlmatch.h\nindex 9f40b00bfb8..bee374a642c 100644\n--- a/urlmatch.h\n+++ b/urlmatch.h\n@@ -2,6 +2,7 @@\n #define URL_MATCH_H\n \n #include \"string-list.h\"\n+#include \"config.h\"\n \n struct url_info {\n \t/* normalized url on success, must be freed, otherwise NULL */\n@@ -48,8 +49,8 @@ struct urlmatch_config {\n \tconst char *key;\n \n \tvoid *cb;\n-\tint (*collect_fn)(const char *var, const char *value, void *cb);\n-\tint (*cascade_fn)(const char *var, const char *value, void *cb);\n+\tconfig_fn_t collect_fn;\n+\tconfig_fn_t cascade_fn;\n \t/*\n \t * Compare the two matches, the one just discovered and the existing\n \t * best match and return a negative value if the found item is to be\n-- \ngitgitgadget\n\n"},{"id":"477808","messageId":"6834e37066e7877646fc7c37aa79704d14381251.1685472133.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 03/14] (RFC-only) config: add kvi arg to config_fn_t","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:42:01Z","receivedAt":"2023-05-30T18:42:33Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\n..without actually changing any of its implementations. This commit does\nnot build - I've split this out for readability, but post-RFC I will\nsquash this with the rest of the refactor + cocci changes.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c                                      |   8 +-\n config.h                                      |  16 +-\n .../coccinelle/config_fn_kvi.pending.cocci    | 146 ++++++++++++++++++\n 3 files changed, 158 insertions(+), 12 deletions(-)\n create mode 100644 contrib/coccinelle/config_fn_kvi.pending.cocci\n\ndiff --git a/config.c b/config.c\nindex 493f47df8ae..945f4f3b77e 100644\n--- a/config.c\n+++ b/config.c\n@@ -489,7 +489,7 @@ static int git_config_include(const char *var, const char *value, void *data)\n \t * Pass along all values, including \"include\" directives; this makes it\n \t * possible to query information on the includes themselves.\n \t */\n-\tret = inc->fn(var, value, inc->data);\n+\tret = inc->fn(var, value, NULL, inc->data);\n \tif (ret < 0)\n \t\treturn ret;\n \n@@ -671,7 +671,7 @@ static int config_parse_pair(const char *key, const char *value,\n \tif (git_config_parse_key(key, &canonical_name, NULL))\n \t\treturn -1;\n \n-\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n+\tret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;\n \tfree(canonical_name);\n \treturn ret;\n }\n@@ -959,7 +959,7 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \t * accurate line number in error messages.\n \t */\n \tcs->linenr--;\n-\tret = fn(name->buf, value, data);\n+\tret = fn(name->buf, value, NULL, data);\n \tif (ret >= 0)\n \t\tcs->linenr++;\n \treturn ret;\n@@ -2303,7 +2303,7 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \n \t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n \n-\t\tif (fn(entry->key, values->items[value_index].string, data) < 0)\n+\t\tif (fn(entry->key, values->items[value_index].string, NULL, data) < 0)\n \t\t\tgit_die_config_linenr(entry->key,\n \t\t\t\t\t      reader->config_kvi->filename,\n \t\t\t\t\t      reader->config_kvi->linenr);\ndiff --git a/config.h b/config.h\nindex 247b572b37b..9d052c52c3c 100644\n--- a/config.h\n+++ b/config.h\n@@ -111,6 +111,13 @@ struct config_options {\n \t} error_action;\n };\n \n+struct key_value_info {\n+\tconst char *filename;\n+\tint linenr;\n+\tenum config_origin_type origin_type;\n+\tenum config_scope scope;\n+};\n+\n /**\n  * A config callback function takes three parameters:\n  *\n@@ -129,7 +136,7 @@ struct config_options {\n  * A config callback should return 0 for success, or -1 if the variable\n  * could not be parsed properly.\n  */\n-typedef int (*config_fn_t)(const char *, const char *, void *);\n+typedef int (*config_fn_t)(const char *, const char *, struct key_value_info *, void *);\n \n int git_default_config(const char *, const char *, void *);\n \n@@ -667,13 +674,6 @@ int git_config_get_expiry(const char *key, const char **output);\n /* parse either \"this many days\" integer, or \"5.days.ago\" approxidate */\n int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t now);\n \n-struct key_value_info {\n-\tconst char *filename;\n-\tint linenr;\n-\tenum config_origin_type origin_type;\n-\tenum config_scope scope;\n-};\n-\n /**\n  * First prints the error message specified by the caller in `err` and then\n  * dies printing the line number and the file name of the highest priority\ndiff --git a/contrib/coccinelle/config_fn_kvi.pending.cocci b/contrib/coccinelle/config_fn_kvi.pending.cocci\nnew file mode 100644\nindex 00000000000..d4c84599afa\n--- /dev/null\n+++ b/contrib/coccinelle/config_fn_kvi.pending.cocci\n@@ -0,0 +1,146 @@\n+// These are safe to apply to *.c *.h builtin/*.c\n+\n+@ get_fn @\n+identifier fn, R;\n+@@\n+(\n+(\n+git_config_from_file\n+|\n+git_config_from_file_with_options\n+|\n+git_config_from_mem\n+|\n+git_config_from_blob_oid\n+|\n+read_early_config\n+|\n+read_very_early_config\n+|\n+config_with_options\n+|\n+git_config\n+|\n+git_protected_config\n+|\n+config_from_gitmodules\n+)\n+  (fn, ...)\n+|\n+repo_config(R, fn, ...)\n+)\n+\n+@ extends get_fn @\n+identifier C1, C2, D;\n+@@\n+int fn(const char *C1, const char *C2,\n++  struct key_value_info *kvi,\n+  void *D);\n+\n+@ extends get_fn @\n+@@\n+int fn(const char *, const char *,\n++  struct key_value_info *,\n+  void *);\n+\n+@ extends get_fn @\n+// Don't change fns that look like callback fns but aren't\n+identifier fn2 != tar_filter_config && != git_diff_heuristic_config &&\n+  != git_default_submodule_config && != git_color_config &&\n+  != bundle_list_update && != parse_object_filter_config;\n+identifier C1, C2, D1, D2, S;\n+attribute name UNUSED;\n+@@\n+int fn(const char *C1, const char *C2,\n++  struct key_value_info *kvi,\n+  void *D1) {\n+<+...\n+(\n+fn2(C1, C2,\n++ kvi,\n+D2);\n+|\n+if(fn2(C1, C2,\n++ kvi,\n+D2) < 0) { ... }\n+|\n+return fn2(C1, C2,\n++ kvi,\n+D2);\n+|\n+S = fn2(C1, C2,\n++ kvi,\n+D2);\n+)\n+...+>\n+  }\n+\n+@ extends get_fn@\n+identifier C1, C2, D;\n+attribute name UNUSED;\n+@@\n+int fn(const char *C1, const char *C2,\n++  struct key_value_info *kvi UNUSED,\n+  void *D) {...}\n+\n+\n+// The previous rules don't catch all callbacks, especially if they're defined\n+// in a separate file from the git_config() call. Fix these manually.\n+@@\n+identifier C1, C2, D;\n+attribute name UNUSED;\n+@@\n+int\n+(\n+git_ident_config\n+|\n+urlmatch_collect_fn\n+|\n+write_one_config\n+|\n+forbid_remote_url\n+|\n+credential_config_callback\n+)\n+  (const char *C1, const char *C2,\n++  struct key_value_info *kvi UNUSED,\n+  void *D) {...}\n+\n+@@\n+identifier C1, C2, D, D2, S, fn2;\n+@@\n+int\n+(\n+http_options\n+|\n+git_status_config\n+|\n+git_commit_config\n+|\n+git_default_core_config\n+|\n+grep_config\n+)\n+  (const char *C1, const char *C2,\n++  struct key_value_info *kvi,\n+  void *D) {\n+<+...\n+(\n+fn2(C1, C2,\n++ kvi,\n+D2);\n+|\n+if(fn2(C1, C2,\n++ kvi,\n+D2) < 0) { ... }\n+|\n+return fn2(C1, C2,\n++ kvi,\n+D2);\n+|\n+S = fn2(C1, C2,\n++ kvi,\n+D2);\n+)\n+...+>\n+  }\n-- \ngitgitgadget\n\n"},{"id":"477809","messageId":"74f43fc727ebb07874b902b0ec64dd0954ffff30.1685472134.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 08/14] builtin/config.c: test misuse of format_config()","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:42:06Z","receivedAt":"2023-05-30T18:42:35Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\ncurrent_config_*() functions aren't meant to be called outside of\nconfig callbacks because they read state that is only set when iterating\nthrough config. However, several sites in builtin/config.c are\nindirectly calling these functions outside of config callbacks thanks to\nthe format_config() helper. Show the current, bad behavior via tests\nso that the fixes in a subsequent commit will be clearer.\n\nThe misbehaving cases are:\n\n* \"git config --get-urlmatch --show-scope\" results in an \"unknown\"\n   scope, where it arguably should show the config file's scope. It's\n   clear that this wasn't intended, though: we knew that\n   \"--get-urlmatch\" couldn't show config source metadata, which is why\n   \"--show-origin\" was marked incompatible with \"--get-urlmatch\" when\n   it was introduced [1]. It was most likely a mistake that we allowed\n   \"--show-scope\" to sneak through.\n\n* Similarly, \"git config --default\" doesn't set config source metadata ,\n  so \"--show-scope\" also results in \"unknown\", and \"--show-origin\"\n  results in a BUG().\n\n[1] https://lore.kernel.org/git/20160205112001.GA13397@sigill.intra.peff.net/\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n t/t1300-config.sh | 25 +++++++++++++++++++++++++\n 1 file changed, 25 insertions(+)\n\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 2575279ab84..57fe250b78f 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1627,6 +1627,21 @@ test_expect_success 'urlmatch' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'urlmatch with --show-scope' '\n+\tcat >.git/config <<-\\EOF &&\n+\t[http \"https://weak.example.com\"]\n+\t\tsslVerify = false\n+\t\tcookieFile = /tmp/cookie.txt\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tunknown\thttp.cookiefile /tmp/cookie.txt\n+\tunknown\thttp.sslverify false\n+\tEOF\n+\tgit config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'urlmatch favors more specific URLs' '\n \tcat >.git/config <<-\\EOF &&\n \t[http \"https://example.com/\"]\n@@ -2014,6 +2029,10 @@ test_expect_success '--show-origin blob ref' '\n \ttest_cmp expect output\n '\n \n+test_expect_success '--show-origin with --default' '\n+\ttest_must_fail git config --show-origin --default foo some.key\n+'\n+\n test_expect_success '--show-scope with --list' '\n \tcat >expect <<-EOF &&\n \tglobal\tuser.global=true\n@@ -2082,6 +2101,12 @@ test_expect_success '--show-scope with --show-origin' '\n \ttest_cmp expect output\n '\n \n+test_expect_success '--show-scope with --default' '\n+\tgit config --show-scope --default foo some.key >actual &&\n+\techo \"unknown\tfoo\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'override global and system config' '\n \ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n \tcat >\"$HOME\"/.gitconfig <<-EOF &&\n-- \ngitgitgadget\n\n"},{"id":"477811","messageId":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.git.git.1682104398.gitgitgadget@gmail.com","subject":"[PATCH v2 00/14] [RFC] config: remove global state from config iteration","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:41:58Z","receivedAt":"2023-05-30T18:42:36Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Thanks for the review on the previous round!\n\nThis v2 is mostly a reorganization of v1, largely based off Jonathan's\nfeedback in [1]. As such, nearly all of v1's cover letter still applies\n(except for the \"Patch Overview\" section [2]) so this cover letter omits\nthose bits that have already been covered. The first non-RFC version will\nget rerolled with full cover letter.\n\nSince a lot of this series removes the config_reader stuff from\ngc/config-parsing-cleanup, it might be useful to diff this with \"master\"\nright before that:\n\ngit diff 0a8c337394 HEAD -- config.c\n\n= Changes since v1\n\n * Reorganize patches in a (hopefully) easier-to-review way.\n\n * Squash bugs in builtin/config.c that became apparent during the refactor.\n   These could have been fixed in v1, but they dropped off my radar.\n\n= Patch overview\n\n * 1-5/14 add the \"kvi\" parameter to the config_fn_t signature. These are\n   mostly unchanged from v1.\n\n * 6-11/14 converts the config.c machinery off \"config_reader.config_kvi\"\n   and \"config_reader.source\" and onto the new \"kvi\" arg. Most of the\n   changes from v1 are here.\n   \n   In v1, we converted all of the config.c machinery first, making the \"kvi\"\n   arg available everywhere before the refactor. Thus we could convert all\n   callers of the current_*() API to the \"kvi\" arg in a single step. However\n   (as Jonathan rightfully pointed out), some of the changes to the\n   machinery are non-trivial, and it's quite difficult to spot bugs in the\n   intermediate patches.\n   \n   In v2, we convert the config.c machinery from \"config_reader\" to \"kvi\"\n   one-by-one: configsets, then files, then CLI. To exercise the \"kvi\" arg\n   as soon as possible, we convert from current_*() to \"kvi\" as soon as it\n   is available. For example, in 6/14 \"kvi\" is available only in configsets,\n   so we convert the current_*() call sites that are only reached via\n   configsets and leave the others untouched. This means that we have a mix\n   of current_*() and \"kvi\" in the middle, but auditing the changes is\n   relatively easy (compared to v1's machinery changes), since you only need\n   to verify that a callback isn't relying on the \"kvi\" arg before it is\n   available, and that current_*() and \"kvi\" give the same value.\n   \n   * 8-9/14 squashes some bugs where builtin/config.c was calling the\n     current_*() API outside of config callbacks. The \"kvi\" plumbing doesn't\n     just make the bugs apparent, it also provides an obvious way to fix the\n     bugs (by injecting \"kvi\" into the right places in builtin/config.c).\n     These would have been nontrivial to fix if we were still using global\n     state.\n\n * 12-14/14 remove config_reader by taking advantage of the \"kvi\" parameter\n   and doing some other light plumbing.\n\n[1]\nhttps://lore.kernel.org/git/20230505210702.3359841-1-jonathantanmy@google.com\n[2]\nhttps://lore.kernel.org/pull.1497.git.git.1682104398.gitgitgadget@gmail.com\n\nGlen Choo (14):\n  config: inline git_color_default_config\n  urlmatch.h: use config_fn_t type\n  (RFC-only) config: add kvi arg to config_fn_t\n  (RFC-only) config: apply cocci to config_fn_t implementations\n  (RFC-only) config: finish config_fn_t refactor\n  config.c: pass kvi in configsets\n  config: provide kvi with config files\n  builtin/config.c: test misuse of format_config()\n  config.c: provide kvi with CLI config\n  trace2: plumb config kvi\n  config: pass kvi to die_bad_number()\n  config.c: remove config_reader from configsets\n  config: add kvi.path, use it to evaluate includes\n  config: pass source to config_parser_event_fn_t\n\n alias.c                                       |   3 +-\n archive-tar.c                                 |   5 +-\n archive-zip.c                                 |   1 +\n builtin/add.c                                 |   5 +-\n builtin/blame.c                               |   5 +-\n builtin/branch.c                              |   8 +-\n builtin/cat-file.c                            |   5 +-\n builtin/checkout.c                            |   7 +-\n builtin/clean.c                               |   9 +-\n builtin/clone.c                               |  10 +-\n builtin/column.c                              |   3 +-\n builtin/commit-graph.c                        |   3 +-\n builtin/commit.c                              |  20 +-\n builtin/config.c                              |  65 ++-\n builtin/difftool.c                            |   5 +-\n builtin/fetch.c                               |  12 +-\n builtin/fsmonitor--daemon.c                   |  11 +-\n builtin/grep.c                                |  12 +-\n builtin/help.c                                |   5 +-\n builtin/index-pack.c                          |   9 +-\n builtin/log.c                                 |  12 +-\n builtin/merge.c                               |   7 +-\n builtin/multi-pack-index.c                    |   1 +\n builtin/pack-objects.c                        |  19 +-\n builtin/patch-id.c                            |   5 +-\n builtin/pull.c                                |   5 +-\n builtin/push.c                                |   5 +-\n builtin/read-tree.c                           |   5 +-\n builtin/rebase.c                              |   5 +-\n builtin/receive-pack.c                        |  15 +-\n builtin/reflog.c                              |   7 +-\n builtin/remote.c                              |  12 +-\n builtin/repack.c                              |   5 +-\n builtin/reset.c                               |   5 +-\n builtin/send-pack.c                           |   5 +-\n builtin/show-branch.c                         |   8 +-\n builtin/stash.c                               |   5 +-\n builtin/submodule--helper.c                   |   3 +-\n builtin/tag.c                                 |   9 +-\n builtin/var.c                                 |   5 +-\n builtin/worktree.c                            |   5 +-\n bundle-uri.c                                  |   9 +-\n color.c                                       |   8 -\n color.h                                       |   6 +-\n compat/mingw.c                                |   3 +-\n compat/mingw.h                                |   4 +-\n config.c                                      | 538 +++++++-----------\n config.h                                      |  54 +-\n connect.c                                     |   4 +-\n .../coccinelle/config_fn_kvi.pending.cocci    | 146 +++++\n contrib/coccinelle/git_config_number.cocci    |  27 +\n convert.c                                     |   4 +-\n credential.c                                  |   1 +\n delta-islands.c                               |   3 +-\n diff.c                                        |  19 +-\n diff.h                                        |   7 +-\n fetch-pack.c                                  |   5 +-\n fmt-merge-msg.c                               |   7 +-\n fmt-merge-msg.h                               |   3 +-\n fsck.c                                        |  11 +-\n fsck.h                                        |   4 +-\n git-compat-util.h                             |   2 +\n gpg-interface.c                               |   6 +-\n grep.c                                        |   7 +-\n grep.h                                        |   4 +-\n help.c                                        |  10 +-\n http.c                                        |  15 +-\n ident.c                                       |   3 +-\n ident.h                                       |   4 +-\n imap-send.c                                   |   7 +-\n ll-merge.c                                    |   1 +\n ls-refs.c                                     |   2 +-\n mailinfo.c                                    |   5 +-\n notes-utils.c                                 |   3 +-\n notes.c                                       |   3 +-\n pager.c                                       |   5 +-\n pretty.c                                      |   1 +\n promisor-remote.c                             |   4 +-\n remote.c                                      |   7 +-\n revision.c                                    |   3 +-\n scalar.c                                      |   3 +-\n sequencer.c                                   |  28 +-\n setup.c                                       |  17 +-\n submodule-config.c                            |  31 +-\n submodule-config.h                            |   3 +-\n t/helper/test-config.c                        |  21 +-\n t/helper/test-userdiff.c                      |   4 +-\n t/t1300-config.sh                             |  27 +\n trace2.c                                      |   4 +-\n trace2.h                                      |   3 +-\n trace2/tr2_cfg.c                              |  12 +-\n trace2/tr2_sysenv.c                           |   3 +-\n trace2/tr2_tgt.h                              |   4 +-\n trace2/tr2_tgt_event.c                        |   4 +-\n trace2/tr2_tgt_normal.c                       |   4 +-\n trace2/tr2_tgt_perf.c                         |   4 +-\n trailer.c                                     |   2 +\n upload-pack.c                                 |  18 +-\n urlmatch.c                                    |   7 +-\n urlmatch.h                                    |   8 +-\n worktree.c                                    |   2 +-\n xdiff-interface.c                             |   5 +-\n xdiff-interface.h                             |   5 +-\n 103 files changed, 883 insertions(+), 642 deletions(-)\n create mode 100644 contrib/coccinelle/config_fn_kvi.pending.cocci\n create mode 100644 contrib/coccinelle/git_config_number.cocci\n\n\nbase-commit: 9857273be005833c71e2d16ba48e193113e12276\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1497%2Fchooglen%2Fconfig%2Fno-global-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1497/chooglen/config/no-global-v2\nPull-Request: https://github.com/git/git/pull/1497\n\nRange-diff vs v1:\n\n  1:  49bc2f6eedc <  -:  ----------- config.c: introduce kvi_fn(), use it for configsets\n  6:  cb021810688 =  1:  d5edf7e3fdd config: inline git_color_default_config\n  7:  e0f43eafa07 =  2:  821f0b90580 urlmatch.h: use config_fn_t type\n  8:  961d06f89cb !  3:  6834e37066e (RFC-only) config: add kvi arg to config_fn_t\n     @@ Commit message\n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n       ## config.c ##\n     -@@ config.c: static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n     - {\n     - \tint ret;\n     - \tconfig_reader_push_kvi(&the_reader, kvi);\n     --\tret = fn(key, value, data);\n     -+\tret = fn(key, value, kvi, data);\n     - \tconfig_reader_pop_kvi(&the_reader);\n     +@@ config.c: static int git_config_include(const char *var, const char *value, void *data)\n     + \t * Pass along all values, including \"include\" directives; this makes it\n     + \t * possible to query information on the includes themselves.\n     + \t */\n     +-\tret = inc->fn(var, value, inc->data);\n     ++\tret = inc->fn(var, value, NULL, inc->data);\n     + \tif (ret < 0)\n     + \t\treturn ret;\n     + \n     +@@ config.c: static int config_parse_pair(const char *key, const char *value,\n     + \tif (git_config_parse_key(key, &canonical_name, NULL))\n     + \t\treturn -1;\n     + \n     +-\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n     ++\tret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;\n     + \tfree(canonical_name);\n       \treturn ret;\n       }\n     +@@ config.c: static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n     + \t * accurate line number in error messages.\n     + \t */\n     + \tcs->linenr--;\n     +-\tret = fn(name->buf, value, data);\n     ++\tret = fn(name->buf, value, NULL, data);\n     + \tif (ret >= 0)\n     + \t\tcs->linenr++;\n     + \treturn ret;\n     +@@ config.c: static void configset_iter(struct config_reader *reader, struct config_set *set,\n     + \n     + \t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n     + \n     +-\t\tif (fn(entry->key, values->items[value_index].string, data) < 0)\n     ++\t\tif (fn(entry->key, values->items[value_index].string, NULL, data) < 0)\n     + \t\t\tgit_die_config_linenr(entry->key,\n     + \t\t\t\t\t      reader->config_kvi->filename,\n     + \t\t\t\t\t      reader->config_kvi->linenr);\n      \n       ## config.h ##\n      @@ config.h: struct config_options {\n     @@ config.h: struct config_options {\n      +\tint linenr;\n      +\tenum config_origin_type origin_type;\n      +\tenum config_scope scope;\n     -+\tconst char *path;\n     -+\tstruct key_value_info *prev;\n      +};\n      +\n       /**\n     @@ config.h: int git_config_get_expiry(const char *key, const char **output);\n      -\tint linenr;\n      -\tenum config_origin_type origin_type;\n      -\tenum config_scope scope;\n     --\tconst char *path;\n     --\tstruct key_value_info *prev;\n      -};\n      -\n       /**\n  9:  5eb3874b494 !  4:  bd52c6232ec (RFC-only) config: apply cocci to config_fn_t implementations\n     @@ config.c: static void populate_remote_urls(struct config_include_data *inc)\n       \t\t\t     void *data UNUSED)\n       {\n       \tconst char *remote_name;\n     -@@ config.c: static int include_condition_is_true(struct key_value_info *kvi,\n     +@@ config.c: static int include_condition_is_true(struct config_source *cs,\n     + \treturn 0;\n     + }\n       \n     - static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n     - \t\t  struct key_value_info *kvi, void *data);\n      -static int git_config_include(const char *var, const char *value, void *data)\n      +static int git_config_include(const char *var, const char *value,\n      +\t\t\t      struct key_value_info *kvi UNUSED, void *data)\n       {\n       \tstruct config_include_data *inc = data;\n     - \tstruct key_value_info *kvi = inc->config_reader->config_kvi;\n     + \tstruct config_source *cs = inc->config_reader->source;\n      @@ config.c: int git_config_color(char *dest, const char *var, const char *value)\n       \treturn 0;\n       }\n     @@ config.c: struct configset_add_data {\n       {\n       \tstruct configset_add_data *data = cb;\n       \tconfigset_add_value(data->config_reader, data->config_set, key, value);\n     -@@ config.c: static int store_aux_event(enum config_event_t type, size_t begin, size_t end,\n     +@@ config.c: static int store_aux_event(enum config_event_t type,\n       \treturn 0;\n       }\n       \n 10:  1071e70c928 !  5:  f363b160259 (RFC-only) config: finish config_fn_t refactor\n     @@ Commit message\n              refactoring away, since git_xmerge_config() can call\n              git_default_config().\n      \n     -      - config.c:git_config_include()\n     -\n     -        Replace the local \"kvi\" variable with the \"kvi\" parameter. This\n     -        makes config_include_data.config_reader obsolete, so remove it.\n     -\n          * Hard for cocci to catch\n      \n            - urlmatch.c\n     @@ compat/mingw.h: typedef _sigset_t sigset_t;\n       \n       /*\n      \n     - ## config.c ##\n     -@@ config.c: struct config_include_data {\n     - \tvoid *data;\n     - \tconst struct config_options *opts;\n     - \tstruct git_config_source *config_source;\n     --\tstruct config_reader *config_reader;\n     - \n     - \t/*\n     - \t * All remote URLs discovered when reading all config files.\n     -@@ config.c: static int include_condition_is_true(struct key_value_info *kvi,\n     - static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n     - \t\t  struct key_value_info *kvi, void *data);\n     - static int git_config_include(const char *var, const char *value,\n     --\t\t\t      struct key_value_info *kvi UNUSED, void *data)\n     -+\t\t\t      struct key_value_info *kvi, void *data)\n     - {\n     - \tstruct config_include_data *inc = data;\n     --\tstruct key_value_info *kvi = inc->config_reader->config_kvi;\n     - \tconst char *cond, *key;\n     - \tsize_t cond_len;\n     - \tint ret;\n     -@@ config.c: int config_with_options(config_fn_t fn, void *data,\n     - \t\tinc.data = data;\n     - \t\tinc.opts = opts;\n     - \t\tinc.config_source = config_source;\n     --\t\tinc.config_reader = &the_reader;\n     - \t\tfn = git_config_include;\n     - \t\tdata = &inc;\n     - \t}\n     -\n       ## diff.h ##\n      @@ diff.h: void free_diffstat_info(struct diffstat_t *diffstat);\n       int parse_long_opt(const char *opt, const char **argv,\n 11:  b38653477c7 !  6:  f57c1007cad config: remove current_config_(line|name|origin_type)\n     @@ Metadata\n      Author: Glen Choo <chooglen@google.com>\n      \n       ## Commit message ##\n     -    config: remove current_config_(line|name|origin_type)\n     +    config.c: pass kvi in configsets\n      \n     -    Trivially replace current_config_(line|name|origin_type) by reading the\n     -    corresponding values from \"struct key_value_info\". This includes some\n     -    light \"kvi\" plumbing for builtin/config.c, and for *origin_type,\n     -    splitting out a function that turns \"enum config_origin_type\" into the\n     -    human-readable string that callbacks actually want.\n     +    Trivially pass \"struct key_value_info\" to config callbacks in\n     +    configset_iter(). Then, in config callbacks that are only used with\n     +    configsets, use the \"kvi\" arg to replace calls to current_config_*(),\n     +    and delete current_config_line() because it has no remaining callers.\n      \n     -    Signed-off-by: Glen Choo <chooglen@google.com>\n     +    This leaves builtin/config.c and config.c as the only remaining users of\n     +    current_config_*().\n      \n     - ## builtin/config.c ##\n     -@@ builtin/config.c: static void check_argc(int argc, int min, int max)\n     - \tusage_builtin_config();\n     - }\n     - \n     --static void show_config_origin(struct strbuf *buf)\n     -+static void show_config_origin(struct key_value_info *kvi, struct strbuf *buf)\n     - {\n     - \tconst char term = end_nul ? '\\0' : '\\t';\n     - \n     --\tstrbuf_addstr(buf, current_config_origin_type());\n     -+\tstrbuf_addstr(buf, config_origin_type_name(kvi->origin_type));\n     - \tstrbuf_addch(buf, ':');\n     - \tif (end_nul)\n     --\t\tstrbuf_addstr(buf, current_config_name());\n     -+\t\tstrbuf_addstr(buf, kvi->filename ? kvi->filename : \"\");\n     - \telse\n     --\t\tquote_c_style(current_config_name(), buf, NULL, 0);\n     -+\t\tquote_c_style(kvi->filename ? kvi->filename : \"\", buf, NULL, 0);\n     - \tstrbuf_addch(buf, term);\n     - }\n     - \n     -@@ builtin/config.c: static void show_config_scope(struct strbuf *buf)\n     - }\n     - \n     - static int show_all_config(const char *key_, const char *value_,\n     --\t\t\t   struct key_value_info *kvi UNUSED, void *cb UNUSED)\n     -+\t\t\t   struct key_value_info *kvi, void *cb UNUSED)\n     - {\n     - \tif (show_origin || show_scope) {\n     - \t\tstruct strbuf buf = STRBUF_INIT;\n     - \t\tif (show_scope)\n     - \t\t\tshow_config_scope(&buf);\n     - \t\tif (show_origin)\n     --\t\t\tshow_config_origin(&buf);\n     -+\t\t\tshow_config_origin(kvi, &buf);\n     - \t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n     - \t\tfwrite(buf.buf, 1, buf.len, stdout);\n     - \t\tstrbuf_release(&buf);\n     -@@ builtin/config.c: struct strbuf_list {\n     - \tint alloc;\n     - };\n     - \n     --static int format_config(struct strbuf *buf, const char *key_, const char *value_)\n     -+static int format_config(struct strbuf *buf, const char *key_, const char *value_,\n     -+\t\t\t struct key_value_info *kvi)\n     - {\n     - \tif (show_scope)\n     - \t\tshow_config_scope(buf);\n     - \tif (show_origin)\n     --\t\tshow_config_origin(buf);\n     -+\t\tshow_config_origin(kvi, buf);\n     - \tif (show_keys)\n     - \t\tstrbuf_addstr(buf, key_);\n     - \tif (!omit_values) {\n     -@@ builtin/config.c: static int format_config(struct strbuf *buf, const char *key_, const char *value\n     - }\n     - \n     - static int collect_config(const char *key_, const char *value_,\n     --\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n     -+\t\t\t  struct key_value_info *kvi, void *cb)\n     - {\n     - \tstruct strbuf_list *values = cb;\n     - \n     -@@ builtin/config.c: static int collect_config(const char *key_, const char *value_,\n     - \tALLOC_GROW(values->items, values->nr + 1, values->alloc);\n     - \tstrbuf_init(&values->items[values->nr], 0);\n     - \n     --\treturn format_config(&values->items[values->nr++], key_, value_);\n     -+\treturn format_config(&values->items[values->nr++], key_, value_, kvi);\n     - }\n     - \n     - static int get_value(const char *key_, const char *regex_, unsigned flags)\n     -@@ builtin/config.c: static int get_value(const char *key_, const char *regex_, unsigned flags)\n     - \t\tALLOC_GROW(values.items, values.nr + 1, values.alloc);\n     - \t\titem = &values.items[values.nr++];\n     - \t\tstrbuf_init(item, 0);\n     --\t\tif (format_config(item, key_, default_value) < 0)\n     -+\t\tif (format_config(item, key_, default_value, NULL) < 0)\n     - \t\t\tdie(_(\"failed to format default config value: %s\"),\n     - \t\t\t\tdefault_value);\n     - \t}\n     -@@ builtin/config.c: static int get_urlmatch(const char *var, const char *url)\n     - \t\tstruct strbuf buf = STRBUF_INIT;\n     - \n     - \t\tformat_config(&buf, item->string,\n     --\t\t\t      matched->value_is_null ? NULL : matched->value.buf);\n     -+\t\t\t      matched->value_is_null ? NULL : matched->value.buf, NULL);\n     - \t\tfwrite(buf.buf, 1, buf.len, stdout);\n     - \t\tstrbuf_release(&buf);\n     - \n     +    Signed-off-by: Glen Choo <chooglen@google.com>\n      \n       ## builtin/remote.c ##\n      @@ builtin/remote.c: struct push_default_info\n     @@ builtin/remote.c: struct push_default_info\n       {\n       \tstruct push_default_info* info = cb;\n       \tif (strcmp(key, \"remote.pushdefault\") ||\n     -@@ builtin/remote.c: static int config_read_push_default(const char *key, const char *value,\n     + \t    !value || strcmp(value, info->old_name))\n     + \t\treturn 0;\n       \n     - \tinfo->scope = current_config_scope();\n     +-\tinfo->scope = current_config_scope();\n     ++\tinfo->scope = kvi->scope;\n       \tstrbuf_reset(&info->origin);\n      -\tstrbuf_addstr(&info->origin, current_config_name());\n      -\tinfo->linenr = current_config_line();\n     -+\tstrbuf_addstr(&info->origin, kvi->filename);\n     ++\tstrbuf_addstr(&info->origin, config_origin_type_name(kvi->origin_type));\n      +\tinfo->linenr = kvi->linenr;\n       \n       \treturn 0;\n       }\n      \n       ## config.c ##\n     +@@ config.c: static void configset_iter(struct config_reader *reader, struct config_set *set,\n     + \tstruct string_list *values;\n     + \tstruct config_set_element *entry;\n     + \tstruct configset_list *list = &set->list;\n     ++\tstruct key_value_info *kvi;\n     + \n     + \tfor (i = 0; i < list->nr; i++) {\n     + \t\tentry = list->items[i].e;\n     + \t\tvalue_index = list->items[i].value_index;\n     + \t\tvalues = &entry->value_list;\n     ++\t\tkvi = values->items[value_index].util;\n     + \n     + \t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n     + \n     +-\t\tif (fn(entry->key, values->items[value_index].string, NULL, data) < 0)\n     +-\t\t\tgit_die_config_linenr(entry->key,\n     +-\t\t\t\t\t      reader->config_kvi->filename,\n     +-\t\t\t\t\t      reader->config_kvi->linenr);\n     +-\n     ++\t\tif (fn(entry->key, values->items[value_index].string, kvi, data) < 0)\n     ++\t\t\tgit_die_config_linenr(entry->key, kvi->filename, kvi->linenr);\n     + \t\tconfig_reader_set_kvi(reader, NULL);\n     + \t}\n     + }\n      @@ config.c: static int reader_origin_type(struct config_reader *reader,\n       \treturn 0;\n       }\n     @@ config.c: static int reader_origin_type(struct config_reader *reader,\n       \tswitch (type) {\n       \tcase CONFIG_ORIGIN_BLOB:\n       \t\treturn \"blob\";\n     -@@ config.c: static int reader_config_name(struct config_reader *reader, const char **out)\n     - \treturn 0;\n     +@@ config.c: const char *current_config_origin_type(void)\n     + \t}\n       }\n       \n     --const char *current_config_name(void)\n     --{\n     --\tconst char *name;\n     --\tif (reader_config_name(&the_reader, &name))\n     --\t\tBUG(\"current_config_name called outside config callback\");\n     --\treturn name ? name : \"\";\n     --}\n     --\n     - enum config_scope current_config_scope(void)\n     ++const char *current_config_origin_type(void)\n     ++{\n     ++\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n     ++\n     ++\tif (reader_origin_type(&the_reader, &type))\n     ++\t\tBUG(\"current_config_origin_type called outside config callback\");\n     ++\n     ++\treturn config_origin_type_name(type);\n     ++}\n     ++\n     + const char *config_scope_name(enum config_scope scope)\n       {\n     - \tif (the_reader.config_kvi)\n     + \tswitch (scope) {\n      @@ config.c: enum config_scope current_config_scope(void)\n     - \t\treturn CONFIG_SCOPE_UNKNOWN;\n     + \t\treturn the_reader.parsing_scope;\n       }\n       \n      -int current_config_line(void)\n     @@ config.c: enum config_scope current_config_scope(void)\n      -\tif (the_reader.config_kvi)\n      -\t\treturn the_reader.config_kvi->linenr;\n      -\telse\n     --\t\tBUG(\"current_config_line called outside config callback\");\n     +-\t\treturn the_reader.source->linenr;\n      -}\n      -\n       int lookup_config(const char **mapping, int nr_mapping, const char *var)\n     @@ config.c: enum config_scope current_config_scope(void)\n       \tint i;\n      \n       ## config.h ##\n     -@@ config.h: void git_global_config(char **user, char **xdg);\n     - int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n     - \n     +@@ config.h: int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n       enum config_scope current_config_scope(void);\n     --const char *current_config_origin_type(void);\n     --const char *current_config_name(void);\n     + const char *current_config_origin_type(void);\n     + const char *current_config_name(void);\n      -int current_config_line(void);\n      +const char *config_origin_type_name(enum config_origin_type type);\n       \n       /*\n        * Match and parse a config key of the form:\n      \n     + ## remote.c ##\n     +@@ remote.c: static void read_branches_file(struct remote_state *remote_state,\n     + }\n     + \n     + static int handle_config(const char *key, const char *value,\n     +-\t\t\t struct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t struct key_value_info *kvi, void *cb)\n     + {\n     + \tconst char *name;\n     + \tsize_t namelen;\n     +@@ remote.c: static int handle_config(const char *key, const char *value,\n     + \t}\n     + \tremote = make_remote(remote_state, name, namelen);\n     + \tremote->origin = REMOTE_CONFIG;\n     +-\tif (current_config_scope() == CONFIG_SCOPE_LOCAL ||\n     +-\t    current_config_scope() == CONFIG_SCOPE_WORKTREE)\n     ++\tif (kvi->scope == CONFIG_SCOPE_LOCAL ||\n     ++\t    kvi->scope == CONFIG_SCOPE_WORKTREE)\n     + \t\tremote->configured_in_repo = 1;\n     + \tif (!strcmp(subkey, \"mirror\"))\n     + \t\tremote->mirror = git_config_bool(key, value);\n     +\n       ## t/helper/test-config.c ##\n      @@\n        */\n     @@ t/helper/test-config.c: static int iterate_cb(const char *var, const char *value\n      -\tprintf(\"origin=%s\\n\", current_config_origin_type());\n      -\tprintf(\"name=%s\\n\", current_config_name());\n      -\tprintf(\"lno=%d\\n\", current_config_line());\n     +-\tprintf(\"scope=%s\\n\", config_scope_name(current_config_scope()));\n      +\tprintf(\"origin=%s\\n\", config_origin_type_name(kvi->origin_type));\n      +\tprintf(\"name=%s\\n\", kvi->filename ? kvi->filename : \"\");\n      +\tprintf(\"lno=%d\\n\", kvi->linenr);\n     - \tprintf(\"scope=%s\\n\", config_scope_name(current_config_scope()));\n     ++\tprintf(\"scope=%s\\n\", config_scope_name(kvi->scope));\n       \n       \treturn 0;\n     + }\n  3:  8c7a84137c8 !  7:  641a56f0b40 config: use kvi for config files\n     @@ Metadata\n      Author: Glen Choo <chooglen@google.com>\n      \n       ## Commit message ##\n     -    config: use kvi for config files\n     +    config: provide kvi with config files\n      \n     -    Plumb \"struct key_value_info\" and use \"kvi_fn()\" when parsing config\n     -    files. As a result, \"config_reader.kvi\" is now always set correctly, so\n     -    we can remove \"config_reader.scope\" (but not the \".source\" member since\n     -    that's still needed by some non-parsing machinery). This requires\n     -    plumbing an additional \"enum config_scope\" arg through\n     -    \"git_config_from_file_with_options()\" and the underlying machinery to\n     -    make up for the fact that \"struct key_value_info\" has a \".scope\" member,\n     -    but \"struct config_source\" does not.\n     +    Refactor out the configset logic that caches \"struct config_source\" and\n     +    \"enum config_scope\" as a \"struct key_value_info\", and use it to pass the\n     +    \"kvi\" arg to config callbacks when parsing config files. Get the \"enum\n     +    config_scope\" value by plumbing an additional arg through\n     +    git_config_from_file_with_options() and the underlying machinery.\n      \n     -    To handle \"include\" directives correctly, use push/pop semantics for\n     -    \"config_reader.config_kvi\" (instead of \"set\" semantics) like we do for\n     -    \"config_reader.source\". Otherwise, \"config_reader.config_kvi\" won't be\n     -    set correctly when we finish parsing an included config file and we want\n     -    to \"pop\" it to resume parsing the original file. This distinction only\n     -    matters while there is a global \"kvi\", i.e. it will be obsolete at the\n     -    end of the series.\n     +    We do not exercise the \"kvi\" arg yet because the remaining\n     +    current_config_*() callers may be used with config_with_options(), which\n     +    may read config from parameters, but parameters don't pass \"kvi\" yet.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n     @@ bundle-uri.c: int bundle_uri_parse_config_format(const char *uri,\n       \tif (!result && list->mode == BUNDLE_MODE_NONE) {\n      \n       ## config.c ##\n     -@@ config.c: struct config_reader {\n     - \t */\n     - \tstruct config_source *source;\n     - \tstruct key_value_info *config_kvi;\n     --\t/*\n     --\t * The \"scope\" of the current config source being parsed (repo, global,\n     --\t * etc). Like \"source\", this is only set when parsing a config source.\n     --\t * It's not part of \"source\" because it transcends a single file (i.e.,\n     --\t * a file included from .git/config is still in \"repo\" scope).\n     --\t *\n     --\t * When iterating through a configset, the equivalent value is\n     --\t * \"config_kvi.scope\" (see above).\n     --\t */\n     --\tenum config_scope parsing_scope;\n     - };\n     - /*\n     -  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n     -@@ config.c: static inline struct config_source *config_reader_pop_source(struct config_reade\n     - \treturn ret;\n     - }\n     - \n     --static inline void config_reader_set_kvi(struct config_reader *reader,\n     --\t\t\t\t\t struct key_value_info *kvi)\n     -+static inline void config_reader_push_kvi(struct config_reader *reader,\n     -+\t\t\t\t\t  struct key_value_info *kvi)\n     - {\n     -+\tkvi->prev = reader->config_kvi;\n     - \treader->config_kvi = kvi;\n     - }\n     - \n     --static inline void config_reader_set_scope(struct config_reader *reader,\n     --\t\t\t\t\t   enum config_scope scope)\n     -+static inline struct key_value_info *config_reader_pop_kvi(struct config_reader *reader)\n     - {\n     --\treader->parsing_scope = scope;\n     -+\tstruct key_value_info *ret;\n     -+\tif (!reader->config_kvi)\n     -+\t\tBUG(\"tried to pop config_kvi, but we weren't reading config\");\n     -+\tret = reader->config_kvi;\n     -+\treader->config_kvi = reader->config_kvi->prev;\n     -+\treturn ret;\n     - }\n     - \n     - static int pack_compression_seen;\n      @@ config.c: static int handle_path_include(struct config_source *cs, const char *path,\n       \t\t\t    !cs ? \"<unknown>\" :\n       \t\t\t    cs->name ? cs->name :\n     @@ config.c: static int handle_path_include(struct config_source *cs, const char *p\n       \t\tinc->depth--;\n       \t}\n       cleanup:\n     -@@ config.c: static void populate_remote_urls(struct config_include_data *inc)\n     - {\n     - \tstruct config_options opts;\n     - \n     --\tenum config_scope store_scope = inc->config_reader->parsing_scope;\n     --\n     - \topts = *inc->opts;\n     - \topts.unconditional_remote_url = 1;\n     - \n     --\tconfig_reader_set_scope(inc->config_reader, 0);\n     --\n     - \tinc->remote_urls = xmalloc(sizeof(*inc->remote_urls));\n     - \tstring_list_init_dup(inc->remote_urls);\n     - \tconfig_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);\n     --\n     --\tconfig_reader_set_scope(inc->config_reader, store_scope);\n     - }\n     - \n     - static int forbid_remote_url(const char *var, const char *value UNUSED,\n      @@ config.c: static int include_condition_is_true(struct config_source *cs,\n     - \treturn 0;\n       }\n       \n     -+static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n     -+\t\t  struct key_value_info *kvi, void *data);\n     - static int git_config_include(const char *var, const char *value, void *data)\n     + static int git_config_include(const char *var, const char *value,\n     +-\t\t\t      struct key_value_info *kvi UNUSED, void *data)\n     ++\t\t\t      struct key_value_info *kvi, void *data)\n       {\n       \tstruct config_include_data *inc = data;\n       \tstruct config_source *cs = inc->config_reader->source;\n     -+\tstruct key_value_info *kvi = inc->config_reader->config_kvi;\n     - \tconst char *cond, *key;\n     - \tsize_t cond_len;\n     - \tint ret;\n     -@@ config.c: static int git_config_include(const char *var, const char *value, void *data)\n     +@@ config.c: static int git_config_include(const char *var, const char *value,\n       \t * Pass along all values, including \"include\" directives; this makes it\n       \t * possible to query information on the includes themselves.\n       \t */\n     --\tret = inc->fn(var, value, inc->data);\n     -+\tret = kvi_fn(inc->fn, var, value, kvi, inc->data);\n     +-\tret = inc->fn(var, value, NULL, inc->data);\n     ++\tret = inc->fn(var, value, kvi, inc->data);\n       \tif (ret < 0)\n       \t\treturn ret;\n       \n     -@@ config.c: out_free_ret_1:\n     - }\n     - \n     - static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n     --\t\t  struct key_value_info *kvi,\n     --\t\t  void *data)\n     -+\t\t  struct key_value_info *kvi, void *data)\n     - {\n     - \tint ret;\n     --\tconfig_reader_set_kvi(&the_reader, kvi);\n     -+\tconfig_reader_push_kvi(&the_reader, kvi);\n     - \tret = fn(key, value, data);\n     --\tconfig_reader_set_kvi(&the_reader, NULL);\n     -+\tconfig_reader_pop_kvi(&the_reader);\n     - \treturn ret;\n     - }\n     - \n      @@ config.c: static char *parse_value(struct config_source *cs)\n       \t}\n       }\n     @@ config.c: static int get_value(struct config_source *cs, config_fn_t fn, void *d\n       \t * accurate line number in error messages.\n       \t */\n       \tcs->linenr--;\n     --\tret = fn(name->buf, value, data);\n     +-\tret = fn(name->buf, value, NULL, data);\n      +\tkvi->linenr = cs->linenr;\n     -+\tret = kvi_fn(fn, name->buf, value, kvi, data);\n     ++\tret = fn(name->buf, value, kvi, data);\n       \tif (ret >= 0)\n       \t\tcs->linenr++;\n       \treturn ret;\n     @@ config.c: static int git_parse_source(struct config_source *cs, config_fn_t fn,\n       \t\t\tbreak;\n       \t}\n       \n     -@@ config.c: int git_default_config(const char *var, const char *value, void *cb)\n     +@@ config.c: int git_default_config(const char *var, const char *value,\n        * this function.\n        */\n       static int do_config_from(struct config_reader *reader,\n     @@ config.c: static int do_config_from_file(struct config_reader *reader,\n      -\tret = do_config_from(reader, &top, fn, data, opts);\n      +\tret = do_config_from(reader, &top, fn, data, scope, opts);\n       \tfunlockfile(f);\n     -+\n       \treturn ret;\n       }\n       \n     @@ config.c: int git_config_from_blob_oid(config_fn_t fn,\n       }\n       \n       char *git_system_config(void)\n     -@@ config.c: int git_config_system(void)\n     - \treturn !git_env_bool(\"GIT_CONFIG_NOSYSTEM\", 0);\n     - }\n     - \n     --static int do_git_config_sequence(struct config_reader *reader,\n     --\t\t\t\t  const struct config_options *opts,\n     -+static int do_git_config_sequence(const struct config_options *opts,\n     - \t\t\t\t  config_fn_t fn, void *data)\n     - {\n     - \tint ret = 0;\n      @@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n     - \tchar *xdg_config = NULL;\n     - \tchar *user_config = NULL;\n     - \tchar *repo_config;\n     --\tenum config_scope prev_parsing_scope = reader->parsing_scope;\n     - \n     - \tif (opts->commondir)\n     - \t\trepo_config = mkpathdup(\"%s/config\", opts->commondir);\n     -@@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n     - \telse\n     - \t\trepo_config = NULL;\n     - \n     --\tconfig_reader_set_scope(reader, CONFIG_SCOPE_SYSTEM);\n       \tif (git_config_system() && system_config &&\n       \t    !access_or_die(system_config, R_OK,\n       \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n     @@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n      +\t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n      +\t\t\t\t\t\t\t NULL);\n       \n     --\tconfig_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);\n     + \tconfig_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);\n       \tgit_global_config(&user_config, &xdg_config);\n       \n       \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n     @@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n      +\t\tret += git_config_from_file_with_options(fn, user_config, data,\n      +\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n       \n     --\tconfig_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);\n     + \tconfig_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);\n       \tif (!opts->ignore_repo && repo_config &&\n       \t    !access_or_die(repo_config, R_OK, 0))\n      -\t\tret += git_config_from_file(fn, repo_config, data);\n      +\t\tret += git_config_from_file_with_options(fn, repo_config, data,\n      +\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n       \n     --\tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n     + \tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n       \tif (!opts->ignore_worktree && repository_format_worktree_config) {\n       \t\tchar *path = git_pathdup(\"config.worktree\");\n       \t\tif (!access_or_die(path, R_OK, 0))\n     @@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n       \t\tfree(path);\n       \t}\n       \n     --\tconfig_reader_set_scope(reader, 0);\n     - \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n     - \t\tdie(_(\"unable to parse command-line config\"));\n     - \n     --\tconfig_reader_set_scope(reader, prev_parsing_scope);\n     - \tfree(system_config);\n     - \tfree(xdg_config);\n     - \tfree(user_config);\n     -@@ config.c: int config_with_options(config_fn_t fn, void *data,\n     - \t\t\tconst struct config_options *opts)\n     - {\n     - \tstruct config_include_data inc = CONFIG_INCLUDE_INIT;\n     --\tenum config_scope prev_scope = the_reader.parsing_scope;\n     - \tint ret;\n     - \n     - \tif (opts->respect_includes) {\n      @@ config.c: int config_with_options(config_fn_t fn, void *data,\n     - \t\tdata = &inc;\n     - \t}\n     - \n     --\tif (config_source)\n     --\t\tconfig_reader_set_scope(&the_reader, config_source->scope);\n     --\n     - \t/*\n     - \t * If we have a specific filename, use it. Otherwise, follow the\n       \t * regular lookup sequence.\n       \t */\n       \tif (config_source && config_source->use_stdin) {\n     @@ config.c: int config_with_options(config_fn_t fn, void *data,\n      -\t\t\t\t\t\tdata);\n      +\t\t\t\t\t       data, config_source->scope);\n       \t} else {\n     --\t\tret = do_git_config_sequence(&the_reader, opts, fn, data);\n     -+\t\tret = do_git_config_sequence(opts, fn, data);\n     - \t}\n     - \n     - \tif (inc.remote_urls) {\n     - \t\tstring_list_clear(inc.remote_urls, 0);\n     - \t\tFREE_AND_NULL(inc.remote_urls);\n     + \t\tret = do_git_config_sequence(&the_reader, opts, fn, data);\n       \t}\n     --\tconfig_reader_set_scope(&the_reader, prev_scope);\n     - \treturn ret;\n     - }\n     - \n      @@ config.c: static int configset_add_value(struct config_reader *reader,\n     - \tl_item->e = e;\n     - \tl_item->value_index = e->value_list.nr - 1;\n     - \n     --\tif (reader->source && reader->source->name) {\n     + \tif (!reader->source)\n     + \t\tBUG(\"configset_add_value has no source\");\n     + \tif (reader->source->name) {\n      -\t\tkv_info->filename = strintern(reader->source->name);\n      -\t\tkv_info->linenr = reader->source->linenr;\n      -\t\tkv_info->origin_type = reader->source->origin_type;\n     --\t\tkv_info->scope = reader->parsing_scope;\n     --\t} else\n     --\t\tkvi_from_param(kv_info);\n     -+\tmemcpy(kv_info, reader->config_kvi, sizeof(struct key_value_info));\n     ++\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n     + \t} else {\n     + \t\t/* for values read from `git_config_from_parameters()` */\n     + \t\tkv_info->filename = NULL;\n     + \t\tkv_info->linenr = -1;\n     + \t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n     ++\t\tkv_info->scope = reader->parsing_scope;\n     + \t}\n     +-\tkv_info->scope = reader->parsing_scope;\n       \tsi->util = kv_info;\n       \n       \treturn 0;\n     @@ config.c: int git_config_set_multivar_in_file_gently(const char *config_filename\n       \t\t\terror(_(\"invalid config file %s\"), config_filename);\n       \t\t\tret = CONFIG_INVALID_FILE;\n       \t\t\tgoto out_free;\n     -@@ config.c: enum config_scope current_config_scope(void)\n     - \tif (the_reader.config_kvi)\n     - \t\treturn the_reader.config_kvi->scope;\n     - \telse\n     --\t\treturn the_reader.parsing_scope;\n     -+\t\t/*\n     -+\t\t * FIXME This should be a BUG, but tr2_list_env_vars_fl is\n     -+\t\t * calling this outside of a config callback. This will be\n     -+\t\t * easier to fix when we plumb kvi through the config callbacks,\n     -+\t\t * so leave this untouched for now.\n     -+\t\t */\n     -+\t\treturn CONFIG_SCOPE_UNKNOWN;\n     - }\n     - \n     - int current_config_line(void)\n      \n       ## config.h ##\n     -@@ config.h: int git_default_config(const char *, const char *, void *);\n     +@@ config.h: int git_default_config(const char *, const char *, struct key_value_info *,\n       int git_config_from_file(config_fn_t fn, const char *, void *);\n       \n       int git_config_from_file_with_options(config_fn_t fn, const char *,\n     @@ config.h: int git_default_config(const char *, const char *, void *);\n       void git_config_push_parameter(const char *text);\n       void git_config_push_env(const char *spec);\n       int git_config_from_parameters(config_fn_t fn, void *data);\n     -@@ config.h: struct key_value_info {\n     - \tint linenr;\n     - \tenum config_origin_type origin_type;\n     - \tenum config_scope scope;\n     -+\tstruct key_value_info *prev;\n     - };\n     - \n     - /**\n      \n       ## fsck.c ##\n      @@ fsck.c: static int fsck_blob(const struct object_id *oid, const char *buf,\n  -:  ----------- >  8:  74f43fc727e builtin/config.c: test misuse of format_config()\n  2:  a682612cff2 !  9:  3760015d2c0 config.c: use kvi for CLI config\n     @@ Metadata\n      Author: Glen Choo <chooglen@google.com>\n      \n       ## Commit message ##\n     -    config.c: use kvi for CLI config\n     +    config.c: provide kvi with CLI config\n      \n     -    Plumb \"struct key_value_info\" and use \"kvi_fn()\" when parsing CLI\n     -    config. Do this by refactoring out and reusing the logic that sets the\n     -    \"struct key_value_info\" members when caching CLI config in a configset.\n     +    Refactor out kvi_from_param() from the logic that caches CLI config in\n     +    configsets, and use it to pass the \"kvi\" arg to config callbacks when\n     +    parsing CLI config. Now that \"kvi\" is always present when config\n     +    machinery calls config callbacks, plumb \"kvi\" so that we can replace\n     +    nearly all calls to current_config_*(). (The exception is an edge case\n     +    where trace2/*.c calls current_config_scope(). That will be handled in a\n     +    later commit.) Note that this results in \"kvi\" containing a different,\n     +    more complete set of information than the mocked up \"struct\n     +    config_source\" in git_config_from_parameters().\n      \n     -    This lets us get rid of the fake \"struct config_source\" in\n     -    \"git_config_from_parameters()\", so we now only have to maintain one\n     -    implementation. Additionally, this plumbing also reveals that\n     -    \"git_config_parse_parameter()\" hasn't been setting either\n     -    \"the_reader.source\" or \"the_reader.config_kvi\", so any calls to\n     -    \"current_*\" would either BUG() or return *_UNKNOWN values.\n     +    Plumbing \"kvi\" reveals a few places where we've been doing the wrong\n     +    thing:\n      \n     -    Also, get rid of the BUG() checks that forbid setting \".config_kvi\" and\n     -    \".source\" at the same time, since we will run afoul of that check. They\n     -    will soon be unnecessary when we remove \".source\".\n     +    * git_config_parse_parameter() hasn't been setting config source\n     +      information, so plumb \"kvi\" there too.\n     +\n     +    * \"git config --get-urlmatch --show-scope\" iterates config to collect\n     +      values, but then attempts to display the scope after config iteration.\n     +      Fix this by copying the \"kvi\" arg in the collection phase so that it\n     +      can be read back later. This means that we can now support \"git config\n     +      --get-urlmatch --show-origin\" (we don't allow this combination of args\n     +      because of this bug), but that is left unchanged for now.\n     +\n     +    * \"git config --default\" doesn't have config source metadata when\n     +      displaying the default value. Fix this by treating the default value\n     +      as if it came from the command line (e.g. like we do with \"git -c\" or\n     +      \"git config --file\"), using kvi_from_param().\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n     - ## config.c ##\n     -@@ config.c: static struct config_reader the_reader;\n     - static inline void config_reader_push_source(struct config_reader *reader,\n     - \t\t\t\t\t     struct config_source *top)\n     + ## builtin/config.c ##\n     +@@ builtin/config.c: static void check_argc(int argc, int min, int max)\n     + \tusage_builtin_config();\n     + }\n     + \n     +-static void show_config_origin(struct strbuf *buf)\n     ++static void show_config_origin(struct key_value_info *kvi, struct strbuf *buf)\n       {\n     --\tif (reader->config_kvi)\n     --\t\tBUG(\"source should not be set while iterating a config set\");\n     - \ttop->prev = reader->source;\n     - \treader->source = top;\n     + \tconst char term = end_nul ? '\\0' : '\\t';\n     + \n     +-\tstrbuf_addstr(buf, current_config_origin_type());\n     ++\tstrbuf_addstr(buf, config_origin_type_name(kvi->origin_type));\n     + \tstrbuf_addch(buf, ':');\n     + \tif (end_nul)\n     +-\t\tstrbuf_addstr(buf, current_config_name());\n     ++\t\tstrbuf_addstr(buf, kvi->filename ? kvi->filename : \"\");\n     + \telse\n     +-\t\tquote_c_style(current_config_name(), buf, NULL, 0);\n     ++\t\tquote_c_style(kvi->filename ? kvi->filename : \"\", buf, NULL, 0);\n     + \tstrbuf_addch(buf, term);\n       }\n     -@@ config.c: static inline struct config_source *config_reader_pop_source(struct config_reade\n     - static inline void config_reader_set_kvi(struct config_reader *reader,\n     - \t\t\t\t\t struct key_value_info *kvi)\n     + \n     +-static void show_config_scope(struct strbuf *buf)\n     ++static void show_config_scope(struct key_value_info *kvi, struct strbuf *buf)\n       {\n     --\tif (kvi && (reader->source || reader->parsing_scope))\n     --\t\tBUG(\"kvi should not be set while parsing a config source\");\n     - \treader->config_kvi = kvi;\n     + \tconst char term = end_nul ? '\\0' : '\\t';\n     +-\tconst char *scope = config_scope_name(current_config_scope());\n     ++\tconst char *scope = config_scope_name(kvi->scope);\n     + \n     + \tstrbuf_addstr(buf, N_(scope));\n     + \tstrbuf_addch(buf, term);\n       }\n       \n     - static inline void config_reader_set_scope(struct config_reader *reader,\n     - \t\t\t\t\t   enum config_scope scope)\n     + static int show_all_config(const char *key_, const char *value_,\n     +-\t\t\t   struct key_value_info *kvi UNUSED, void *cb UNUSED)\n     ++\t\t\t   struct key_value_info *kvi, void *cb UNUSED)\n     + {\n     + \tif (show_origin || show_scope) {\n     + \t\tstruct strbuf buf = STRBUF_INIT;\n     + \t\tif (show_scope)\n     +-\t\t\tshow_config_scope(&buf);\n     ++\t\t\tshow_config_scope(kvi, &buf);\n     + \t\tif (show_origin)\n     +-\t\t\tshow_config_origin(&buf);\n     ++\t\t\tshow_config_origin(kvi, &buf);\n     + \t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n     + \t\tfwrite(buf.buf, 1, buf.len, stdout);\n     + \t\tstrbuf_release(&buf);\n     +@@ builtin/config.c: struct strbuf_list {\n     + \tint alloc;\n     + };\n     + \n     +-static int format_config(struct strbuf *buf, const char *key_, const char *value_)\n     ++static int format_config(struct strbuf *buf, const char *key_,\n     ++\t\t\t const char *value_, struct key_value_info *kvi)\n       {\n     --\tif (scope && reader->config_kvi)\n     --\t\tBUG(\"scope should only be set when iterating through a config source\");\n     - \treader->parsing_scope = scope;\n     + \tif (show_scope)\n     +-\t\tshow_config_scope(buf);\n     ++\t\tshow_config_scope(kvi, buf);\n     + \tif (show_origin)\n     +-\t\tshow_config_origin(buf);\n     ++\t\tshow_config_origin(kvi, buf);\n     + \tif (show_keys)\n     + \t\tstrbuf_addstr(buf, key_);\n     + \tif (!omit_values) {\n     +@@ builtin/config.c: static int format_config(struct strbuf *buf, const char *key_, const char *value\n       }\n       \n     -@@ config.c: static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n     + static int collect_config(const char *key_, const char *value_,\n     +-\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t  struct key_value_info *kvi, void *cb)\n     + {\n     + \tstruct strbuf_list *values = cb;\n     + \n     +@@ builtin/config.c: static int collect_config(const char *key_, const char *value_,\n     + \tALLOC_GROW(values->items, values->nr + 1, values->alloc);\n     + \tstrbuf_init(&values->items[values->nr], 0);\n     + \n     +-\treturn format_config(&values->items[values->nr++], key_, value_);\n     ++\treturn format_config(&values->items[values->nr++], key_, value_, kvi);\n     + }\n     + \n     + static int get_value(const char *key_, const char *regex_, unsigned flags)\n     +@@ builtin/config.c: static int get_value(const char *key_, const char *regex_, unsigned flags)\n     + \t\t\t    &given_config_source, &config_options);\n     + \n     + \tif (!values.nr && default_value) {\n     ++\t\tstruct key_value_info kvi = { 0 };\n     + \t\tstruct strbuf *item;\n     ++\n     ++\t\tkvi_from_param(&kvi);\n     + \t\tALLOC_GROW(values.items, values.nr + 1, values.alloc);\n     + \t\titem = &values.items[values.nr++];\n     + \t\tstrbuf_init(item, 0);\n     +-\t\tif (format_config(item, key_, default_value) < 0)\n     ++\t\tif (format_config(item, key_, default_value, &kvi) < 0)\n     + \t\t\tdie(_(\"failed to format default config value: %s\"),\n     + \t\t\t\tdefault_value);\n     + \t}\n     +@@ builtin/config.c: static void check_write(void)\n     + struct urlmatch_current_candidate_value {\n     + \tchar value_is_null;\n     + \tstruct strbuf value;\n     ++\tstruct key_value_info kvi;\n     + };\n     + \n     + static int urlmatch_collect_fn(const char *var, const char *value,\n     +-\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t       struct key_value_info *kvi, void *cb)\n     + {\n     + \tstruct string_list *values = cb;\n     + \tstruct string_list_item *item = string_list_insert(values, var);\n     +@@ builtin/config.c: static int urlmatch_collect_fn(const char *var, const char *value,\n     + \t} else {\n     + \t\tstrbuf_reset(&matched->value);\n     + \t}\n     ++\tmemcpy(&matched->kvi, kvi, sizeof(struct key_value_info));\n     + \n     + \tif (value) {\n     + \t\tstrbuf_addstr(&matched->value, value);\n     +@@ builtin/config.c: static int get_urlmatch(const char *var, const char *url)\n     + \t\tstruct strbuf buf = STRBUF_INIT;\n     + \n     + \t\tformat_config(&buf, item->string,\n     +-\t\t\t      matched->value_is_null ? NULL : matched->value.buf);\n     ++\t\t\t      matched->value_is_null ? NULL : matched->value.buf,\n     ++\t\t\t      &matched->kvi);\n     + \t\tfwrite(buf.buf, 1, buf.len, stdout);\n     + \t\tstrbuf_release(&buf);\n     + \n     +\n     + ## config.c ##\n     +@@ config.c: static const char include_depth_advice[] = N_(\n     + \"from\\n\"\n     + \"\t%s\\n\"\n     + \"This might be due to circular includes.\");\n     +-static int handle_path_include(struct config_source *cs, const char *path,\n     ++static int handle_path_include(struct config_source *cs,\n     ++\t\t\t       struct key_value_info *kvi,\n     ++\t\t\t       const char *path,\n     + \t\t\t       struct config_include_data *inc)\n     + {\n     + \tint ret = 0;\n     +@@ config.c: static int handle_path_include(struct config_source *cs, const char *path,\n     + \t\t\t    cs->name ? cs->name :\n     + \t\t\t    \"the command line\");\n     + \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n     +-\t\t\t\t\t\t\tcurrent_config_scope(),\n     +-\t\t\t\t\t\t\tNULL);\n     ++\t\t\t\t\t\t\tkvi->scope, NULL);\n     + \t\tinc->depth--;\n     + \t}\n     + cleanup:\n     +@@ config.c: static int git_config_include(const char *var, const char *value,\n     + \t\treturn ret;\n     + \n     + \tif (!strcmp(var, \"include.path\"))\n     +-\t\tret = handle_path_include(cs, value, inc);\n     ++\t\tret = handle_path_include(cs, kvi, value, inc);\n     + \n     + \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n     + \t    cond && include_condition_is_true(cs, inc, cond, cond_len) &&\n     +@@ config.c: static int git_config_include(const char *var, const char *value,\n     + \n     + \t\tif (inc->opts->unconditional_remote_url)\n     + \t\t\tinc->fn = forbid_remote_url;\n     +-\t\tret = handle_path_include(cs, value, inc);\n     ++\t\tret = handle_path_include(cs, kvi, value, inc);\n     + \t\tinc->fn = old_fn;\n     + \t}\n     + \n     +@@ config.c: out_free_ret_1:\n       }\n       \n       static int config_parse_pair(const char *key, const char *value,\n     @@ config.c: static int config_parse_pair(const char *key, const char *value,\n       \tif (git_config_parse_key(key, &canonical_name, NULL))\n       \t\treturn -1;\n       \n     --\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n     -+\tret = (kvi_fn(fn, canonical_name, value, kvi, data) < 0) ? -1 : 0;\n     +-\tret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;\n     ++\tret = (fn(canonical_name, value, kvi, data) < 0) ? -1 : 0;\n       \tfree(canonical_name);\n       \treturn ret;\n       }\n       \n      +\n      +/* for values read from `git_config_from_parameters()` */\n     -+static void kvi_from_param(struct key_value_info *out)\n     ++void kvi_from_param(struct key_value_info *out)\n      +{\n      +\tout->filename = NULL;\n      +\tout->linenr = -1;\n     @@ config.c: static int parse_config_env_list(char *env, config_fn_t fn, void *data\n       \t\t}\n       \t\telse {\n      @@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)\n     - \tstruct strvec to_free = STRVEC_INIT;\n       \tint ret = 0;\n       \tchar *envw = NULL;\n     --\tstruct config_source source = CONFIG_SOURCE_INIT;\n     --\n     --\tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n     --\tconfig_reader_push_source(&the_reader, &source);\n     + \tstruct config_source source = CONFIG_SOURCE_INIT;\n      +\tstruct key_value_info kvi = { 0 };\n       \n     + \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n     + \tconfig_reader_push_source(&the_reader, &source);\n     + \n      +\tkvi_from_param(&kvi);\n     ++\n       \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n       \tif (env) {\n       \t\tunsigned long count;\n     @@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)\n       \t\t\tret = -1;\n       \t\t\tgoto out;\n       \t\t}\n     -@@ config.c: out:\n     - \tstrbuf_release(&envvar);\n     - \tstrvec_clear(&to_free);\n     - \tfree(envw);\n     --\tconfig_reader_pop_source(&the_reader);\n     - \treturn ret;\n     +@@ config.c: static int configset_find_element(struct config_set *set, const char *key,\n     + \treturn 0;\n       }\n       \n     -@@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n     - \t\tfree(path);\n     - \t}\n     - \n     --\tconfig_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);\n     -+\tconfig_reader_set_scope(reader, 0);\n     - \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n     - \t\tdie(_(\"unable to parse command-line config\"));\n     - \n     +-static int configset_add_value(struct config_reader *reader,\n     ++static int configset_add_value(struct key_value_info *kvi_p,\n     ++\t\t\t       struct config_reader *reader,\n     + \t\t\t       struct config_set *set, const char *key,\n     + \t\t\t       const char *value)\n     + {\n      @@ config.c: static int configset_add_value(struct config_reader *reader,\n       \tl_item->e = e;\n       \tl_item->value_index = e->value_list.nr - 1;\n       \n      -\tif (!reader->source)\n      -\t\tBUG(\"configset_add_value has no source\");\n     --\tif (reader->source->name) {\n     -+\tif (reader->source && reader->source->name) {\n     - \t\tkv_info->filename = strintern(reader->source->name);\n     - \t\tkv_info->linenr = reader->source->linenr;\n     - \t\tkv_info->origin_type = reader->source->origin_type;\n     --\t} else {\n     + \tif (reader->source->name) {\n     +-\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n     ++\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n     + \t} else {\n      -\t\t/* for values read from `git_config_from_parameters()` */\n      -\t\tkv_info->filename = NULL;\n      -\t\tkv_info->linenr = -1;\n      -\t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n     --\t}\n     --\tkv_info->scope = reader->parsing_scope;\n     -+\t\tkv_info->scope = reader->parsing_scope;\n     -+\t} else\n     +-\t\tkv_info->scope = reader->parsing_scope;\n      +\t\tkvi_from_param(kv_info);\n     + \t}\n       \tsi->util = kv_info;\n       \n     +@@ config.c: struct configset_add_data {\n     + #define CONFIGSET_ADD_INIT { 0 }\n     + \n     + static int config_set_callback(const char *key, const char *value,\n     +-\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t       struct key_value_info *kvi, void *cb)\n     + {\n     + \tstruct configset_add_data *data = cb;\n     +-\tconfigset_add_value(data->config_reader, data->config_set, key, value);\n     ++\tconfigset_add_value(kvi, data->config_reader, data->config_set, key, value);\n     + \treturn 0;\n     + }\n     + \n     +@@ config.c: const char *config_origin_type_name(enum config_origin_type type)\n     + \t}\n     + }\n     + \n     +-const char *current_config_origin_type(void)\n     +-{\n     +-\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n     +-\n     +-\tif (reader_origin_type(&the_reader, &type))\n     +-\t\tBUG(\"current_config_origin_type called outside config callback\");\n     +-\n     +-\treturn config_origin_type_name(type);\n     +-}\n     +-\n     + const char *config_scope_name(enum config_scope scope)\n     + {\n     + \tswitch (scope) {\n     +@@ config.c: static int reader_config_name(struct config_reader *reader, const char **out)\n       \treturn 0;\n     + }\n     + \n     +-const char *current_config_name(void)\n     +-{\n     +-\tconst char *name;\n     +-\tif (reader_config_name(&the_reader, &name))\n     +-\t\tBUG(\"current_config_name called outside config callback\");\n     +-\treturn name ? name : \"\";\n     +-}\n     +-\n     + enum config_scope current_config_scope(void)\n     + {\n     + \tif (the_reader.config_kvi)\n     +\n     + ## config.h ##\n     +@@ config.h: void git_global_config(char **user, char **xdg);\n     + int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n     + \n     + enum config_scope current_config_scope(void);\n     +-const char *current_config_origin_type(void);\n     +-const char *current_config_name(void);\n     + const char *config_origin_type_name(enum config_origin_type type);\n     ++void kvi_from_param(struct key_value_info *out);\n     + \n     + /*\n     +  * Match and parse a config key of the form:\n     +\n     + ## t/t1300-config.sh ##\n     +@@ t/t1300-config.sh: test_expect_success 'urlmatch with --show-scope' '\n     + \tEOF\n     + \n     + \tcat >expect <<-EOF &&\n     +-\tunknown\thttp.cookiefile /tmp/cookie.txt\n     +-\tunknown\thttp.sslverify false\n     ++\tlocal\thttp.cookiefile /tmp/cookie.txt\n     ++\tlocal\thttp.sslverify false\n     + \tEOF\n     + \tgit config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&\n     + \ttest_cmp expect actual\n     +@@ t/t1300-config.sh: test_expect_success '--show-origin blob ref' '\n     + '\n     + \n     + test_expect_success '--show-origin with --default' '\n     +-\ttest_must_fail git config --show-origin --default foo some.key\n     ++\tgit config --show-origin --default foo some.key >actual &&\n     ++\techo \"command line:\tfoo\" >expect &&\n     ++\ttest_cmp expect actual\n     + '\n     + \n     + test_expect_success '--show-scope with --list' '\n     +@@ t/t1300-config.sh: test_expect_success '--show-scope with --show-origin' '\n     + \n     + test_expect_success '--show-scope with --default' '\n     + \tgit config --show-scope --default foo some.key >actual &&\n     +-\techo \"unknown\tfoo\" >expect &&\n     ++\techo \"command\tfoo\" >expect &&\n     + \ttest_cmp expect actual\n     + '\n     + \n 12:  4723ae3bde6 ! 10:  7dc0c46b864 config: remove current_config_scope()\n     @@ Metadata\n      Author: Glen Choo <chooglen@google.com>\n      \n       ## Commit message ##\n     -    config: remove current_config_scope()\n     +    trace2: plumb config kvi\n      \n     -    Replace current_config_scope() by reading the corresponding value off\n     -    \"struct key_value_info\".\n     -\n     -    Most instances of this are trivial, except for the trace2/* files. There\n     -    is a code path starting from trace2_def_param_fl() that eventually calls\n     -    current_config_scope(), and thus it needs to have \"kvi\" plumbed through\n     -    it. Additional plumbing is also needed to get \"kvi\" to\n     +    There is a code path starting from trace2_def_param_fl() that eventually\n     +    calls current_config_scope(), and thus it needs to have \"kvi\" plumbed\n     +    through it. Additional plumbing is also needed to get \"kvi\" to\n          trace2_def_param_fl(), which gets called by two code paths:\n      \n          - Through tr2_cfg_cb(), which is a config callback, so it trivially\n     @@ Commit message\n            information.\n      \n            Teach tr2_list_env_vars_fl() to be well-behaved by using\n     -      kvi_from_param(), which is already used internally by config.c for\n     -      CLI/environment variable-based config.\n     +      kvi_from_param(), which is used elsewhere for CLI/environment\n     +      variable-based config.\n     +\n     +    As a result, current_config_scope() has no more callers, so remove it.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n     - ## builtin/config.c ##\n     -@@ builtin/config.c: static void show_config_origin(struct key_value_info *kvi, struct strbuf *buf)\n     - \tstrbuf_addch(buf, term);\n     + ## config.c ##\n     +@@ config.c: struct config_reader {\n     + \t */\n     + \tstruct config_source *source;\n     + \tstruct key_value_info *config_kvi;\n     +-\t/*\n     +-\t * The \"scope\" of the current config source being parsed (repo, global,\n     +-\t * etc). Like \"source\", this is only set when parsing a config source.\n     +-\t * It's not part of \"source\" because it transcends a single file (i.e.,\n     +-\t * a file included from .git/config is still in \"repo\" scope).\n     +-\t *\n     +-\t * When iterating through a configset, the equivalent value is\n     +-\t * \"config_kvi.scope\" (see above).\n     +-\t */\n     +-\tenum config_scope parsing_scope;\n     + };\n     + /*\n     +  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n     +@@ config.c: static inline struct config_source *config_reader_pop_source(struct config_reade\n     + static inline void config_reader_set_kvi(struct config_reader *reader,\n     + \t\t\t\t\t struct key_value_info *kvi)\n     + {\n     +-\tif (kvi && (reader->source || reader->parsing_scope))\n     +-\t\tBUG(\"kvi should not be set while parsing a config source\");\n     + \treader->config_kvi = kvi;\n       }\n       \n     --static void show_config_scope(struct strbuf *buf)\n     -+static void show_config_scope(struct key_value_info *kvi, struct strbuf *buf)\n     - {\n     - \tconst char term = end_nul ? '\\0' : '\\t';\n     --\tconst char *scope = config_scope_name(current_config_scope());\n     -+\tconst char *scope = config_scope_name(kvi->scope);\n     - \n     - \tstrbuf_addstr(buf, N_(scope));\n     - \tstrbuf_addch(buf, term);\n     -@@ builtin/config.c: static int show_all_config(const char *key_, const char *value_,\n     - \tif (show_origin || show_scope) {\n     - \t\tstruct strbuf buf = STRBUF_INIT;\n     - \t\tif (show_scope)\n     --\t\t\tshow_config_scope(&buf);\n     -+\t\t\tshow_config_scope(kvi, &buf);\n     - \t\tif (show_origin)\n     - \t\t\tshow_config_origin(kvi, &buf);\n     - \t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n     -@@ builtin/config.c: static int format_config(struct strbuf *buf, const char *key_, const char *value\n     - \t\t\t struct key_value_info *kvi)\n     +-static inline void config_reader_set_scope(struct config_reader *reader,\n     +-\t\t\t\t\t   enum config_scope scope)\n     +-{\n     +-\tif (scope && reader->config_kvi)\n     +-\t\tBUG(\"scope should only be set when iterating through a config source\");\n     +-\treader->parsing_scope = scope;\n     +-}\n     +-\n     + static int pack_compression_seen;\n     + static int zlib_compression_seen;\n     + \n     +@@ config.c: static void populate_remote_urls(struct config_include_data *inc)\n       {\n     - \tif (show_scope)\n     --\t\tshow_config_scope(buf);\n     -+\t\tshow_config_scope(kvi, buf);\n     - \tif (show_origin)\n     - \t\tshow_config_origin(kvi, buf);\n     - \tif (show_keys)\n     -\n     - ## builtin/remote.c ##\n     -@@ builtin/remote.c: static int config_read_push_default(const char *key, const char *value,\n     - \t    !value || strcmp(value, info->old_name))\n     - \t\treturn 0;\n     - \n     --\tinfo->scope = current_config_scope();\n     -+\tinfo->scope = kvi->scope;\n     - \tstrbuf_reset(&info->origin);\n     - \tstrbuf_addstr(&info->origin, kvi->filename);\n     - \tinfo->linenr = kvi->linenr;\n     -\n     - ## config.c ##\n     -@@ config.c: static int config_parse_pair(const char *key, const char *value,\n     + \tstruct config_options opts;\n     + \n     +-\tenum config_scope store_scope = inc->config_reader->parsing_scope;\n     +-\n     + \topts = *inc->opts;\n     + \topts.unconditional_remote_url = 1;\n     + \n     +-\tconfig_reader_set_scope(inc->config_reader, 0);\n     +-\n     + \tinc->remote_urls = xmalloc(sizeof(*inc->remote_urls));\n     + \tstring_list_init_dup(inc->remote_urls);\n     + \tconfig_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);\n     +-\n     +-\tconfig_reader_set_scope(inc->config_reader, store_scope);\n     + }\n     + \n     + static int forbid_remote_url(const char *var, const char *value UNUSED,\n     +@@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n     + \tchar *xdg_config = NULL;\n     + \tchar *user_config = NULL;\n     + \tchar *repo_config;\n     +-\tenum config_scope prev_parsing_scope = reader->parsing_scope;\n     + \n     + \tif (opts->commondir)\n     + \t\trepo_config = mkpathdup(\"%s/config\", opts->commondir);\n     +@@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n     + \telse\n     + \t\trepo_config = NULL;\n     + \n     +-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_SYSTEM);\n     + \tif (git_config_system() && system_config &&\n     + \t    !access_or_die(system_config, R_OK,\n     + \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n     +@@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n     + \t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n     + \t\t\t\t\t\t\t NULL);\n     + \n     +-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);\n     + \tgit_global_config(&user_config, &xdg_config);\n       \n     + \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n     +@@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n     + \t\tret += git_config_from_file_with_options(fn, user_config, data,\n     + \t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n       \n     - /* for values read from `git_config_from_parameters()` */\n     --static void kvi_from_param(struct key_value_info *out)\n     -+void kvi_from_param(struct key_value_info *out)\n     +-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);\n     + \tif (!opts->ignore_repo && repo_config &&\n     + \t    !access_or_die(repo_config, R_OK, 0))\n     + \t\tret += git_config_from_file_with_options(fn, repo_config, data,\n     + \t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n     + \n     +-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n     + \tif (!opts->ignore_worktree && repository_format_worktree_config) {\n     + \t\tchar *path = git_pathdup(\"config.worktree\");\n     + \t\tif (!access_or_die(path, R_OK, 0))\n     +@@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n     + \t\tfree(path);\n     + \t}\n     + \n     +-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);\n     + \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n     + \t\tdie(_(\"unable to parse command-line config\"));\n     + \n     +-\tconfig_reader_set_scope(reader, prev_parsing_scope);\n     + \tfree(system_config);\n     + \tfree(xdg_config);\n     + \tfree(user_config);\n     +@@ config.c: int config_with_options(config_fn_t fn, void *data,\n     + \t\t\tconst struct config_options *opts)\n       {\n     - \tout->filename = NULL;\n     - \tout->linenr = -1;\n     + \tstruct config_include_data inc = CONFIG_INCLUDE_INIT;\n     +-\tenum config_scope prev_scope = the_reader.parsing_scope;\n     + \tint ret;\n     + \n     + \tif (opts->respect_includes) {\n     +@@ config.c: int config_with_options(config_fn_t fn, void *data,\n     + \t\tdata = &inc;\n     + \t}\n     + \n     +-\tif (config_source)\n     +-\t\tconfig_reader_set_scope(&the_reader, config_source->scope);\n     +-\n     + \t/*\n     + \t * If we have a specific filename, use it. Otherwise, follow the\n     + \t * regular lookup sequence.\n     +@@ config.c: int config_with_options(config_fn_t fn, void *data,\n     + \t\tstring_list_clear(inc.remote_urls, 0);\n     + \t\tFREE_AND_NULL(inc.remote_urls);\n     + \t}\n     +-\tconfig_reader_set_scope(&the_reader, prev_scope);\n     + \treturn ret;\n     + }\n     + \n      @@ config.c: static int reader_config_name(struct config_reader *reader, const char **out)\n       \treturn 0;\n       }\n     @@ config.c: static int reader_config_name(struct config_reader *reader, const char\n      -\tif (the_reader.config_kvi)\n      -\t\treturn the_reader.config_kvi->scope;\n      -\telse\n     --\t\t/*\n     --\t\t * FIXME This should be a BUG, but tr2_list_env_vars_fl is\n     --\t\t * calling this outside of a config callback. This will be\n     --\t\t * easier to fix when we plumb kvi through the config callbacks,\n     --\t\t * so leave this untouched for now.\n     --\t\t */\n     --\t\treturn CONFIG_SCOPE_UNKNOWN;\n     +-\t\treturn the_reader.parsing_scope;\n      -}\n      -\n       int lookup_config(const char **mapping, int nr_mapping, const char *var)\n     @@ config.h: void git_global_config(char **user, char **xdg);\n       \n      -enum config_scope current_config_scope(void);\n       const char *config_origin_type_name(enum config_origin_type type);\n     + void kvi_from_param(struct key_value_info *out);\n       \n     - /*\n     -@@ config.h: NORETURN void git_die_config_linenr(const char *key, const char *filename, int l\n     - \tlookup_config(mapping, ARRAY_SIZE(mapping), var)\n     - int lookup_config(const char **mapping, int nr_mapping, const char *var);\n     - \n     -+void kvi_from_param(struct key_value_info *out);\n     -+\n     - #endif /* CONFIG_H */\n     -\n     - ## remote.c ##\n     -@@ remote.c: static void read_branches_file(struct remote_state *remote_state,\n     - }\n     - \n     - static int handle_config(const char *key, const char *value,\n     --\t\t\t struct key_value_info *kvi UNUSED, void *cb)\n     -+\t\t\t struct key_value_info *kvi, void *cb)\n     - {\n     - \tconst char *name;\n     - \tsize_t namelen;\n     -@@ remote.c: static int handle_config(const char *key, const char *value,\n     - \t}\n     - \tremote = make_remote(remote_state, name, namelen);\n     - \tremote->origin = REMOTE_CONFIG;\n     --\tif (current_config_scope() == CONFIG_SCOPE_LOCAL ||\n     --\t    current_config_scope() == CONFIG_SCOPE_WORKTREE)\n     -+\tif (kvi->scope == CONFIG_SCOPE_LOCAL ||\n     -+\t    kvi->scope == CONFIG_SCOPE_WORKTREE)\n     - \t\tremote->configured_in_repo = 1;\n     - \tif (!strcmp(subkey, \"mirror\"))\n     - \t\tremote->mirror = git_config_bool(key, value);\n     -\n     - ## t/helper/test-config.c ##\n     -@@ t/helper/test-config.c: static int iterate_cb(const char *var, const char *value,\n     - \tprintf(\"origin=%s\\n\", config_origin_type_name(kvi->origin_type));\n     - \tprintf(\"name=%s\\n\", kvi->filename ? kvi->filename : \"\");\n     - \tprintf(\"lno=%d\\n\", kvi->linenr);\n     --\tprintf(\"scope=%s\\n\", config_scope_name(current_config_scope()));\n     -+\tprintf(\"scope=%s\\n\", config_scope_name(kvi->scope));\n     - \n     - \treturn 0;\n     - }\n      \n       ## trace2.c ##\n      @@ trace2.c: void trace2_thread_exit_fl(const char *file, int line)\n 13:  11a32c86203 ! 11:  504eb206b5a config: pass kvi to die_bad_number()\n     @@ Commit message\n      \n          Plumb \"struct key_value_info\" through all code paths that end in\n          die_bad_number(), which lets us remove the helper functions that read\n     -    analogous values from \"struct config_reader\".\n     +    analogous values from \"struct config_reader\". As a result, nothing reads\n     +    config_reader.config_kvi any more, so remove that too.\n      \n          In config.c, this requires changing the signature of\n          git_configset_get_value() to 'return' \"kvi\" in an out parameter so that\n     @@ builtin/commit.c: static int git_commit_config(const char *k, const char *v,\n       \n      \n       ## builtin/config.c ##\n     -@@ builtin/config.c: static int format_config(struct strbuf *buf, const char *key_, const char *value\n     +@@ builtin/config.c: static int format_config(struct strbuf *buf, const char *key_,\n       \n       \t\tif (type == TYPE_INT)\n       \t\t\tstrbuf_addf(buf, \"%\"PRId64,\n     @@ builtin/submodule--helper.c: static int update_clone_task_finished(int result,\n       \n      \n       ## config.c ##\n     +@@ config.c: struct config_reader {\n     + \t *\n     + \t * The \"source\" variable will be non-NULL only when we are actually\n     + \t * parsing a real config source (file, blob, cmdline, etc).\n     +-\t *\n     +-\t * The \"config_kvi\" variable will be non-NULL only when we are feeding\n     +-\t * cached config from a configset into a callback.\n     +-\t *\n     +-\t * They cannot be non-NULL at the same time. If they are both NULL, then\n     +-\t * we aren't parsing anything (and depending on the function looking at\n     +-\t * the variables, it's either a bug for it to be called in the first\n     +-\t * place, or it's a function which can be reused for non-config\n     +-\t * purposes, and should fall back to some sane behavior).\n     + \t */\n     + \tstruct config_source *source;\n     +-\tstruct key_value_info *config_kvi;\n     + };\n     + /*\n     +  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n     +@@ config.c: static struct config_reader the_reader;\n     + static inline void config_reader_push_source(struct config_reader *reader,\n     + \t\t\t\t\t     struct config_source *top)\n     + {\n     +-\tif (reader->config_kvi)\n     +-\t\tBUG(\"source should not be set while iterating a config set\");\n     + \ttop->prev = reader->source;\n     + \treader->source = top;\n     + }\n     +@@ config.c: static inline struct config_source *config_reader_pop_source(struct config_reade\n     + \treturn ret;\n     + }\n     + \n     +-static inline void config_reader_set_kvi(struct config_reader *reader,\n     +-\t\t\t\t\t struct key_value_info *kvi)\n     +-{\n     +-\treader->config_kvi = kvi;\n     +-}\n     +-\n     + static int pack_compression_seen;\n     + static int zlib_compression_seen;\n     + \n      @@ config.c: int git_parse_ssize_t(const char *value, ssize_t *ret)\n       \treturn 1;\n       }\n     @@ config.c: int git_default_config(const char *var, const char *value,\n       \t\tif (level == -1)\n       \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n       \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n     +@@ config.c: static void configset_iter(struct config_reader *reader, struct config_set *set,\n     + \t\tvalues = &entry->value_list;\n     + \t\tkvi = values->items[value_index].util;\n     + \n     +-\t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n     +-\n     + \t\tif (fn(entry->key, values->items[value_index].string, kvi, data) < 0)\n     + \t\t\tgit_die_config_linenr(entry->key, kvi->filename, kvi->linenr);\n     +-\t\tconfig_reader_set_kvi(reader, NULL);\n     + \t}\n     + }\n     + \n      @@ config.c: int git_configset_add_file(struct config_set *set, const char *filename)\n       \treturn git_config_from_file(config_set_callback, filename, &data);\n       }\n     @@ config.c: int parse_config_key(const char *var,\n      -{\n      -\tif (the_reader.config_kvi)\n      -\t\t*type = reader->config_kvi->origin_type;\n     +-\telse if(the_reader.source)\n     +-\t\t*type = reader->source->origin_type;\n      -\telse\n      -\t\treturn 1;\n      -\treturn 0;\n     @@ config.c: const char *config_scope_name(enum config_scope scope)\n      -{\n      -\tif (the_reader.config_kvi)\n      -\t\t*out = reader->config_kvi->filename;\n     +-\telse if (the_reader.source)\n     +-\t\t*out = reader->source->name;\n      -\telse\n      -\t\treturn 1;\n      -\treturn 0;\n 14:  33e59152293 ! 12:  52db0d3be82 config: remove config_reader from configset_add_value\n     @@ Metadata\n      Author: Glen Choo <chooglen@google.com>\n      \n       ## Commit message ##\n     -    config: remove config_reader from configset_add_value\n     +    config.c: remove config_reader from configsets\n      \n     -    Since we now get \"kvi\" from the config callback, we can stop passing it\n     -    via \"*data\". Now \"struct config_reader\" has no more references, so get\n     -    rid of it.\n     +    Remove the last usage of \"struct config_reader\" from configsets by\n     +    copying the \"kvi\" arg instead of recomputing \"kvi\" from\n     +    config_reader.source. Since we no longer need to pass both \"struct\n     +    config_reader\" and \"struct config_set\" in a single \"void *cb\", remove\n     +    \"struct configset_add_data\" too.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n       ## config.c ##\n     -@@ config.c: struct config_source {\n     - };\n     - #define CONFIG_SOURCE_INIT { 0 }\n     - \n     --struct config_reader {\n     --\tstruct key_value_info *config_kvi;\n     --};\n     --/*\n     -- * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n     -- * \"the_reader\". \"the_reader\" should only be used if that is infeasible, e.g. in\n     -- * a public function.\n     -- */\n     --static struct config_reader the_reader;\n     --\n     --static inline void config_reader_push_kvi(struct config_reader *reader,\n     --\t\t\t\t\t  struct key_value_info *kvi)\n     --{\n     --\tkvi->prev = reader->config_kvi;\n     --\treader->config_kvi = kvi;\n     --}\n     --\n     --static inline struct key_value_info *config_reader_pop_kvi(struct config_reader *reader)\n     --{\n     --\tstruct key_value_info *ret;\n     --\tif (!reader->config_kvi)\n     --\t\tBUG(\"tried to pop config_kvi, but we weren't reading config\");\n     --\tret = reader->config_kvi;\n     --\treader->config_kvi = reader->config_kvi->prev;\n     --\treturn ret;\n     --}\n     --\n     - static int pack_compression_seen;\n     - static int zlib_compression_seen;\n     - \n     -@@ config.c: static int include_condition_is_true(struct key_value_info *kvi,\n     - \treturn 0;\n     - }\n     - \n     --static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n     --\t\t  struct key_value_info *kvi, void *data);\n     - static int git_config_include(const char *var, const char *value,\n     - \t\t\t      struct key_value_info *kvi, void *data)\n     - {\n     -@@ config.c: static int git_config_include(const char *var, const char *value,\n     - \t * Pass along all values, including \"include\" directives; this makes it\n     - \t * possible to query information on the includes themselves.\n     - \t */\n     --\tret = kvi_fn(inc->fn, var, value, kvi, inc->data);\n     -+\tret = inc->fn(var, value, kvi, inc->data);\n     - \tif (ret < 0)\n     - \t\treturn ret;\n     - \n     -@@ config.c: out_free_ret_1:\n     - \treturn -CONFIG_INVALID_KEY;\n     - }\n     - \n     --static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n     --\t\t  struct key_value_info *kvi, void *data)\n     --{\n     --\tint ret;\n     --\tconfig_reader_push_kvi(&the_reader, kvi);\n     --\tret = fn(key, value, kvi, data);\n     --\tconfig_reader_pop_kvi(&the_reader);\n     --\treturn ret;\n     --}\n     --\n     - static int config_parse_pair(const char *key, const char *value,\n     - \t\t\t     struct key_value_info *kvi,\n     - \t\t\t     config_fn_t fn, void *data)\n     -@@ config.c: static int config_parse_pair(const char *key, const char *value,\n     - \tif (git_config_parse_key(key, &canonical_name, NULL))\n     - \t\treturn -1;\n     - \n     --\tret = (kvi_fn(fn, canonical_name, value, kvi, data) < 0) ? -1 : 0;\n     -+\tret = (fn(canonical_name, value, kvi, data) < 0) ? -1 : 0;\n     - \tfree(canonical_name);\n     +@@ config.c: int config_with_options(config_fn_t fn, void *data,\n       \treturn ret;\n       }\n     -@@ config.c: static int get_value(struct config_source *cs, struct key_value_info *kvi,\n     - \t */\n     - \tcs->linenr--;\n     - \tkvi->linenr = cs->linenr;\n     --\tret = kvi_fn(fn, name->buf, value, kvi, data);\n     -+\tret = fn(name->buf, value, kvi, data);\n     - \tif (ret >= 0)\n     - \t\tcs->linenr++;\n     - \treturn ret;\n     -@@ config.c: static void configset_iter(struct config_set *set, config_fn_t fn, void *data)\n     - \t\tvalues = &entry->value_list;\n     - \t\tkvi = values->items[value_index].util;\n       \n     --\t\tif (kvi_fn(fn, entry->key, values->items[value_index].string,\n     --\t\t\t   kvi, data) < 0)\n     -+\t\tif (fn(entry->key, values->items[value_index].string, kvi,\n     -+\t\t       data) < 0)\n     - \t\t\tgit_die_config_linenr(entry->key, kvi->filename,\n     - \t\t\t\t\t      kvi->linenr);\n     - \t}\n     +-static void configset_iter(struct config_reader *reader, struct config_set *set,\n     +-\t\t\t   config_fn_t fn, void *data)\n     ++static void configset_iter(struct config_set *set, config_fn_t fn, void *data)\n     + {\n     + \tint i, value_index;\n     + \tstruct string_list *values;\n      @@ config.c: static int configset_find_element(struct config_set *set, const char *key,\n     - \treturn 0;\n       }\n       \n     --static int configset_add_value(struct config_reader *reader,\n     --\t\t\t       struct config_set *set, const char *key,\n     --\t\t\t       const char *value)\n     -+static int configset_add_value(struct config_set *set, const char *key,\n     -+\t\t\t       const char *value, struct key_value_info *kvi_p)\n     + static int configset_add_value(struct key_value_info *kvi_p,\n     +-\t\t\t       struct config_reader *reader,\n     + \t\t\t       struct config_set *set, const char *key,\n     + \t\t\t       const char *value)\n       {\n     - \tstruct config_set_element *e;\n     - \tstruct string_list_item *si;\n     -@@ config.c: static int configset_add_value(struct config_reader *reader,\n     +@@ config.c: static int configset_add_value(struct key_value_info *kvi_p,\n       \tl_item->e = e;\n       \tl_item->value_index = e->value_list.nr - 1;\n       \n     --\tmemcpy(kv_info, reader->config_kvi, sizeof(struct key_value_info));\n     +-\tif (reader->source->name) {\n     +-\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n     +-\t} else {\n     +-\t\tkvi_from_param(kv_info);\n     +-\t}\n      +\tmemcpy(kv_info, kvi_p, sizeof(struct key_value_info));\n       \tsi->util = kv_info;\n       \n     @@ config.c: void git_configset_clear(struct config_set *set)\n      -\tstruct config_set *config_set;\n      -\tstruct config_reader *config_reader;\n      -};\n     - #define CONFIGSET_ADD_INIT { 0 }\n     - \n     +-#define CONFIGSET_ADD_INIT { 0 }\n     +-\n       static int config_set_callback(const char *key, const char *value,\n     --\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n     -+\t\t\t       struct key_value_info *kvi, void *cb)\n     + \t\t\t       struct key_value_info *kvi, void *cb)\n       {\n      -\tstruct configset_add_data *data = cb;\n     --\tconfigset_add_value(data->config_reader, data->config_set, key, value);\n     +-\tconfigset_add_value(kvi, data->config_reader, data->config_set, key, value);\n      +\tstruct config_set *set = cb;\n     -+\tconfigset_add_value(set, key, value, kvi);\n     ++\tconfigset_add_value(kvi, set, key, value);\n       \treturn 0;\n       }\n       \n     @@ config.c: static void repo_read_config(struct repository *repo)\n       \tgit_configset_init(repo->config);\n      -\tdata.config_set = repo->config;\n      -\tdata.config_reader = &the_reader;\n     - \n     +-\n      -\tif (config_with_options(config_set_callback, &data, NULL, &opts) < 0)\n     -+\tif (config_with_options(config_set_callback, repo->config, NULL, &opts) < 0)\n     ++\tif (config_with_options(config_set_callback, repo->config, NULL,\n     ++\t\t\t\t&opts) < 0)\n       \t\t/*\n       \t\t * config_with_options() normally returns only\n       \t\t * zero, as most errors are fatal, and\n     +@@ config.c: static void repo_config_clear(struct repository *repo)\n     + void repo_config(struct repository *repo, config_fn_t fn, void *data)\n     + {\n     + \tgit_config_check_init(repo);\n     +-\tconfigset_iter(&the_reader, repo->config, fn, data);\n     ++\tconfigset_iter(repo->config, fn, data);\n     + }\n     + \n     + int repo_config_get(struct repository *repo, const char *key)\n      @@ config.c: static void read_protected_config(void)\n       \t\t.ignore_worktree = 1,\n       \t\t.system_gently = 1,\n     @@ config.c: static void read_protected_config(void)\n       }\n       \n       void git_protected_config(config_fn_t fn, void *data)\n     + {\n     + \tif (!protected_config.hash_initialized)\n     + \t\tread_protected_config();\n     +-\tconfigset_iter(&the_reader, &protected_config, fn, data);\n     ++\tconfigset_iter(&protected_config, fn, data);\n     + }\n     + \n     + /* Functions used historically to read configuration from 'the_repository' */\n  4:  b407fe1ed1d ! 13:  7d9b9eefc78 config: add kvi.path, use it to evaluate includes\n     @@ Commit message\n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n       ## config.c ##\n     +@@ config.c: struct config_include_data {\n     + \tvoid *data;\n     + \tconst struct config_options *opts;\n     + \tstruct git_config_source *config_source;\n     +-\tstruct config_reader *config_reader;\n     + \n     + \t/*\n     + \t * All remote URLs discovered when reading all config files.\n      @@ config.c: static const char include_depth_advice[] = N_(\n       \"from\\n\"\n       \"\t%s\\n\"\n       \"This might be due to circular includes.\");\n     --static int handle_path_include(struct config_source *cs, const char *path,\n     +-static int handle_path_include(struct config_source *cs,\n     +-\t\t\t       struct key_value_info *kvi,\n     +-\t\t\t       const char *path,\n      +static int handle_path_include(struct key_value_info *kvi, const char *path,\n       \t\t\t       struct config_include_data *inc)\n       {\n       \tint ret = 0;\n     -@@ config.c: static int handle_path_include(struct config_source *cs, const char *path,\n     +@@ config.c: static int handle_path_include(struct config_source *cs,\n       \tif (!is_absolute_path(path)) {\n       \t\tchar *slash;\n       \n     @@ config.c: static int handle_path_include(struct config_source *cs, const char *p\n       \t\tstrbuf_addstr(&buf, path);\n       \t\tpath = buf.buf;\n       \t}\n     -@@ config.c: static int handle_path_include(struct config_source *cs, const char *path,\n     +@@ config.c: static int handle_path_include(struct config_source *cs,\n       \tif (!access_or_die(path, R_OK, 0)) {\n       \t\tif (++inc->depth > MAX_INCLUDE_DEPTH)\n       \t\t\tdie(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,\n     @@ config.c: static int handle_path_include(struct config_source *cs, const char *p\n      +\t\t\t    kvi->filename ? kvi->filename :\n       \t\t\t    \"the command line\");\n       \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n     --\t\t\t\t\t\t\tcurrent_config_scope(),\n     --\t\t\t\t\t\t\tNULL);\n     -+\t\t\t\t\t\t\tkvi->scope, NULL);\n     - \t\tinc->depth--;\n     - \t}\n     - cleanup:\n     + \t\t\t\t\t\t\tkvi->scope, NULL);\n      @@ config.c: static void add_trailing_starstar_for_dir(struct strbuf *pat)\n       \t\tstrbuf_addstr(pat, \"**\");\n       }\n     @@ config.c: static int include_by_remote_url(struct config_include_data *inc,\n       \telse if (skip_prefix_mem(cond, cond_len, \"onbranch:\", &cond, &cond_len))\n       \t\treturn include_by_branch(cond, cond_len);\n       \telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.*.url:\", &cond,\n     -@@ config.c: static int kvi_fn(config_fn_t fn, const char *key, const char *value,\n     - static int git_config_include(const char *var, const char *value, void *data)\n     +@@ config.c: static int git_config_include(const char *var, const char *value,\n     + \t\t\t      struct key_value_info *kvi, void *data)\n       {\n       \tstruct config_include_data *inc = data;\n      -\tstruct config_source *cs = inc->config_reader->source;\n     - \tstruct key_value_info *kvi = inc->config_reader->config_kvi;\n       \tconst char *cond, *key;\n       \tsize_t cond_len;\n     -@@ config.c: static int git_config_include(const char *var, const char *value, void *data)\n     + \tint ret;\n     +@@ config.c: static int git_config_include(const char *var, const char *value,\n       \t\treturn ret;\n       \n       \tif (!strcmp(var, \"include.path\"))\n     --\t\tret = handle_path_include(cs, value, inc);\n     +-\t\tret = handle_path_include(cs, kvi, value, inc);\n      +\t\tret = handle_path_include(kvi, value, inc);\n       \n       \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n     @@ config.c: static int git_config_include(const char *var, const char *value, void\n       \n       \t\tif (inc->opts->unconditional_remote_url)\n       \t\t\tinc->fn = forbid_remote_url;\n     --\t\tret = handle_path_include(cs, value, inc);\n     +-\t\tret = handle_path_include(cs, kvi, value, inc);\n      +\t\tret = handle_path_include(kvi, value, inc);\n       \t\tinc->fn = old_fn;\n       \t}\n       \n     -@@ config.c: static void kvi_from_param(struct key_value_info *out)\n     +@@ config.c: void kvi_from_param(struct key_value_info *out)\n       \tout->linenr = -1;\n       \tout->origin_type = CONFIG_ORIGIN_CMDLINE;\n       \tout->scope = CONFIG_SCOPE_COMMAND;\n     @@ config.c: static void kvi_from_source(struct config_source *cs,\n       }\n       \n       static int git_parse_source(struct config_source *cs, config_fn_t fn,\n     +@@ config.c: int config_with_options(config_fn_t fn, void *data,\n     + \t\tinc.data = data;\n     + \t\tinc.opts = opts;\n     + \t\tinc.config_source = config_source;\n     +-\t\tinc.config_reader = &the_reader;\n     + \t\tfn = git_config_include;\n     + \t\tdata = &inc;\n     + \t}\n      \n       ## config.h ##\n      @@ config.h: struct key_value_info {\n     @@ config.h: struct key_value_info {\n       \tenum config_origin_type origin_type;\n       \tenum config_scope scope;\n      +\tconst char *path;\n     - \tstruct key_value_info *prev;\n       };\n       \n     + /**\n  5:  e80d1b5f483 ! 14:  9e35b5b1f4d config: pass source to config_parser_event_fn_t\n     @@ Commit message\n          config.c, but this refactor is okay because this function has only ever\n          been (and probably ever will be) used internally by config.c.\n      \n     -    This removes the last user of \"config_reader.source\", so remove it too.\n     +    As a result, the_reader isn't used anywhere, so \"struct config_reader\"\n     +    is obsolete (it was only intended to be used with the_reader). Remove\n     +    them.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n       ## config.c ##\n      @@ config.c: struct config_source {\n     + };\n       #define CONFIG_SOURCE_INIT { 0 }\n       \n     - struct config_reader {\n     +-struct config_reader {\n      -\t/*\n      -\t * These members record the \"current\" config source, which can be\n      -\t * accessed by parsing callbacks.\n      -\t *\n      -\t * The \"source\" variable will be non-NULL only when we are actually\n      -\t * parsing a real config source (file, blob, cmdline, etc).\n     --\t *\n     --\t * The \"config_kvi\" variable will be non-NULL only when we are feeding\n     --\t * cached config from a configset into a callback.\n     --\t *\n     --\t * They cannot be non-NULL at the same time. If they are both NULL, then\n     --\t * we aren't parsing anything (and depending on the function looking at\n     --\t * the variables, it's either a bug for it to be called in the first\n     --\t * place, or it's a function which can be reused for non-config\n     --\t * purposes, and should fall back to some sane behavior).\n      -\t */\n      -\tstruct config_source *source;\n     - \tstruct key_value_info *config_kvi;\n     - };\n     - /*\n     -@@ config.c: struct config_reader {\n     -  */\n     - static struct config_reader the_reader;\n     - \n     +-};\n     +-/*\n     +- * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n     +- * \"the_reader\". \"the_reader\" should only be used if that is infeasible, e.g. in\n     +- * a public function.\n     +- */\n     +-static struct config_reader the_reader;\n     +-\n      -static inline void config_reader_push_source(struct config_reader *reader,\n      -\t\t\t\t\t     struct config_source *top)\n      -{\n     @@ config.c: struct config_reader {\n      -\treturn ret;\n      -}\n      -\n     - static inline void config_reader_push_kvi(struct config_reader *reader,\n     - \t\t\t\t\t  struct key_value_info *kvi)\n     - {\n     + static int pack_compression_seen;\n     + static int zlib_compression_seen;\n     + \n     +@@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)\n     + \tstruct strvec to_free = STRVEC_INIT;\n     + \tint ret = 0;\n     + \tchar *envw = NULL;\n     +-\tstruct config_source source = CONFIG_SOURCE_INIT;\n     + \tstruct key_value_info kvi = { 0 };\n     + \n     +-\tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n     +-\tconfig_reader_push_source(&the_reader, &source);\n     +-\n     + \tkvi_from_param(&kvi);\n     +-\n     + \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n     + \tif (env) {\n     + \t\tunsigned long count;\n     +@@ config.c: out:\n     + \tstrbuf_release(&envvar);\n     + \tstrvec_clear(&to_free);\n     + \tfree(envw);\n     +-\tconfig_reader_pop_source(&the_reader);\n     + \treturn ret;\n     + }\n     + \n      @@ config.c: static int do_event(struct config_source *cs, enum config_event_t type,\n       \n       \tif (data->previous_type != CONFIG_EVENT_EOF &&\n     @@ config.c: static int do_event(struct config_source *cs, enum config_event_t type\n       \t\treturn -1;\n       \n       \tdata->previous_type = type;\n     -@@ config.c: int git_default_config(const char *var, const char *value, void *cb)\n     +@@ config.c: int git_default_config(const char *var, const char *value,\n        * fgetc, ungetc, ftell of top need to be initialized before calling\n        * this function.\n        */\n     @@ config.c: static int do_config_from_file(struct config_reader *reader,\n      -\tret = do_config_from(reader, &top, fn, data, scope, opts);\n      +\tret = do_config_from(&top, fn, data, scope, opts);\n       \tfunlockfile(f);\n     - \n       \treturn ret;\n     + }\n      @@ config.c: static int do_config_from_file(struct config_reader *reader,\n       static int git_config_from_stdin(config_fn_t fn, void *data,\n       \t\t\t\t enum config_scope scope)\n     @@ config.c: int git_config_from_mem(config_fn_t fn,\n       }\n       \n       int git_config_from_blob_oid(config_fn_t fn,\n     +@@ config.c: int git_config_system(void)\n     + \treturn !git_env_bool(\"GIT_CONFIG_NOSYSTEM\", 0);\n     + }\n     + \n     +-static int do_git_config_sequence(struct config_reader *reader,\n     +-\t\t\t\t  const struct config_options *opts,\n     ++static int do_git_config_sequence(const struct config_options *opts,\n     + \t\t\t\t  config_fn_t fn, void *data)\n     + {\n     + \tint ret = 0;\n     +@@ config.c: int config_with_options(config_fn_t fn, void *data,\n     + \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n     + \t\t\t\t\t       data, config_source->scope);\n     + \t} else {\n     +-\t\tret = do_git_config_sequence(&the_reader, opts, fn, data);\n     ++\t\tret = do_git_config_sequence(opts, fn, data);\n     + \t}\n     + \n     + \tif (inc.remote_urls) {\n      @@ config.c: void git_die_config(const char *key, const char *err, ...)\n        */\n       \n     @@ config.c: int git_config_set_multivar_in_file_gently(const char *config_filename\n       \t/* parse-key returns negative; flip the sign to feed exit(3) */\n       \tret = 0 - git_config_parse_key(key, &store.key, &store.baselen);\n       \tif (ret)\n     -@@ config.c: static int reader_origin_type(struct config_reader *reader,\n     - {\n     - \tif (the_reader.config_kvi)\n     - \t\t*type = reader->config_kvi->origin_type;\n     --\telse if(the_reader.source)\n     --\t\t*type = reader->source->origin_type;\n     - \telse\n     - \t\treturn 1;\n     - \treturn 0;\n     -@@ config.c: static int reader_config_name(struct config_reader *reader, const char **out)\n     - {\n     - \tif (the_reader.config_kvi)\n     - \t\t*out = reader->config_kvi->filename;\n     --\telse if (the_reader.source)\n     --\t\t*out = reader->source->name;\n     - \telse\n     - \t\treturn 1;\n     - \treturn 0;\n     -@@ config.c: int current_config_line(void)\n     - \tif (the_reader.config_kvi)\n     - \t\treturn the_reader.config_kvi->linenr;\n     - \telse\n     --\t\treturn the_reader.source->linenr;\n     -+\t\tBUG(\"current_config_line called outside config callback\");\n     - }\n     - \n     - int lookup_config(const char **mapping, int nr_mapping, const char *var)\n      \n       ## config.h ##\n      @@ config.h: enum config_event_t {\n\n-- \ngitgitgadget\n"},{"id":"477810","messageId":"f57c1007cad106a6387c77ac4d06208d39bfb9f8.1685472133.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 06/14] config.c: pass kvi in configsets","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:42:04Z","receivedAt":"2023-05-30T18:42:39Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nTrivially pass \"struct key_value_info\" to config callbacks in\nconfigset_iter(). Then, in config callbacks that are only used with\nconfigsets, use the \"kvi\" arg to replace calls to current_config_*(),\nand delete current_config_line() because it has no remaining callers.\n\nThis leaves builtin/config.c and config.c as the only remaining users of\ncurrent_config_*().\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/remote.c       |  8 ++++----\n config.c               | 34 +++++++++++++++-------------------\n config.h               |  2 +-\n remote.c               |  6 +++---\n t/helper/test-config.c | 10 +++++-----\n 5 files changed, 28 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex edb4a9ddd7f..0a18c49f1f8 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -646,17 +646,17 @@ struct push_default_info\n };\n \n static int config_read_push_default(const char *key, const char *value,\n-\tstruct key_value_info *kvi UNUSED, void *cb)\n+\tstruct key_value_info *kvi, void *cb)\n {\n \tstruct push_default_info* info = cb;\n \tif (strcmp(key, \"remote.pushdefault\") ||\n \t    !value || strcmp(value, info->old_name))\n \t\treturn 0;\n \n-\tinfo->scope = current_config_scope();\n+\tinfo->scope = kvi->scope;\n \tstrbuf_reset(&info->origin);\n-\tstrbuf_addstr(&info->origin, current_config_name());\n-\tinfo->linenr = current_config_line();\n+\tstrbuf_addstr(&info->origin, config_origin_type_name(kvi->origin_type));\n+\tinfo->linenr = kvi->linenr;\n \n \treturn 0;\n }\ndiff --git a/config.c b/config.c\nindex 1bc2e35a3e0..66078b22eef 100644\n--- a/config.c\n+++ b/config.c\n@@ -2301,19 +2301,18 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \tstruct string_list *values;\n \tstruct config_set_element *entry;\n \tstruct configset_list *list = &set->list;\n+\tstruct key_value_info *kvi;\n \n \tfor (i = 0; i < list->nr; i++) {\n \t\tentry = list->items[i].e;\n \t\tvalue_index = list->items[i].value_index;\n \t\tvalues = &entry->value_list;\n+\t\tkvi = values->items[value_index].util;\n \n \t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n \n-\t\tif (fn(entry->key, values->items[value_index].string, NULL, data) < 0)\n-\t\t\tgit_die_config_linenr(entry->key,\n-\t\t\t\t\t      reader->config_kvi->filename,\n-\t\t\t\t\t      reader->config_kvi->linenr);\n-\n+\t\tif (fn(entry->key, values->items[value_index].string, kvi, data) < 0)\n+\t\t\tgit_die_config_linenr(entry->key, kvi->filename, kvi->linenr);\n \t\tconfig_reader_set_kvi(reader, NULL);\n \t}\n }\n@@ -3951,13 +3950,8 @@ static int reader_origin_type(struct config_reader *reader,\n \treturn 0;\n }\n \n-const char *current_config_origin_type(void)\n+const char *config_origin_type_name(enum config_origin_type type)\n {\n-\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n-\n-\tif (reader_origin_type(&the_reader, &type))\n-\t\tBUG(\"current_config_origin_type called outside config callback\");\n-\n \tswitch (type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\treturn \"blob\";\n@@ -3974,6 +3968,16 @@ const char *current_config_origin_type(void)\n \t}\n }\n \n+const char *current_config_origin_type(void)\n+{\n+\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n+\n+\tif (reader_origin_type(&the_reader, &type))\n+\t\tBUG(\"current_config_origin_type called outside config callback\");\n+\n+\treturn config_origin_type_name(type);\n+}\n+\n const char *config_scope_name(enum config_scope scope)\n {\n \tswitch (scope) {\n@@ -4021,14 +4025,6 @@ enum config_scope current_config_scope(void)\n \t\treturn the_reader.parsing_scope;\n }\n \n-int current_config_line(void)\n-{\n-\tif (the_reader.config_kvi)\n-\t\treturn the_reader.config_kvi->linenr;\n-\telse\n-\t\treturn the_reader.source->linenr;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex 9e0c1f12165..ea4ffe37a55 100644\n--- a/config.h\n+++ b/config.h\n@@ -367,7 +367,7 @@ int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n enum config_scope current_config_scope(void);\n const char *current_config_origin_type(void);\n const char *current_config_name(void);\n-int current_config_line(void);\n+const char *config_origin_type_name(enum config_origin_type type);\n \n /*\n  * Match and parse a config key of the form:\ndiff --git a/remote.c b/remote.c\nindex 10868a963f2..266601157e3 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -349,7 +349,7 @@ static void read_branches_file(struct remote_state *remote_state,\n }\n \n static int handle_config(const char *key, const char *value,\n-\t\t\t struct key_value_info *kvi UNUSED, void *cb)\n+\t\t\t struct key_value_info *kvi, void *cb)\n {\n \tconst char *name;\n \tsize_t namelen;\n@@ -414,8 +414,8 @@ static int handle_config(const char *key, const char *value,\n \t}\n \tremote = make_remote(remote_state, name, namelen);\n \tremote->origin = REMOTE_CONFIG;\n-\tif (current_config_scope() == CONFIG_SCOPE_LOCAL ||\n-\t    current_config_scope() == CONFIG_SCOPE_WORKTREE)\n+\tif (kvi->scope == CONFIG_SCOPE_LOCAL ||\n+\t    kvi->scope == CONFIG_SCOPE_WORKTREE)\n \t\tremote->configured_in_repo = 1;\n \tif (!strcmp(subkey, \"mirror\"))\n \t\tremote->mirror = git_config_bool(key, value);\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 00cd49e5145..7027ffa187f 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -43,7 +43,7 @@\n  */\n \n static int iterate_cb(const char *var, const char *value,\n-\t\t      struct key_value_info *kvi UNUSED, void *data UNUSED)\n+\t\t      struct key_value_info *kvi, void *data UNUSED)\n {\n \tstatic int nr;\n \n@@ -52,10 +52,10 @@ static int iterate_cb(const char *var, const char *value,\n \n \tprintf(\"key=%s\\n\", var);\n \tprintf(\"value=%s\\n\", value ? value : \"(null)\");\n-\tprintf(\"origin=%s\\n\", current_config_origin_type());\n-\tprintf(\"name=%s\\n\", current_config_name());\n-\tprintf(\"lno=%d\\n\", current_config_line());\n-\tprintf(\"scope=%s\\n\", config_scope_name(current_config_scope()));\n+\tprintf(\"origin=%s\\n\", config_origin_type_name(kvi->origin_type));\n+\tprintf(\"name=%s\\n\", kvi->filename ? kvi->filename : \"\");\n+\tprintf(\"lno=%d\\n\", kvi->linenr);\n+\tprintf(\"scope=%s\\n\", config_scope_name(kvi->scope));\n \n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"477812","messageId":"f363b16025996f6cf16a683f5e2b3fe3721f4d93.1685472133.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 05/14] (RFC-only) config: finish config_fn_t refactor","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:42:03Z","receivedAt":"2023-05-30T18:42:40Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nHere's an exhaustive list of all of the changes:\n\n* Cases that need a judgement call\n\n  - trace2/tr2_cfg.c:tr2_cfg_set_fl()\n\n    This function needs to account for tr2_cfg_cb() now using \"kvi\".\n    Since this is only called (indirectly) by git_config_set(), config\n    source information has never been available here, so just pass NULL.\n    It will be tr2_cfg_cb()'s responsibility to not use \"kvi\".\n\n  - builtin/checkout.c:checkout_main()\n\n    This calls git_xmerge_config() as a shorthand for parsing a CLI arg.\n    \"kvi\" doesn't apply, so just pass NULL. This might be worth\n    refactoring away, since git_xmerge_config() can call\n    git_default_config().\n\n* Hard for cocci to catch\n\n  - urlmatch.c\n\n    Manually refactor the custom config callbacks in \"struct\n    urlmatch_config\".\n\n  - diff.h, fsck.h, grep.h, ident.h, xdiff-interface.h\n\n    \"struct key_value_info\" hasn't been defined yet, so forward declare\n    it. Alternatively, maybe these files should \"#include config.h\".\n\n* Likely deficiencies in .cocci patch\n\n  - submodule-config.c:gitmodules_cb()\n\n    Manually refactor a parse_config() call that gets missed because it\n    uses a different \"*data\" arg.\n\n  - grep.h, various\n\n    Manually refactor grep_config() calls. Not sure why these don't get\n    picked up.\n\n  - config.c:git_config_include(), http.c:http_options()\n\n    Manually add \"kvi\" where it was missed. Not sure why they get missed.\n\n  - builtin/clone.c:write_one_config()\n\n    Manually refactor a git_clone_config() call. Probably got missed\n    because I didn't include git_config_parse_parameter().\n\n  - ident.h\n\n    Remove the UNUSED attribute. Not sure why this is the only instance\n    of this.\n\n  - git-compat-util.h, compat/mingw.[h|c]\n\n    Manually refactor noop_core_config(), platform_core_config() and\n    mingw_core_config(). I can probably add these as \"manual fixups\" in\n    cocci.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/checkout.c | 2 +-\n builtin/clone.c    | 4 ++--\n builtin/grep.c     | 2 +-\n compat/mingw.c     | 3 ++-\n compat/mingw.h     | 4 +++-\n diff.h             | 1 +\n fsck.h             | 1 +\n git-compat-util.h  | 2 ++\n grep.c             | 6 +++---\n grep.h             | 4 +++-\n http.c             | 5 +++--\n ident.h            | 3 ++-\n submodule-config.c | 4 ++--\n trace2/tr2_cfg.c   | 2 +-\n urlmatch.c         | 6 +++---\n xdiff-interface.h  | 2 ++\n 16 files changed, 32 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 92017ba6696..9641423dc2f 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1687,7 +1687,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \n \tif (opts->conflict_style) {\n \t\topts->merge = 1; /* implied */\n-\t\tgit_xmerge_config(\"merge.conflictstyle\", opts->conflict_style, NULL);\n+\t\tgit_xmerge_config(\"merge.conflictstyle\", opts->conflict_style, NULL, NULL);\n \t}\n \tif (opts->force) {\n \t\topts->discard_changes = 1;\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 1e1cf104194..e654757c45d 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -791,14 +791,14 @@ static int git_clone_config(const char *k, const char *v,\n }\n \n static int write_one_config(const char *key, const char *value,\n-\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n+\t\t\t    struct key_value_info *kvi, void *data)\n {\n \t/*\n \t * give git_clone_config a chance to write config values back to the\n \t * environment, since git_config_set_multivar_gently only deals with\n \t * config-file writes\n \t */\n-\tint apply_failed = git_clone_config(key, value, data);\n+\tint apply_failed = git_clone_config(key, value, kvi, data);\n \tif (apply_failed)\n \t\treturn apply_failed;\n \ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 177befc3ed4..6e795f9f3a2 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -290,7 +290,7 @@ static int wait_all(void)\n static int grep_cmd_config(const char *var, const char *value,\n \t\t\t   struct key_value_info *kvi, void *cb)\n {\n-\tint st = grep_config(var, value, cb);\n+\tint st = grep_config(var, value, kvi, cb);\n \n \tif (git_color_config(var, value, cb) < 0)\n \t\tst = -1;\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex 94c5a1daa40..c8181469a2f 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -242,7 +242,8 @@ static int core_restrict_inherited_handles = -1;\n static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;\n static char *unset_environment_variables;\n \n-int mingw_core_config(const char *var, const char *value, void *cb)\n+int mingw_core_config(const char *var, const char *value,\n+\t\t      struct key_value_info *kvi UNUSED, void *cb)\n {\n \tif (!strcmp(var, \"core.hidedotfiles\")) {\n \t\tif (value && !strcasecmp(value, \"dotgitonly\"))\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 209cf7cebad..4f2b489b883 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -11,7 +11,9 @@ typedef _sigset_t sigset_t;\n #undef _POSIX_THREAD_SAFE_FUNCTIONS\n #endif\n \n-int mingw_core_config(const char *var, const char *value, void *cb);\n+struct key_value_info;\n+int mingw_core_config(const char *var, const char *value,\n+\t\t      struct key_value_info *, void *cb);\n #define platform_core_config mingw_core_config\n \n /*\ndiff --git a/diff.h b/diff.h\nindex 6a3efa63753..2ceb0fd2d66 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -532,6 +532,7 @@ void free_diffstat_info(struct diffstat_t *diffstat);\n int parse_long_opt(const char *opt, const char **argv,\n \t\t   const char **optarg);\n \n+struct key_value_info;\n int git_diff_basic_config(const char *var, const char *value,\n \t\t\t  struct key_value_info *kvi, void *cb);\n int git_diff_heuristic_config(const char *var, const char *value, void *cb);\ndiff --git a/fsck.h b/fsck.h\nindex a06f202576c..914e67a067d 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -233,6 +233,7 @@ void fsck_put_object_name(struct fsck_options *options,\n const char *fsck_describe_object(struct fsck_options *options,\n \t\t\t\t const struct object_id *oid);\n \n+struct key_value_info;\n /*\n  * git_config() callback for use by fsck-y tools that want to support\n  * fsck.<msg> fsck.skipList etc.\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 4a200a9fb41..6812b592c15 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -440,8 +440,10 @@ typedef uintmax_t timestamp_t;\n #endif\n \n #ifndef platform_core_config\n+struct key_value_info;\n static inline int noop_core_config(const char *var UNUSED,\n \t\t\t\t   const char *value UNUSED,\n+\t\t\t\t   struct key_value_info *kvi UNUSED,\n \t\t\t\t   void *cb UNUSED)\n {\n \treturn 0;\ndiff --git a/grep.c b/grep.c\nindex 1516b0689d0..2d3b9bf5d92 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -55,7 +55,7 @@ define_list_config_array_extra(color_grep_slots, {\"match\"});\n  * the grep_defaults template.\n  */\n int grep_config(const char *var, const char *value,\n-\t\tstruct key_value_info *kvi UNUSED, void *cb)\n+\t\tstruct key_value_info *kvi, void *cb)\n {\n \tstruct grep_opt *opt = cb;\n \tconst char *slot;\n@@ -90,9 +90,9 @@ int grep_config(const char *var, const char *value,\n \tif (!strcmp(var, \"color.grep\"))\n \t\topt->color = git_config_colorbool(var, value);\n \tif (!strcmp(var, \"color.grep.match\")) {\n-\t\tif (grep_config(\"color.grep.matchcontext\", value, cb) < 0)\n+\t\tif (grep_config(\"color.grep.matchcontext\", value, kvi, cb) < 0)\n \t\t\treturn -1;\n-\t\tif (grep_config(\"color.grep.matchselected\", value, cb) < 0)\n+\t\tif (grep_config(\"color.grep.matchselected\", value, kvi, cb) < 0)\n \t\t\treturn -1;\n \t} else if (skip_prefix(var, \"color.grep.\", &slot)) {\n \t\tint i = LOOKUP_CONFIG(color_grep_slots, slot);\ndiff --git a/grep.h b/grep.h\nindex c59592e3bdb..6d2fb0ada54 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -202,7 +202,9 @@ struct grep_opt {\n \t.output = std_output, \\\n }\n \n-int grep_config(const char *var, const char *value, void *);\n+struct key_value_info;\n+int grep_config(const char *var, const char *value, struct key_value_info *kvi,\n+\t\tvoid *);\n void grep_init(struct grep_opt *, struct repository *repo);\n \n void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);\ndiff --git a/http.c b/http.c\nindex d5d82c5230f..3d4292eba6a 100644\n--- a/http.c\n+++ b/http.c\n@@ -361,7 +361,8 @@ static void process_curl_messages(void)\n \t}\n }\n \n-static int http_options(const char *var, const char *value, void *cb)\n+static int http_options(const char *var, const char *value,\n+\t\t\tstruct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(\"http.version\", var)) {\n \t\treturn git_config_string(&curl_http_version, var, value);\n@@ -532,7 +533,7 @@ static int http_options(const char *var, const char *value, void *cb)\n \t}\n \n \t/* Fall back on the default ones */\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, kvi, cb);\n }\n \n static int curl_empty_auth_enabled(void)\ndiff --git a/ident.h b/ident.h\nindex f55db41ff99..4e3bd347c52 100644\n--- a/ident.h\n+++ b/ident.h\n@@ -62,7 +62,8 @@ const char *fmt_name(enum want_ident);\n int committer_ident_sufficiently_given(void);\n int author_ident_sufficiently_given(void);\n \n+struct key_value_info;\n int git_ident_config(const char *, const char *,\n-\t\t     struct key_value_info *UNUSED, void *);\n+\t\t     struct key_value_info *, void *);\n \n #endif\ndiff --git a/submodule-config.c b/submodule-config.c\nindex fb12d8040e8..0b794b6f32e 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -675,7 +675,7 @@ out:\n }\n \n static int gitmodules_cb(const char *var, const char *value,\n-\t\t\t struct key_value_info *kvi UNUSED, void *data)\n+\t\t\t struct key_value_info *kvi, void *data)\n {\n \tstruct repository *repo = data;\n \tstruct parse_config_parameter parameter;\n@@ -685,7 +685,7 @@ static int gitmodules_cb(const char *var, const char *value,\n \tparameter.gitmodules_oid = null_oid();\n \tparameter.overwrite = 1;\n \n-\treturn parse_config(var, value, &parameter);\n+\treturn parse_config(var, value, kvi, &parameter);\n }\n \n void repo_read_gitmodules(struct repository *repo, int skip_if_read)\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 6871258d468..8ed139c69f4 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -146,5 +146,5 @@ void tr2_cfg_set_fl(const char *file, int line, const char *key,\n \tstruct tr2_cfg_data data = { file, line };\n \n \tif (tr2_cfg_load_patterns() > 0)\n-\t\ttr2_cfg_cb(key, value, &data);\n+\t\ttr2_cfg_cb(key, value, NULL, &data);\n }\ndiff --git a/urlmatch.c b/urlmatch.c\nindex 47683974d8c..c94500b61b3 100644\n--- a/urlmatch.c\n+++ b/urlmatch.c\n@@ -552,7 +552,7 @@ static int cmp_matches(const struct urlmatch_item *a,\n }\n \n int urlmatch_config_entry(const char *var, const char *value,\n-\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n+\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tstruct string_list_item *item;\n \tstruct urlmatch_config *collect = cb;\n@@ -566,7 +566,7 @@ int urlmatch_config_entry(const char *var, const char *value,\n \n \tif (!skip_prefix(var, collect->section, &key) || *(key++) != '.') {\n \t\tif (collect->cascade_fn)\n-\t\t\treturn collect->cascade_fn(var, value, cb);\n+\t\t\treturn collect->cascade_fn(var, value, kvi, cb);\n \t\treturn 0; /* not interested */\n \t}\n \tdot = strrchr(key, '.');\n@@ -610,7 +610,7 @@ int urlmatch_config_entry(const char *var, const char *value,\n \tstrbuf_addstr(&synthkey, collect->section);\n \tstrbuf_addch(&synthkey, '.');\n \tstrbuf_addstr(&synthkey, key);\n-\tretval = collect->collect_fn(synthkey.buf, value, collect->cb);\n+\tretval = collect->collect_fn(synthkey.buf, value, kvi, collect->cb);\n \n \tstrbuf_release(&synthkey);\n \treturn retval;\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex c1676b11702..749cdf77579 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -50,6 +50,8 @@ int buffer_is_binary(const char *ptr, unsigned long size);\n \n void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n+\n+struct key_value_info;\n int git_xmerge_config(const char *var, const char *value,\n \t\t      struct key_value_info *kvi, void *cb);\n extern int git_xmerge_style;\n-- \ngitgitgadget\n\n"},{"id":"477813","messageId":"641a56f0b40a88b0127819989ed1f81f844a090e.1685472133.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 07/14] config: provide kvi with config files","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:42:05Z","receivedAt":"2023-05-30T18:42:57Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nRefactor out the configset logic that caches \"struct config_source\" and\n\"enum config_scope\" as a \"struct key_value_info\", and use it to pass the\n\"kvi\" arg to config callbacks when parsing config files. Get the \"enum\nconfig_scope\" value by plumbing an additional arg through\ngit_config_from_file_with_options() and the underlying machinery.\n\nWe do not exercise the \"kvi\" arg yet because the remaining\ncurrent_config_*() callers may be used with config_with_options(), which\nmay read config from parameters, but parameters don't pass \"kvi\" yet.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n bundle-uri.c       |   1 +\n config.c           | 104 +++++++++++++++++++++++++++++----------------\n config.h           |   8 ++--\n fsck.c             |   3 +-\n submodule-config.c |   5 ++-\n 5 files changed, 79 insertions(+), 42 deletions(-)\n\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex 7f01fbbff74..bb88ccbca4b 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -254,6 +254,7 @@ int bundle_uri_parse_config_format(const char *uri,\n \t}\n \tresult = git_config_from_file_with_options(config_to_bundle_list,\n \t\t\t\t\t\t   filename, list,\n+\t\t\t\t\t\t   CONFIG_SCOPE_UNKNOWN,\n \t\t\t\t\t\t   &opts);\n \n \tif (!result && list->mode == BUNDLE_MODE_NONE) {\ndiff --git a/config.c b/config.c\nindex 66078b22eef..4b6830fcc79 100644\n--- a/config.c\n+++ b/config.c\n@@ -251,7 +251,9 @@ static int handle_path_include(struct config_source *cs, const char *path,\n \t\t\t    !cs ? \"<unknown>\" :\n \t\t\t    cs->name ? cs->name :\n \t\t\t    \"the command line\");\n-\t\tret = git_config_from_file(git_config_include, path, inc);\n+\t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n+\t\t\t\t\t\t\tcurrent_config_scope(),\n+\t\t\t\t\t\t\tNULL);\n \t\tinc->depth--;\n \t}\n cleanup:\n@@ -481,7 +483,7 @@ static int include_condition_is_true(struct config_source *cs,\n }\n \n static int git_config_include(const char *var, const char *value,\n-\t\t\t      struct key_value_info *kvi UNUSED, void *data)\n+\t\t\t      struct key_value_info *kvi, void *data)\n {\n \tstruct config_include_data *inc = data;\n \tstruct config_source *cs = inc->config_reader->source;\n@@ -493,7 +495,7 @@ static int git_config_include(const char *var, const char *value,\n \t * Pass along all values, including \"include\" directives; this makes it\n \t * possible to query information on the includes themselves.\n \t */\n-\tret = inc->fn(var, value, NULL, inc->data);\n+\tret = inc->fn(var, value, kvi, inc->data);\n \tif (ret < 0)\n \t\treturn ret;\n \n@@ -929,8 +931,8 @@ static char *parse_value(struct config_source *cs)\n \t}\n }\n \n-static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n-\t\t     struct strbuf *name)\n+static int get_value(struct config_source *cs, struct key_value_info *kvi,\n+\t\t     config_fn_t fn, void *data, struct strbuf *name)\n {\n \tint c;\n \tchar *value;\n@@ -963,7 +965,8 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \t * accurate line number in error messages.\n \t */\n \tcs->linenr--;\n-\tret = fn(name->buf, value, NULL, data);\n+\tkvi->linenr = cs->linenr;\n+\tret = fn(name->buf, value, kvi, data);\n \tif (ret >= 0)\n \t\tcs->linenr++;\n \treturn ret;\n@@ -1062,8 +1065,19 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \treturn 0;\n }\n \n+static void kvi_from_source(struct config_source *cs,\n+\t\t\t    enum config_scope scope,\n+\t\t\t    struct key_value_info *out)\n+{\n+\tout->filename = strintern(cs->name);\n+\tout->origin_type = cs->origin_type;\n+\tout->linenr = cs->linenr;\n+\tout->scope = scope;\n+}\n+\n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n-\t\t\t    void *data, const struct config_options *opts)\n+\t\t\t    struct key_value_info *kvi, void *data,\n+\t\t\t    const struct config_options *opts)\n {\n \tint comment = 0;\n \tsize_t baselen = 0;\n@@ -1147,7 +1161,7 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t */\n \t\tstrbuf_setlen(var, baselen);\n \t\tstrbuf_addch(var, tolower(c));\n-\t\tif (get_value(cs, fn, data, var) < 0)\n+\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n \t\t\tbreak;\n \t}\n \n@@ -2005,9 +2019,11 @@ int git_default_config(const char *var, const char *value,\n  * this function.\n  */\n static int do_config_from(struct config_reader *reader,\n-\t\t\t  struct config_source *top, config_fn_t fn, void *data,\n+\t\t\t  struct config_source *top, config_fn_t fn,\n+\t\t\t  void *data, enum config_scope scope,\n \t\t\t  const struct config_options *opts)\n {\n+\tstruct key_value_info kvi = { 0 };\n \tint ret;\n \n \t/* push config-file parsing state stack */\n@@ -2017,8 +2033,9 @@ static int do_config_from(struct config_reader *reader,\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n \tconfig_reader_push_source(reader, top);\n+\tkvi_from_source(top, scope, &kvi);\n \n-\tret = git_parse_source(top, fn, data, opts);\n+\tret = git_parse_source(top, fn, &kvi, data, opts);\n \n \t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n@@ -2032,7 +2049,8 @@ static int do_config_from_file(struct config_reader *reader,\n \t\t\t       config_fn_t fn,\n \t\t\t       const enum config_origin_type origin_type,\n \t\t\t       const char *name, const char *path, FILE *f,\n-\t\t\t       void *data, const struct config_options *opts)\n+\t\t\t       void *data, enum config_scope scope,\n+\t\t\t       const struct config_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n@@ -2047,19 +2065,20 @@ static int do_config_from_file(struct config_reader *reader,\n \ttop.do_ftell = config_file_ftell;\n \n \tflockfile(f);\n-\tret = do_config_from(reader, &top, fn, data, opts);\n+\tret = do_config_from(reader, &top, fn, data, scope, opts);\n \tfunlockfile(f);\n \treturn ret;\n }\n \n-static int git_config_from_stdin(config_fn_t fn, void *data)\n+static int git_config_from_stdin(config_fn_t fn, void *data,\n+\t\t\t\t enum config_scope scope)\n {\n \treturn do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, \"\",\n-\t\t\t\t   NULL, stdin, data, NULL);\n+\t\t\t\t   NULL, stdin, data, scope, NULL);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n-\t\t\t\t      void *data,\n+\t\t\t\t      void *data, enum config_scope scope,\n \t\t\t\t      const struct config_options *opts)\n {\n \tint ret = -1;\n@@ -2070,7 +2089,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \tf = fopen_or_warn(filename, \"r\");\n \tif (f) {\n \t\tret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,\n-\t\t\t\t\t  filename, filename, f, data, opts);\n+\t\t\t\t\t  filename, filename, f, data, scope,\n+\t\t\t\t\t  opts);\n \t\tfclose(f);\n \t}\n \treturn ret;\n@@ -2078,13 +2098,15 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \n int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n {\n-\treturn git_config_from_file_with_options(fn, filename, data, NULL);\n+\treturn git_config_from_file_with_options(fn, filename, data,\n+\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, NULL);\n }\n \n int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type origin_type,\n \t\t\tconst char *name, const char *buf, size_t len,\n-\t\t\tvoid *data, const struct config_options *opts)\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \n@@ -2099,14 +2121,15 @@ int git_config_from_mem(config_fn_t fn,\n \ttop.do_ungetc = config_buf_ungetc;\n \ttop.do_ftell = config_buf_ftell;\n \n-\treturn do_config_from(&the_reader, &top, fn, data, opts);\n+\treturn do_config_from(&the_reader, &top, fn, data, scope, opts);\n }\n \n int git_config_from_blob_oid(config_fn_t fn,\n \t\t\t      const char *name,\n \t\t\t      struct repository *repo,\n \t\t\t      const struct object_id *oid,\n-\t\t\t      void *data)\n+\t\t\t      void *data,\n+\t\t\t      enum config_scope scope)\n {\n \tenum object_type type;\n \tchar *buf;\n@@ -2122,7 +2145,7 @@ int git_config_from_blob_oid(config_fn_t fn,\n \t}\n \n \tret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size,\n-\t\t\t\t  data, NULL);\n+\t\t\t\t  data, scope, NULL);\n \tfree(buf);\n \n \treturn ret;\n@@ -2131,13 +2154,14 @@ int git_config_from_blob_oid(config_fn_t fn,\n static int git_config_from_blob_ref(config_fn_t fn,\n \t\t\t\t    struct repository *repo,\n \t\t\t\t    const char *name,\n-\t\t\t\t    void *data)\n+\t\t\t\t    void *data,\n+\t\t\t\t    enum config_scope scope)\n {\n \tstruct object_id oid;\n \n \tif (repo_get_oid(repo, name, &oid) < 0)\n \t\treturn error(_(\"unable to resolve config blob '%s'\"), name);\n-\treturn git_config_from_blob_oid(fn, name, repo, &oid, data);\n+\treturn git_config_from_blob_oid(fn, name, repo, &oid, data, scope);\n }\n \n char *git_system_config(void)\n@@ -2212,27 +2236,34 @@ static int do_git_config_sequence(struct config_reader *reader,\n \tif (git_config_system() && system_config &&\n \t    !access_or_die(system_config, R_OK,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n-\t\tret += git_config_from_file(fn, system_config, data);\n+\t\tret += git_config_from_file_with_options(fn, system_config,\n+\t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n+\t\t\t\t\t\t\t NULL);\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);\n \tgit_global_config(&user_config, &xdg_config);\n \n \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file(fn, xdg_config, data);\n+\t\tret += git_config_from_file_with_options(fn, xdg_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n \tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file(fn, user_config, data);\n+\t\tret += git_config_from_file_with_options(fn, user_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);\n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n-\t\tret += git_config_from_file(fn, repo_config, data);\n+\t\tret += git_config_from_file_with_options(fn, repo_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n \tif (!opts->ignore_worktree && repository_format_worktree_config) {\n \t\tchar *path = git_pathdup(\"config.worktree\");\n \t\tif (!access_or_die(path, R_OK, 0))\n-\t\t\tret += git_config_from_file(fn, path, data);\n+\t\t\tret += git_config_from_file_with_options(fn, path, data,\n+\t\t\t\t\t\t\t\t CONFIG_SCOPE_WORKTREE,\n+\t\t\t\t\t\t\t\t NULL);\n \t\tfree(path);\n \t}\n \n@@ -2274,14 +2305,16 @@ int config_with_options(config_fn_t fn, void *data,\n \t * regular lookup sequence.\n \t */\n \tif (config_source && config_source->use_stdin) {\n-\t\tret = git_config_from_stdin(fn, data);\n+\t\tret = git_config_from_stdin(fn, data, config_source->scope);\n \t} else if (config_source && config_source->file) {\n-\t\tret = git_config_from_file(fn, config_source->file, data);\n+\t\tret = git_config_from_file_with_options(fn, config_source->file,\n+\t\t\t\t\t\t\tdata, config_source->scope,\n+\t\t\t\t\t\t\tNULL);\n \t} else if (config_source && config_source->blob) {\n \t\tstruct repository *repo = config_source->repo ?\n \t\t\tconfig_source->repo : the_repository;\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n-\t\t\t\t\t\tdata);\n+\t\t\t\t\t       data, config_source->scope);\n \t} else {\n \t\tret = do_git_config_sequence(&the_reader, opts, fn, data);\n \t}\n@@ -2423,16 +2456,14 @@ static int configset_add_value(struct config_reader *reader,\n \tif (!reader->source)\n \t\tBUG(\"configset_add_value has no source\");\n \tif (reader->source->name) {\n-\t\tkv_info->filename = strintern(reader->source->name);\n-\t\tkv_info->linenr = reader->source->linenr;\n-\t\tkv_info->origin_type = reader->source->origin_type;\n+\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n \t} else {\n \t\t/* for values read from `git_config_from_parameters()` */\n \t\tkv_info->filename = NULL;\n \t\tkv_info->linenr = -1;\n \t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n+\t\tkv_info->scope = reader->parsing_scope;\n \t}\n-\tkv_info->scope = reader->parsing_scope;\n \tsi->util = kv_info;\n \n \treturn 0;\n@@ -3472,7 +3503,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \t\t */\n \t\tif (git_config_from_file_with_options(store_aux,\n \t\t\t\t\t\t      config_filename,\n-\t\t\t\t\t\t      &store, &opts)) {\n+\t\t\t\t\t\t      &store, CONFIG_SCOPE_UNKNOWN,\n+\t\t\t\t\t\t      &opts)) {\n \t\t\terror(_(\"invalid config file %s\"), config_filename);\n \t\t\tret = CONFIG_INVALID_FILE;\n \t\t\tgoto out_free;\ndiff --git a/config.h b/config.h\nindex ea4ffe37a55..ca5aac62980 100644\n--- a/config.h\n+++ b/config.h\n@@ -150,16 +150,18 @@ int git_default_config(const char *, const char *, struct key_value_info *,\n int git_config_from_file(config_fn_t fn, const char *, void *);\n \n int git_config_from_file_with_options(config_fn_t fn, const char *,\n-\t\t\t\t      void *,\n+\t\t\t\t      void *, enum config_scope,\n \t\t\t\t      const struct config_options *);\n int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type,\n \t\t\tconst char *name,\n \t\t\tconst char *buf, size_t len,\n-\t\t\tvoid *data, const struct config_options *opts);\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_options *opts);\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     struct repository *repo,\n-\t\t\t     const struct object_id *oid, void *data);\n+\t\t\t     const struct object_id *oid, void *data,\n+\t\t\t     enum config_scope scope);\n void git_config_push_parameter(const char *text);\n void git_config_push_env(const char *spec);\n int git_config_from_parameters(config_fn_t fn, void *data);\ndiff --git a/fsck.c b/fsck.c\nindex 1569e64f011..ec26857c79d 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1236,7 +1236,8 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\tdata.ret = 0;\n \t\tconfig_opts.error_action = CONFIG_ERROR_SILENT;\n \t\tif (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB,\n-\t\t\t\t\t\".gitmodules\", buf, size, &data, &config_opts))\n+\t\t\t\t\t\".gitmodules\", buf, size, &data,\n+\t\t\t\t\tCONFIG_SCOPE_UNKNOWN, &config_opts))\n \t\t\tdata.ret |= report(options, oid, OBJ_BLOB,\n \t\t\t\t\tFSCK_MSG_GITMODULES_PARSE,\n \t\t\t\t\t\"could not parse gitmodules blob\");\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 0b794b6f32e..7d773f33621 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -604,7 +604,7 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \tparameter.gitmodules_oid = &oid;\n \tparameter.overwrite = 0;\n \tgit_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf,\n-\t\t\tconfig, config_size, &parameter, NULL);\n+\t\t\t    config, config_size, &parameter, CONFIG_SCOPE_UNKNOWN, NULL);\n \tstrbuf_release(&rev);\n \tfree(config);\n \n@@ -713,7 +713,8 @@ void gitmodules_config_oid(const struct object_id *commit_oid)\n \n \tif (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) {\n \t\tgit_config_from_blob_oid(gitmodules_cb, rev.buf,\n-\t\t\t\t\t the_repository, &oid, the_repository);\n+\t\t\t\t\t the_repository, &oid, the_repository,\n+\t\t\t\t\t CONFIG_SCOPE_UNKNOWN);\n \t}\n \tstrbuf_release(&rev);\n \n-- \ngitgitgadget\n\n"},{"id":"477814","messageId":"bd52c6232ec1692948de89b4129bbfe764add631.1685472133.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 04/14] (RFC-only) config: apply cocci to config_fn_t implementations","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:42:02Z","receivedAt":"2023-05-30T18:43:00Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPass \"struct key_value_info\" to *most* functions that are invoked as\n\"config_fn_t\" callbacks by applying\ncontrib/coccinelle/config_fn_kvi.pending.cocci. None of the functions\nactually use the \"kvi\" arg yet (besides propagating it to a function\nthat now expects \"kvi\"), but this will be addressed in a later commit.\nWhen deciding whether or not to propagate \"kvi\" to an inner function,\nonly propagate the \"kvi\" arg if the inner function is actually invoked\nelsewhere as a config callback; it does not matter whether the function\nhappens have the same signature as config_fn_t.\n\nThis commit does not build and has several style issues (e.g. a lack of\nspacing around the \"kvi\" arg), but I've split this out for the RFC so\nthat it's more obvious which changes are automatic vs manual. Post-RFC I\nwill squash this with the rest of the refactor + cocci changes.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n alias.c                     |  3 ++-\n archive-tar.c               |  3 ++-\n archive-zip.c               |  1 +\n builtin/add.c               |  5 +++--\n builtin/blame.c             |  5 +++--\n builtin/branch.c            |  5 +++--\n builtin/cat-file.c          |  5 +++--\n builtin/checkout.c          |  5 +++--\n builtin/clean.c             |  5 +++--\n builtin/clone.c             |  8 +++++---\n builtin/column.c            |  3 ++-\n builtin/commit-graph.c      |  1 +\n builtin/commit.c            | 10 ++++++----\n builtin/config.c            | 10 +++++++---\n builtin/difftool.c          |  5 +++--\n builtin/fetch.c             |  8 +++++---\n builtin/fsmonitor--daemon.c |  5 +++--\n builtin/grep.c              |  5 +++--\n builtin/help.c              |  5 +++--\n builtin/index-pack.c        |  5 +++--\n builtin/log.c               | 10 ++++++----\n builtin/merge.c             |  7 ++++---\n builtin/multi-pack-index.c  |  1 +\n builtin/pack-objects.c      |  5 +++--\n builtin/patch-id.c          |  5 +++--\n builtin/pull.c              |  5 +++--\n builtin/push.c              |  5 +++--\n builtin/read-tree.c         |  5 +++--\n builtin/rebase.c            |  5 +++--\n builtin/receive-pack.c      |  5 +++--\n builtin/reflog.c            |  7 ++++---\n builtin/remote.c            |  6 ++++--\n builtin/repack.c            |  5 +++--\n builtin/reset.c             |  5 +++--\n builtin/send-pack.c         |  5 +++--\n builtin/show-branch.c       |  5 +++--\n builtin/stash.c             |  5 +++--\n builtin/submodule--helper.c |  1 +\n builtin/tag.c               |  5 +++--\n builtin/var.c               |  5 +++--\n builtin/worktree.c          |  5 +++--\n bundle-uri.c                |  8 ++++++--\n config.c                    | 28 ++++++++++++++++++----------\n config.h                    |  3 ++-\n connect.c                   |  4 ++--\n convert.c                   |  4 +++-\n credential.c                |  1 +\n delta-islands.c             |  3 ++-\n diff.c                      | 10 ++++++----\n diff.h                      |  6 ++++--\n fetch-pack.c                |  5 +++--\n fmt-merge-msg.c             |  5 +++--\n fmt-merge-msg.h             |  3 ++-\n fsck.c                      |  8 +++++---\n fsck.h                      |  3 ++-\n gpg-interface.c             |  6 ++++--\n grep.c                      |  3 ++-\n help.c                      |  7 +++++--\n ident.c                     |  3 ++-\n ident.h                     |  3 ++-\n imap-send.c                 |  5 +++--\n ll-merge.c                  |  1 +\n ls-refs.c                   |  2 +-\n mailinfo.c                  |  5 +++--\n notes-utils.c               |  3 ++-\n notes.c                     |  3 ++-\n pager.c                     |  5 ++++-\n pretty.c                    |  1 +\n promisor-remote.c           |  4 +++-\n remote.c                    |  3 ++-\n revision.c                  |  3 ++-\n scalar.c                    |  3 ++-\n sequencer.c                 |  8 +++++---\n setup.c                     | 15 ++++++++++-----\n submodule-config.c          | 15 +++++++++++----\n t/helper/test-config.c      |  9 ++++++---\n t/helper/test-userdiff.c    |  4 +++-\n trace2/tr2_cfg.c            |  3 ++-\n trace2/tr2_sysenv.c         |  3 ++-\n trailer.c                   |  2 ++\n upload-pack.c               |  8 ++++++--\n urlmatch.c                  |  3 ++-\n urlmatch.h                  |  3 ++-\n xdiff-interface.c           |  5 +++--\n xdiff-interface.h           |  3 ++-\n 85 files changed, 285 insertions(+), 156 deletions(-)\n\ndiff --git a/alias.c b/alias.c\nindex e814948ced3..38c51038a13 100644\n--- a/alias.c\n+++ b/alias.c\n@@ -11,7 +11,8 @@ struct config_alias_data {\n \tstruct string_list *list;\n };\n \n-static int config_alias_cb(const char *key, const char *value, void *d)\n+static int config_alias_cb(const char *key, const char *value,\n+\t\t\t   struct key_value_info *kvi UNUSED, void *d)\n {\n \tstruct config_alias_data *data = d;\n \tconst char *p;\ndiff --git a/archive-tar.c b/archive-tar.c\nindex 497dad0b3af..dcfbce5225a 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -410,7 +410,8 @@ static int tar_filter_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-static int git_tar_config(const char *var, const char *value, void *cb)\n+static int git_tar_config(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n {\n \tif (!strcmp(var, \"tar.umask\")) {\n \t\tif (value && !strcmp(value, \"user\")) {\ndiff --git a/archive-zip.c b/archive-zip.c\nindex e6f5c10a14f..0b028246689 100644\n--- a/archive-zip.c\n+++ b/archive-zip.c\n@@ -616,6 +616,7 @@ static void dos_time(timestamp_t *timestamp, int *dos_date, int *dos_time)\n }\n \n static int archive_zip_config(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi UNUSED,\n \t\t\t      void *data UNUSED)\n {\n \treturn userdiff_config(var, value);\ndiff --git a/builtin/add.c b/builtin/add.c\nindex f12054d9be1..f8e42e05b07 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -355,7 +355,8 @@ static struct option builtin_add_options[] = {\n \tOPT_END(),\n };\n \n-static int add_config(const char *var, const char *value, void *cb)\n+static int add_config(const char *var, const char *value,\n+\t\t      struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"add.ignoreerrors\") ||\n \t    !strcmp(var, \"add.ignore-errors\")) {\n@@ -363,7 +364,7 @@ static int add_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static const char embedded_advice[] = N_(\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex a8d2114adc9..0aafc8172e2 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -692,7 +692,8 @@ static const char *add_prefix(const char *prefix, const char *path)\n \treturn prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n }\n \n-static int git_blame_config(const char *var, const char *value, void *cb)\n+static int git_blame_config(const char *var, const char *value,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"blame.showroot\")) {\n \t\tshow_root = git_config_bool(var, value);\n@@ -765,7 +766,7 @@ static int git_blame_config(const char *var, const char *value, void *cb)\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static int blame_copy_callback(const struct option *option, const char *arg, int unset)\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex c6982181fd5..26091a036d2 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -80,7 +80,8 @@ static unsigned int colopts;\n \n define_list_config_array(color_branch_slots);\n \n-static int git_branch_config(const char *var, const char *value, void *cb)\n+static int git_branch_config(const char *var, const char *value,\n+\t\t\t     struct key_value_info *kvi, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -117,7 +118,7 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static const char *branch_get_color(enum color_branch ix)\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 04d4bb6c777..b1e0e95d631 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -882,12 +882,13 @@ static int batch_objects(struct batch_options *opt)\n \treturn retval;\n }\n \n-static int git_cat_file_config(const char *var, const char *value, void *cb)\n+static int git_cat_file_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi, void *cb)\n {\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static int batch_option_callback(const struct option *opt,\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 38a8cd6a965..92017ba6696 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1182,7 +1182,8 @@ static int switch_branches(const struct checkout_opts *opts,\n \treturn ret || writeout_error;\n }\n \n-static int git_checkout_config(const char *var, const char *value, void *cb)\n+static int git_checkout_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi, void *cb)\n {\n \tstruct checkout_opts *opts = cb;\n \n@@ -1198,7 +1199,7 @@ static int git_checkout_config(const char *var, const char *value, void *cb)\n \tif (starts_with(var, \"submodule.\"))\n \t\treturn git_default_submodule_config(var, value, NULL);\n \n-\treturn git_xmerge_config(var, value, NULL);\n+\treturn git_xmerge_config(var, value,kvi, NULL);\n }\n \n static void setup_new_branch_info_and_source_tree(\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex a06df48a269..1c648276ebf 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -102,7 +102,8 @@ struct menu_stuff {\n \n define_list_config_array(color_interactive_slots);\n \n-static int git_clean_config(const char *var, const char *value, void *cb)\n+static int git_clean_config(const char *var, const char *value,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -132,7 +133,7 @@ static int git_clean_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static const char *clean_get_color(enum color_clean ix)\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 6dc89f1058b..1e1cf104194 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -775,7 +775,8 @@ static int checkout(int submodule_progress, int filter_submodules)\n \treturn err;\n }\n \n-static int git_clone_config(const char *k, const char *v, void *cb)\n+static int git_clone_config(const char *k, const char *v,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(k, \"clone.defaultremotename\")) {\n \t\tfree(remote_name);\n@@ -786,10 +787,11 @@ static int git_clone_config(const char *k, const char *v, void *cb)\n \tif (!strcmp(k, \"clone.filtersubmodules\"))\n \t\tconfig_filter_submodules = git_config_bool(k, v);\n \n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v,kvi, cb);\n }\n \n-static int write_one_config(const char *key, const char *value, void *data)\n+static int write_one_config(const char *key, const char *value,\n+\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n {\n \t/*\n \t * give git_clone_config a chance to write config values back to the\ndiff --git a/builtin/column.c b/builtin/column.c\nindex de623a16c2d..30cfbed62ec 100644\n--- a/builtin/column.c\n+++ b/builtin/column.c\n@@ -13,7 +13,8 @@ static const char * const builtin_column_usage[] = {\n };\n static unsigned int colopts;\n \n-static int column_config(const char *var, const char *value, void *cb)\n+static int column_config(const char *var, const char *value,\n+\t\t\t struct key_value_info *kvi UNUSED, void *cb)\n {\n \treturn git_column_config(var, value, cb, &colopts);\n }\ndiff --git a/builtin/commit-graph.c b/builtin/commit-graph.c\nindex 90114269761..e811866b5dd 100644\n--- a/builtin/commit-graph.c\n+++ b/builtin/commit-graph.c\n@@ -185,6 +185,7 @@ static int write_option_max_new_filters(const struct option *opt,\n }\n \n static int git_commit_graph_write_config(const char *var, const char *value,\n+\t\t\t\t\t struct key_value_info *kvi UNUSED,\n \t\t\t\t\t void *cb UNUSED)\n {\n \tif (!strcmp(var, \"commitgraph.maxnewfilters\"))\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 9d8e1ea91a3..ec468e87039 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1402,7 +1402,8 @@ static int parse_status_slot(const char *slot)\n \treturn LOOKUP_CONFIG(color_status_slots, slot);\n }\n \n-static int git_status_config(const char *k, const char *v, void *cb)\n+static int git_status_config(const char *k, const char *v,\n+\t\t\t     struct key_value_info *kvi, void *cb)\n {\n \tstruct wt_status *s = cb;\n \tconst char *slot_name;\n@@ -1487,7 +1488,7 @@ static int git_status_config(const char *k, const char *v, void *cb)\n \t\ts->detect_rename = git_config_rename(k, v);\n \t\treturn 0;\n \t}\n-\treturn git_diff_ui_config(k, v, NULL);\n+\treturn git_diff_ui_config(k, v,kvi, NULL);\n }\n \n int cmd_status(int argc, const char **argv, const char *prefix)\n@@ -1602,7 +1603,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)\n \treturn 0;\n }\n \n-static int git_commit_config(const char *k, const char *v, void *cb)\n+static int git_commit_config(const char *k, const char *v,\n+\t\t\t     struct key_value_info *kvi, void *cb)\n {\n \tstruct wt_status *s = cb;\n \n@@ -1624,7 +1626,7 @@ static int git_commit_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_status_config(k, v, s);\n+\treturn git_status_config(k, v,kvi, s);\n }\n \n int cmd_commit(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/config.c b/builtin/config.c\nindex fe79fb60c43..b2ad7351d0a 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -214,7 +214,7 @@ static void show_config_scope(struct strbuf *buf)\n }\n \n static int show_all_config(const char *key_, const char *value_,\n-\t\t\t   void *cb UNUSED)\n+\t\t\t   struct key_value_info *kvi UNUSED, void *cb UNUSED)\n {\n \tif (show_origin || show_scope) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n@@ -298,7 +298,8 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value\n \treturn 0;\n }\n \n-static int collect_config(const char *key_, const char *value_, void *cb)\n+static int collect_config(const char *key_, const char *value_,\n+\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct strbuf_list *values = cb;\n \n@@ -466,6 +467,7 @@ static const char *get_colorbool_slot;\n static char parsed_color[COLOR_MAXLEN];\n \n static int git_get_color_config(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi UNUSED,\n \t\t\t\tvoid *cb UNUSED)\n {\n \tif (!strcmp(var, get_color_slot)) {\n@@ -498,6 +500,7 @@ static int get_colorbool_found;\n static int get_diff_color_found;\n static int get_color_ui_found;\n static int git_get_colorbool_config(const char *var, const char *value,\n+\t\t\t\t    struct key_value_info *kvi UNUSED,\n \t\t\t\t    void *data UNUSED)\n {\n \tif (!strcmp(var, get_colorbool_slot))\n@@ -555,7 +558,8 @@ struct urlmatch_current_candidate_value {\n \tstruct strbuf value;\n };\n \n-static int urlmatch_collect_fn(const char *var, const char *value, void *cb)\n+static int urlmatch_collect_fn(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct string_list *values = cb;\n \tstruct string_list_item *item = string_list_insert(values, var);\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex e010a21bfbc..d4d149bcf6b 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -38,14 +38,15 @@ static const char *const builtin_difftool_usage[] = {\n \tNULL\n };\n \n-static int difftool_config(const char *var, const char *value, void *cb)\n+static int difftool_config(const char *var, const char *value,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"difftool.trustexitcode\")) {\n \t\ttrust_exit_code = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static int print_tool_help(void)\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 85bd2801036..aa688291613 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -100,7 +100,8 @@ static int fetch_write_commit_graph = -1;\n static int stdin_refspecs = 0;\n static int negotiate_only;\n \n-static int git_fetch_config(const char *k, const char *v, void *cb)\n+static int git_fetch_config(const char *k, const char *v,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(k, \"fetch.prune\")) {\n \t\tfetch_prune_config = git_config_bool(k, v);\n@@ -140,7 +141,7 @@ static int git_fetch_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v,kvi, cb);\n }\n \n static int parse_refmap_arg(const struct option *opt, const char *arg, int unset)\n@@ -1828,7 +1829,8 @@ struct remote_group_data {\n \tstruct string_list *list;\n };\n \n-static int get_remote_group(const char *key, const char *value, void *priv)\n+static int get_remote_group(const char *key, const char *value,\n+\t\t\t    struct key_value_info *kvi UNUSED, void *priv)\n {\n \tstruct remote_group_data *g = priv;\n \ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex 42af6a2cc7e..a7375d61d02 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -36,7 +36,8 @@ static int fsmonitor__start_timeout_sec = 60;\n #define FSMONITOR__ANNOUNCE_STARTUP \"fsmonitor.announcestartup\"\n static int fsmonitor__announce_startup = 0;\n \n-static int fsmonitor_config(const char *var, const char *value, void *cb)\n+static int fsmonitor_config(const char *var, const char *value,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, FSMONITOR__IPC_THREADS)) {\n \t\tint i = git_config_int(var, value);\n@@ -66,7 +67,7 @@ static int fsmonitor_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n /*\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex c880c9538d6..177befc3ed4 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -287,13 +287,14 @@ static int wait_all(void)\n \treturn hit;\n }\n \n-static int grep_cmd_config(const char *var, const char *value, void *cb)\n+static int grep_cmd_config(const char *var, const char *value,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tint st = grep_config(var, value, cb);\n \n \tif (git_color_config(var, value, cb) < 0)\n \t\tst = -1;\n-\telse if (git_default_config(var, value, cb) < 0)\n+\telse if (git_default_config(var, value,kvi, cb) < 0)\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\ndiff --git a/builtin/help.c b/builtin/help.c\nindex 87333a02ec4..5d4a86c4b41 100644\n--- a/builtin/help.c\n+++ b/builtin/help.c\n@@ -396,7 +396,8 @@ static int add_man_viewer_info(const char *var, const char *value)\n \treturn 0;\n }\n \n-static int git_help_config(const char *var, const char *value, void *cb)\n+static int git_help_config(const char *var, const char *value,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"help.format\")) {\n \t\tif (!value)\n@@ -419,7 +420,7 @@ static int git_help_config(const char *var, const char *value, void *cb)\n \tif (starts_with(var, \"man.\"))\n \t\treturn add_man_viewer_info(var, value);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static struct cmdnames main_cmds, other_cmds;\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex b17e79cd40f..4450510ddfc 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1578,7 +1578,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name,\n \tstrbuf_release(&pack_name);\n }\n \n-static int git_index_pack_config(const char *k, const char *v, void *cb)\n+static int git_index_pack_config(const char *k, const char *v,\n+\t\t\t\t struct key_value_info *kvi, void *cb)\n {\n \tstruct pack_idx_option *opts = cb;\n \n@@ -1605,7 +1606,7 @@ static int git_index_pack_config(const char *k, const char *v, void *cb)\n \t\telse\n \t\t\topts->flags &= ~WRITE_REV;\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v,kvi, cb);\n }\n \n static int cmp_uint32(const void *a_, const void *b_)\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 7d195789633..f8e61330491 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -559,7 +559,8 @@ static int cmd_log_walk(struct rev_info *rev)\n \treturn retval;\n }\n \n-static int git_log_config(const char *var, const char *value, void *cb)\n+static int git_log_config(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -608,7 +609,7 @@ static int git_log_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_diff_ui_config(var, value, cb);\n+\treturn git_diff_ui_config(var, value,kvi, cb);\n }\n \n int cmd_whatchanged(int argc, const char **argv, const char *prefix)\n@@ -974,7 +975,8 @@ static enum cover_from_description parse_cover_from_description(const char *arg)\n \t\tdie(_(\"%s: invalid cover from description mode\"), arg);\n }\n \n-static int git_format_config(const char *var, const char *value, void *cb)\n+static int git_format_config(const char *var, const char *value,\n+\t\t\t     struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"format.headers\")) {\n \t\tif (!value)\n@@ -1103,7 +1105,7 @@ static int git_format_config(const char *var, const char *value, void *cb)\n \tif (!strcmp(var, \"diff.noprefix\"))\n \t\treturn 0;\n \n-\treturn git_log_config(var, value, cb);\n+\treturn git_log_config(var, value,kvi, cb);\n }\n \n static const char *output_directory = NULL;\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex a99be9610e9..492a83a900c 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -620,7 +620,8 @@ static void parse_branch_merge_options(char *bmo)\n \tfree(argv);\n }\n \n-static int git_merge_config(const char *k, const char *v, void *cb)\n+static int git_merge_config(const char *k, const char *v,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tint status;\n \tconst char *str;\n@@ -665,10 +666,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\tstatus = fmt_merge_msg_config(k, v, cb);\n+\tstatus = fmt_merge_msg_config(k, v,kvi, cb);\n \tif (status)\n \t\treturn status;\n-\treturn git_diff_ui_config(k, v, cb);\n+\treturn git_diff_ui_config(k, v,kvi, cb);\n }\n \n static int read_tree_trivial(struct object_id *common, struct object_id *head,\ndiff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\nindex 1b5083f8b26..c3cd7163c84 100644\n--- a/builtin/multi-pack-index.c\n+++ b/builtin/multi-pack-index.c\n@@ -82,6 +82,7 @@ static struct option *add_common_options(struct option *prev)\n }\n \n static int git_multi_pack_index_write_config(const char *var, const char *value,\n+\t\t\t\t\t     struct key_value_info *kvi UNUSED,\n \t\t\t\t\t     void *cb UNUSED)\n {\n \tif (!strcmp(var, \"pack.writebitmaphashcache\")) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 77d88f85b04..ca023000cc0 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3134,7 +3134,8 @@ static void prepare_pack(int window, int depth)\n \tfree(delta_list);\n }\n \n-static int git_pack_config(const char *k, const char *v, void *cb)\n+static int git_pack_config(const char *k, const char *v,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(k, \"pack.window\")) {\n \t\twindow = git_config_int(k, v);\n@@ -3226,7 +3227,7 @@ static int git_pack_config(const char *k, const char *v, void *cb)\n \t\tex->uri = xstrdup(pack_end + 1);\n \t\toidmap_put(&configured_exclusions, ex);\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v,kvi, cb);\n }\n \n /* Counters for trace2 output when in --stdin-packs mode. */\ndiff --git a/builtin/patch-id.c b/builtin/patch-id.c\nindex 9d5585d3a72..9b4f5a71b87 100644\n--- a/builtin/patch-id.c\n+++ b/builtin/patch-id.c\n@@ -196,7 +196,8 @@ struct patch_id_opts {\n \tint verbatim;\n };\n \n-static int git_patch_id_config(const char *var, const char *value, void *cb)\n+static int git_patch_id_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi, void *cb)\n {\n \tstruct patch_id_opts *opts = cb;\n \n@@ -209,7 +210,7 @@ static int git_patch_id_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n int cmd_patch_id(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 5405d09f22f..1b244eee67c 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -359,7 +359,8 @@ static enum rebase_type config_get_rebase(int *rebase_unspecified)\n /**\n  * Read config variables.\n  */\n-static int git_pull_config(const char *var, const char *value, void *cb)\n+static int git_pull_config(const char *var, const char *value,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"rebase.autostash\")) {\n \t\tconfig_autostash = git_config_bool(var, value);\n@@ -372,7 +373,7 @@ static int git_pull_config(const char *var, const char *value, void *cb)\n \t\tcheck_trust_level = 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n /**\ndiff --git a/builtin/push.c b/builtin/push.c\nindex fa550b8f80a..65b79378b92 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -506,7 +506,8 @@ static void set_push_cert_flags(int *flags, int v)\n }\n \n \n-static int git_push_config(const char *k, const char *v, void *cb)\n+static int git_push_config(const char *k, const char *v,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tconst char *slot_name;\n \tint *flags = cb;\n@@ -573,7 +574,7 @@ static int git_push_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(k, v, NULL);\n+\treturn git_default_config(k, v,kvi, NULL);\n }\n \n int cmd_push(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/read-tree.c b/builtin/read-tree.c\nindex 600d4f748fc..e9859b6157e 100644\n--- a/builtin/read-tree.c\n+++ b/builtin/read-tree.c\n@@ -100,12 +100,13 @@ static int debug_merge(const struct cache_entry * const *stages,\n \treturn 0;\n }\n \n-static int git_read_tree_config(const char *var, const char *value, void *cb)\n+static int git_read_tree_config(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"submodule.recurse\"))\n \t\treturn git_default_submodule_config(var, value, cb);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 680fe3c1453..1fd05d708b8 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -791,7 +791,8 @@ static void parse_rebase_merges_value(struct rebase_options *options, const char\n \t\tdie(_(\"Unknown rebase-merges mode: %s\"), value);\n }\n \n-static int rebase_config(const char *var, const char *value, void *data)\n+static int rebase_config(const char *var, const char *value,\n+\t\t\t struct key_value_info *kvi, void *data)\n {\n \tstruct rebase_options *opts = data;\n \n@@ -850,7 +851,7 @@ static int rebase_config(const char *var, const char *value, void *data)\n \t\treturn git_config_string(&opts->default_backend, var, value);\n \t}\n \n-\treturn git_default_config(var, value, data);\n+\treturn git_default_config(var, value,kvi, data);\n }\n \n static int checkout_up_to_date(struct rebase_options *options)\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 9109552533d..2f5fd2abbc3 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -135,7 +135,8 @@ static enum deny_action parse_deny_action(const char *var, const char *value)\n \treturn DENY_IGNORE;\n }\n \n-static int receive_pack_config(const char *var, const char *value, void *cb)\n+static int receive_pack_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi, void *cb)\n {\n \tint status = parse_hide_refs_config(var, value, \"receive\", &hidden_refs);\n \n@@ -262,7 +263,7 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static void show_ref(const char *path, const struct object_id *oid)\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex a1fa0c855f4..ecf21ac9c6e 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -108,7 +108,8 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)\n #define EXPIRE_TOTAL   01\n #define EXPIRE_UNREACH 02\n \n-static int reflog_expire_config(const char *var, const char *value, void *cb)\n+static int reflog_expire_config(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi, void *cb)\n {\n \tconst char *pattern, *key;\n \tsize_t pattern_len;\n@@ -117,7 +118,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)\n \tstruct reflog_expire_cfg *ent;\n \n \tif (parse_config_key(var, \"gc\", &pattern, &pattern_len, &key) < 0)\n-\t\treturn git_default_config(var, value, cb);\n+\t\treturn git_default_config(var, value,kvi, cb);\n \n \tif (!strcmp(key, \"reflogexpire\")) {\n \t\tslot = EXPIRE_TOTAL;\n@@ -128,7 +129,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)\n \t\tif (git_config_expiry_date(&expire, var, value))\n \t\t\treturn -1;\n \t} else\n-\t\treturn git_default_config(var, value, cb);\n+\t\treturn git_default_config(var, value,kvi, cb);\n \n \tif (!pattern) {\n \t\tswitch (slot) {\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 1e0b137d977..edb4a9ddd7f 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -268,6 +268,7 @@ static const char *abbrev_ref(const char *name, const char *prefix)\n #define abbrev_branch(name) abbrev_ref((name), \"refs/heads/\")\n \n static int config_read_branches(const char *key, const char *value,\n+\t\t\t\tstruct key_value_info *kvi UNUSED,\n \t\t\t\tvoid *data UNUSED)\n {\n \tconst char *orig_key = key;\n@@ -645,7 +646,7 @@ struct push_default_info\n };\n \n static int config_read_push_default(const char *key, const char *value,\n-\tvoid *cb)\n+\tstruct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct push_default_info* info = cb;\n \tif (strcmp(key, \"remote.pushdefault\") ||\n@@ -1494,7 +1495,8 @@ static int prune(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n-static int get_remote_default(const char *key, const char *value UNUSED, void *priv)\n+static int get_remote_default(const char *key, const char *value UNUSED,\n+\t\t\t      struct key_value_info *kvi UNUSED, void *priv)\n {\n \tif (strcmp(key, \"remotes.default\") == 0) {\n \t\tint *found = priv;\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex df4d8e0f0ba..7c8401b2227 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -58,7 +58,8 @@ struct pack_objects_args {\n \tint local;\n };\n \n-static int repack_config(const char *var, const char *value, void *cb)\n+static int repack_config(const char *var, const char *value,\n+\t\t\t struct key_value_info *kvi, void *cb)\n {\n \tstruct pack_objects_args *cruft_po_args = cb;\n \tif (!strcmp(var, \"repack.usedeltabaseoffset\")) {\n@@ -90,7 +91,7 @@ static int repack_config(const char *var, const char *value, void *cb)\n \t\treturn git_config_string(&cruft_po_args->depth, var, value);\n \tif (!strcmp(var, \"repack.cruftthreads\"))\n \t\treturn git_config_string(&cruft_po_args->threads, var, value);\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n /*\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex 0ed329236c8..a04d46d7fdd 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -308,12 +308,13 @@ static int reset_refs(const char *rev, const struct object_id *oid)\n \treturn update_ref_status;\n }\n \n-static int git_reset_config(const char *var, const char *value, void *cb)\n+static int git_reset_config(const char *var, const char *value,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"submodule.recurse\"))\n \t\treturn git_default_submodule_config(var, value, cb);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n int cmd_reset(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 4784143004d..b0c90e549b8 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -131,7 +131,8 @@ static void print_helper_status(struct ref *ref)\n \tstrbuf_release(&buf);\n }\n \n-static int send_pack_config(const char *k, const char *v, void *cb)\n+static int send_pack_config(const char *k, const char *v,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(k, \"push.gpgsign\")) {\n \t\tconst char *value;\n@@ -151,7 +152,7 @@ static int send_pack_config(const char *k, const char *v, void *cb)\n \t\t\t}\n \t\t}\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v,kvi, cb);\n }\n \n int cmd_send_pack(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex 82ae2a7e475..ad8a391904e 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -556,7 +556,8 @@ static void append_one_rev(const char *av)\n \tdie(\"bad sha1 reference %s\", av);\n }\n \n-static int git_show_branch_config(const char *var, const char *value, void *cb)\n+static int git_show_branch_config(const char *var, const char *value,\n+\t\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"showbranch.default\")) {\n \t\tif (!value)\n@@ -579,7 +580,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static int omit_in_dense(struct commit *commit, struct commit **rev, int n)\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 735d27039e1..689087d6240 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -836,7 +836,8 @@ static int show_stat = 1;\n static int show_patch;\n static int show_include_untracked;\n \n-static int git_stash_config(const char *var, const char *value, void *cb)\n+static int git_stash_config(const char *var, const char *value,\n+\t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"stash.showstat\")) {\n \t\tshow_stat = git_config_bool(var, value);\n@@ -850,7 +851,7 @@ static int git_stash_config(const char *var, const char *value, void *cb)\n \t\tshow_include_untracked = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\treturn git_diff_basic_config(var, value, cb);\n+\treturn git_diff_basic_config(var, value,kvi, cb);\n }\n \n static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 569068e6a2c..8570effbf0d 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2187,6 +2187,7 @@ static int update_clone_task_finished(int result,\n }\n \n static int git_update_clone_config(const char *var, const char *value,\n+\t\t\t\t   struct key_value_info *kvi UNUSED,\n \t\t\t\t   void *cb)\n {\n \tint *max_jobs = cb;\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 7245a4d30e6..626fe8c641e 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -183,7 +183,8 @@ static const char tag_template_nocleanup[] =\n \t\"Lines starting with '%c' will be kept; you may remove them\"\n \t\" yourself if you want to.\\n\");\n \n-static int git_tag_config(const char *var, const char *value, void *cb)\n+static int git_tag_config(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"tag.gpgsign\")) {\n \t\tconfig_sign_tag = git_config_bool(var, value);\n@@ -208,7 +209,7 @@ static int git_tag_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static void write_tag_body(int fd, const struct object_id *oid)\ndiff --git a/builtin/var.c b/builtin/var.c\nindex acb988d2d56..440add19bb5 100644\n--- a/builtin/var.c\n+++ b/builtin/var.c\n@@ -69,13 +69,14 @@ static const struct git_var *get_git_var(const char *var)\n \treturn NULL;\n }\n \n-static int show_config(const char *var, const char *value, void *cb)\n+static int show_config(const char *var, const char *value,\n+\t\t       struct key_value_info *kvi, void *cb)\n {\n \tif (value)\n \t\tprintf(\"%s=%s\\n\", var, value);\n \telse\n \t\tprintf(\"%s\\n\", var);\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n int cmd_var(int argc, const char **argv, const char *prefix UNUSED)\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 39e9e5c9ce8..d5ccd741e87 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -103,14 +103,15 @@ static int verbose;\n static int guess_remote;\n static timestamp_t expire;\n \n-static int git_worktree_config(const char *var, const char *value, void *cb)\n+static int git_worktree_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"worktree.guessremote\")) {\n \t\tguess_remote = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static int delete_git_dir(const char *id)\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex e2b267cc02b..7f01fbbff74 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -223,7 +223,9 @@ static int bundle_list_update(const char *key, const char *value,\n \treturn 0;\n }\n \n-static int config_to_bundle_list(const char *key, const char *value, void *data)\n+static int config_to_bundle_list(const char *key, const char *value,\n+\t\t\t\t struct key_value_info *kvi UNUSED,\n+\t\t\t\t void *data)\n {\n \tstruct bundle_list *list = data;\n \treturn bundle_list_update(key, value, list);\n@@ -870,7 +872,9 @@ cached:\n \treturn advertise_bundle_uri;\n }\n \n-static int config_to_packet_line(const char *key, const char *value, void *data)\n+static int config_to_packet_line(const char *key, const char *value,\n+\t\t\t\t struct key_value_info *kvi UNUSED,\n+\t\t\t\t void *data)\n {\n \tstruct packet_reader *writer = data;\n \ndiff --git a/config.c b/config.c\nindex 945f4f3b77e..1bc2e35a3e0 100644\n--- a/config.c\n+++ b/config.c\n@@ -201,7 +201,8 @@ struct config_include_data {\n };\n #define CONFIG_INCLUDE_INIT { 0 }\n \n-static int git_config_include(const char *var, const char *value, void *data);\n+static int git_config_include(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi, void *data);\n \n #define MAX_INCLUDE_DEPTH 10\n static const char include_depth_advice[] = N_(\n@@ -380,7 +381,8 @@ static int include_by_branch(const char *cond, size_t cond_len)\n \treturn ret;\n }\n \n-static int add_remote_url(const char *var, const char *value, void *data)\n+static int add_remote_url(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct string_list *remote_urls = data;\n \tconst char *remote_name;\n@@ -414,6 +416,7 @@ static void populate_remote_urls(struct config_include_data *inc)\n }\n \n static int forbid_remote_url(const char *var, const char *value UNUSED,\n+\t\t\t     struct key_value_info *kvi UNUSED,\n \t\t\t     void *data UNUSED)\n {\n \tconst char *remote_name;\n@@ -477,7 +480,8 @@ static int include_condition_is_true(struct config_source *cs,\n \treturn 0;\n }\n \n-static int git_config_include(const char *var, const char *value, void *data)\n+static int git_config_include(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct config_include_data *inc = data;\n \tstruct config_source *cs = inc->config_reader->source;\n@@ -1553,7 +1557,8 @@ int git_config_color(char *dest, const char *var, const char *value)\n \treturn 0;\n }\n \n-static int git_default_core_config(const char *var, const char *value, void *cb)\n+static int git_default_core_config(const char *var, const char *value,\n+\t\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \t/* This needs a better name */\n \tif (!strcmp(var, \"core.filemode\")) {\n@@ -1838,7 +1843,7 @@ static int git_default_core_config(const char *var, const char *value, void *cb)\n \t}\n \n \t/* Add other config variables here and to Documentation/config.txt. */\n-\treturn platform_core_config(var, value, cb);\n+\treturn platform_core_config(var, value,kvi, cb);\n }\n \n static int git_default_sparse_config(const char *var, const char *value)\n@@ -1940,15 +1945,16 @@ static int git_default_mailmap_config(const char *var, const char *value)\n \treturn 0;\n }\n \n-int git_default_config(const char *var, const char *value, void *cb)\n+int git_default_config(const char *var, const char *value,\n+\t\t       struct key_value_info *kvi, void *cb)\n {\n \tif (starts_with(var, \"core.\"))\n-\t\treturn git_default_core_config(var, value, cb);\n+\t\treturn git_default_core_config(var, value,kvi, cb);\n \n \tif (starts_with(var, \"user.\") ||\n \t    starts_with(var, \"author.\") ||\n \t    starts_with(var, \"committer.\"))\n-\t\treturn git_ident_config(var, value, cb);\n+\t\treturn git_ident_config(var, value,kvi, cb);\n \n \tif (starts_with(var, \"i18n.\"))\n \t\treturn git_default_i18n_config(var, value);\n@@ -2481,7 +2487,8 @@ struct configset_add_data {\n };\n #define CONFIGSET_ADD_INIT { 0 }\n \n-static int config_set_callback(const char *key, const char *value, void *cb)\n+static int config_set_callback(const char *key, const char *value,\n+\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct configset_add_data *data = cb;\n \tconfigset_add_value(data->config_reader, data->config_set, key, value);\n@@ -3091,7 +3098,8 @@ static int store_aux_event(enum config_event_t type,\n \treturn 0;\n }\n \n-static int store_aux(const char *key, const char *value, void *cb)\n+static int store_aux(const char *key, const char *value,\n+\t\t     struct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct config_store_data *store = cb;\n \ndiff --git a/config.h b/config.h\nindex 9d052c52c3c..9e0c1f12165 100644\n--- a/config.h\n+++ b/config.h\n@@ -138,7 +138,8 @@ struct key_value_info {\n  */\n typedef int (*config_fn_t)(const char *, const char *, struct key_value_info *, void *);\n \n-int git_default_config(const char *, const char *, void *);\n+int git_default_config(const char *, const char *, struct key_value_info *,\n+\t\t       void *);\n \n /**\n  * Read a specific file in git-config format.\ndiff --git a/connect.c b/connect.c\nindex c0c8a38178c..72aad67a288 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -962,7 +962,7 @@ static struct child_process *git_tcp_connect(int fd[2], char *host, int flags)\n static char *git_proxy_command;\n \n static int git_proxy_command_options(const char *var, const char *value,\n-\t\tvoid *cb)\n+\t\tstruct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"core.gitproxy\")) {\n \t\tconst char *for_pos;\n@@ -1008,7 +1008,7 @@ static int git_proxy_command_options(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static int git_use_proxy(const char *host)\ndiff --git a/convert.c b/convert.c\nindex da06e2f51cb..a563380e7e7 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1011,7 +1011,9 @@ static int apply_filter(const char *path, const char *src, size_t len,\n \treturn 0;\n }\n \n-static int read_convert_config(const char *var, const char *value, void *cb UNUSED)\n+static int read_convert_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi UNUSED,\n+\t\t\t       void *cb UNUSED)\n {\n \tconst char *key, *name;\n \tsize_t namelen;\ndiff --git a/credential.c b/credential.c\nindex e6417bf8804..6b9b8bfbb79 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -46,6 +46,7 @@ static int credential_from_potentially_partial_url(struct credential *c,\n \t\t\t\t\t\t   const char *url);\n \n static int credential_config_callback(const char *var, const char *value,\n+\t\t\t\t      struct key_value_info *kvi UNUSED,\n \t\t\t\t      void *data)\n {\n \tstruct credential *c = data;\ndiff --git a/delta-islands.c b/delta-islands.c\nindex 40f2ccfb550..404f9c07a42 100644\n--- a/delta-islands.c\n+++ b/delta-islands.c\n@@ -341,7 +341,8 @@ static void free_remote_islands(kh_str_t *remote_islands)\n \tkh_destroy_str(remote_islands);\n }\n \n-static int island_config_callback(const char *k, const char *v, void *cb)\n+static int island_config_callback(const char *k, const char *v,\n+\t\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct island_load_data *ild = cb;\n \ndiff --git a/diff.c b/diff.c\nindex 78b0fdd8caa..d7ed2dc900b 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -350,7 +350,8 @@ static unsigned parse_color_moved_ws(const char *arg)\n \treturn ret;\n }\n \n-int git_diff_ui_config(const char *var, const char *value, void *cb)\n+int git_diff_ui_config(const char *var, const char *value,\n+\t\t       struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"diff.color\") || !strcmp(var, \"color.diff\")) {\n \t\tdiff_use_color_default = git_config_colorbool(var, value);\n@@ -433,10 +434,11 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_diff_basic_config(var, value, cb);\n+\treturn git_diff_basic_config(var, value,kvi, cb);\n }\n \n-int git_diff_basic_config(const char *var, const char *value, void *cb)\n+int git_diff_basic_config(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tconst char *name;\n \n@@ -488,7 +490,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)\n \tif (git_diff_heuristic_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static char *quote_two(const char *one, const char *two)\ndiff --git a/diff.h b/diff.h\nindex 6a0737b9c34..6a3efa63753 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -532,10 +532,12 @@ void free_diffstat_info(struct diffstat_t *diffstat);\n int parse_long_opt(const char *opt, const char **argv,\n \t\t   const char **optarg);\n \n-int git_diff_basic_config(const char *var, const char *value, void *cb);\n+int git_diff_basic_config(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi, void *cb);\n int git_diff_heuristic_config(const char *var, const char *value, void *cb);\n void init_diff_ui_defaults(void);\n-int git_diff_ui_config(const char *var, const char *value, void *cb);\n+int git_diff_ui_config(const char *var, const char *value,\n+\t\t       struct key_value_info *kvi, void *cb);\n void repo_diff_setup(struct repository *, struct diff_options *);\n struct option *add_diff_options(const struct option *, struct diff_options *);\n int diff_opt_parse(struct diff_options *, const char **, int, const char *);\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 368f2ed25a1..c1072aa6cac 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1858,7 +1858,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \treturn ref;\n }\n \n-static int fetch_pack_config_cb(const char *var, const char *value, void *cb)\n+static int fetch_pack_config_cb(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi, void *cb)\n {\n \tif (strcmp(var, \"fetch.fsck.skiplist\") == 0) {\n \t\tconst char *path;\n@@ -1880,7 +1881,7 @@ static int fetch_pack_config_cb(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n static void fetch_pack_config(void)\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 1886c92ddb9..97358034fa0 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -19,7 +19,8 @@ static int use_branch_desc;\n static int suppress_dest_pattern_seen;\n static struct string_list suppress_dest_patterns = STRING_LIST_INIT_DUP;\n \n-int fmt_merge_msg_config(const char *key, const char *value, void *cb)\n+int fmt_merge_msg_config(const char *key, const char *value,\n+\t\t\t struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(key, \"merge.log\") || !strcmp(key, \"merge.summary\")) {\n \t\tint is_bool;\n@@ -39,7 +40,7 @@ int fmt_merge_msg_config(const char *key, const char *value, void *cb)\n \t\t\tstring_list_append(&suppress_dest_patterns, value);\n \t\tsuppress_dest_pattern_seen = 1;\n \t} else {\n-\t\treturn git_default_config(key, value, cb);\n+\t\treturn git_default_config(key, value,kvi, cb);\n \t}\n \treturn 0;\n }\ndiff --git a/fmt-merge-msg.h b/fmt-merge-msg.h\nindex 99054042dc5..5262c39268a 100644\n--- a/fmt-merge-msg.h\n+++ b/fmt-merge-msg.h\n@@ -13,7 +13,8 @@ struct fmt_merge_msg_opts {\n };\n \n extern int merge_log_config;\n-int fmt_merge_msg_config(const char *key, const char *value, void *cb);\n+int fmt_merge_msg_config(const char *key, const char *value,\n+\t\t\t struct key_value_info *kvi, void *cb);\n int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \t\t  struct fmt_merge_msg_opts *);\n \ndiff --git a/fsck.c b/fsck.c\nindex 8ef1b022346..1569e64f011 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1162,7 +1162,8 @@ struct fsck_gitmodules_data {\n \tint ret;\n };\n \n-static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)\n+static int fsck_gitmodules_fn(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi UNUSED, void *vdata)\n {\n \tstruct fsck_gitmodules_data *data = vdata;\n \tconst char *subsection, *key;\n@@ -1372,7 +1373,8 @@ int fsck_finish(struct fsck_options *options)\n \treturn ret;\n }\n \n-int git_fsck_config(const char *var, const char *value, void *cb)\n+int git_fsck_config(const char *var, const char *value,\n+\t\t    struct key_value_info *kvi, void *cb)\n {\n \tstruct fsck_options *options = cb;\n \tif (strcmp(var, \"fsck.skiplist\") == 0) {\n@@ -1393,7 +1395,7 @@ int git_fsck_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\n \n /*\ndiff --git a/fsck.h b/fsck.h\nindex e17730e9da9..a06f202576c 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -237,6 +237,7 @@ const char *fsck_describe_object(struct fsck_options *options,\n  * git_config() callback for use by fsck-y tools that want to support\n  * fsck.<msg> fsck.skipList etc.\n  */\n-int git_fsck_config(const char *var, const char *value, void *cb);\n+int git_fsck_config(const char *var, const char *value,\n+\t\t    struct key_value_info *kvi, void *cb);\n \n #endif\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex aceeb083367..5ed89ef69ed 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -12,7 +12,8 @@\n #include \"alias.h\"\n #include \"wrapper.h\"\n \n-static int git_gpg_config(const char *, const char *, void *);\n+static int git_gpg_config(const char *, const char *, struct key_value_info *,\n+\t\t\t  void *);\n \n static void gpg_interface_lazy_init(void)\n {\n@@ -718,7 +719,8 @@ void set_signing_key(const char *key)\n \tconfigured_signing_key = xstrdup(key);\n }\n \n-static int git_gpg_config(const char *var, const char *value, void *cb UNUSED)\n+static int git_gpg_config(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi UNUSED, void *cb UNUSED)\n {\n \tstruct gpg_format *fmt = NULL;\n \tchar *fmtname = NULL;\ndiff --git a/grep.c b/grep.c\nindex b86462a12a9..1516b0689d0 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -54,7 +54,8 @@ define_list_config_array_extra(color_grep_slots, {\"match\"});\n  * Read the configuration file once and store it in\n  * the grep_defaults template.\n  */\n-int grep_config(const char *var, const char *value, void *cb)\n+int grep_config(const char *var, const char *value,\n+\t\tstruct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct grep_opt *opt = cb;\n \tconst char *slot;\ndiff --git a/help.c b/help.c\nindex 5d7637dce92..43d1eb702cd 100644\n--- a/help.c\n+++ b/help.c\n@@ -309,7 +309,8 @@ void load_command_list(const char *prefix,\n \texclude_cmds(other_cmds, main_cmds);\n }\n \n-static int get_colopts(const char *var, const char *value, void *data)\n+static int get_colopts(const char *var, const char *value,\n+\t\t       struct key_value_info *kvi UNUSED, void *data)\n {\n \tunsigned int *colopts = data;\n \n@@ -459,7 +460,8 @@ void list_developer_interfaces_help(void)\n \tputchar('\\n');\n }\n \n-static int get_alias(const char *var, const char *value, void *data)\n+static int get_alias(const char *var, const char *value,\n+\t\t     struct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct string_list *list = data;\n \n@@ -543,6 +545,7 @@ static struct cmdnames aliases;\n #define AUTOCORRECT_IMMEDIATELY (-1)\n \n static int git_unknown_cmd_config(const char *var, const char *value,\n+\t\t\t\t  struct key_value_info *kvi UNUSED,\n \t\t\t\t  void *cb UNUSED)\n {\n \tconst char *p;\ndiff --git a/ident.c b/ident.c\nindex 8fad92d7007..21b7b3ff35b 100644\n--- a/ident.c\n+++ b/ident.c\n@@ -671,7 +671,8 @@ static int set_ident(const char *var, const char *value)\n \treturn 0;\n }\n \n-int git_ident_config(const char *var, const char *value, void *data UNUSED)\n+int git_ident_config(const char *var, const char *value,\n+\t\t     struct key_value_info *kvi UNUSED, void *data UNUSED)\n {\n \tif (!strcmp(var, \"user.useconfigonly\")) {\n \t\tident_use_config_only = git_config_bool(var, value);\ndiff --git a/ident.h b/ident.h\nindex 96a64896a01..f55db41ff99 100644\n--- a/ident.h\n+++ b/ident.h\n@@ -62,6 +62,7 @@ const char *fmt_name(enum want_ident);\n int committer_ident_sufficiently_given(void);\n int author_ident_sufficiently_given(void);\n \n-int git_ident_config(const char *, const char *, void *);\n+int git_ident_config(const char *, const char *,\n+\t\t     struct key_value_info *UNUSED, void *);\n \n #endif\ndiff --git a/imap-send.c b/imap-send.c\nindex a62424e90a4..3cc98f1a0a5 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1322,7 +1322,8 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)\n \treturn 1;\n }\n \n-static int git_imap_config(const char *var, const char *val, void *cb)\n+static int git_imap_config(const char *var, const char *val,\n+\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \n \tif (!strcmp(\"imap.sslverify\", var))\n@@ -1356,7 +1357,7 @@ static int git_imap_config(const char *var, const char *val, void *cb)\n \t\t\tserver.host = xstrdup(val);\n \t\t}\n \t} else\n-\t\treturn git_default_config(var, val, cb);\n+\t\treturn git_default_config(var, val,kvi, cb);\n \n \treturn 0;\n }\ndiff --git a/ll-merge.c b/ll-merge.c\nindex 8be38d3bd41..2d240609ddf 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -252,6 +252,7 @@ static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;\n static const char *default_ll_merge;\n \n static int read_merge_config(const char *var, const char *value,\n+\t\t\t     struct key_value_info *kvi UNUSED,\n \t\t\t     void *cb UNUSED)\n {\n \tstruct ll_merge_driver *fn;\ndiff --git a/ls-refs.c b/ls-refs.c\nindex b9f3e08ec3d..90b902c84f8 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -136,7 +136,7 @@ static void send_possibly_unborn_head(struct ls_refs_data *data)\n }\n \n static int ls_refs_config(const char *var, const char *value,\n-\t\t\t  void *cb_data)\n+\t\t\t  struct key_value_info *kvi UNUSED, void *cb_data)\n {\n \tstruct ls_refs_data *data = cb_data;\n \t/*\ndiff --git a/mailinfo.c b/mailinfo.c\nindex 2aeb20e5e62..e2f469c96f7 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -1241,12 +1241,13 @@ int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)\n \treturn 0;\n }\n \n-static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n+static int git_mailinfo_config(const char *var, const char *value,\n+\t\t\t       struct key_value_info *kvi, void *mi_)\n {\n \tstruct mailinfo *mi = mi_;\n \n \tif (!starts_with(var, \"mailinfo.\"))\n-\t\treturn git_default_config(var, value, NULL);\n+\t\treturn git_default_config(var, value,kvi, NULL);\n \tif (!strcmp(var, \"mailinfo.scissors\")) {\n \t\tmi->use_scissors = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/notes-utils.c b/notes-utils.c\nindex cb88171b7bb..f7a0ec60731 100644\n--- a/notes-utils.c\n+++ b/notes-utils.c\n@@ -93,7 +93,8 @@ static combine_notes_fn parse_combine_notes_fn(const char *v)\n \t\treturn NULL;\n }\n \n-static int notes_rewrite_config(const char *k, const char *v, void *cb)\n+static int notes_rewrite_config(const char *k, const char *v,\n+\t\t\t\tstruct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct notes_rewrite_cfg *c = cb;\n \tif (starts_with(k, \"notes.rewrite.\") && !strcmp(k+14, c->cmd)) {\ndiff --git a/notes.c b/notes.c\nindex 45fb7f22d1d..89b84ea0869 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -973,7 +973,8 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,\n \tfree(globs_copy);\n }\n \n-static int notes_display_config(const char *k, const char *v, void *cb)\n+static int notes_display_config(const char *k, const char *v,\n+\t\t\t\tstruct key_value_info *kvi UNUSED, void *cb)\n {\n \tint *load_refs = cb;\n \ndiff --git a/pager.c b/pager.c\nindex b66bbff2785..4b77e0d2e57 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -39,6 +39,7 @@ static void wait_for_pager_signal(int signo)\n }\n \n static int core_pager_config(const char *var, const char *value,\n+\t\t\t     struct key_value_info *kvi UNUSED,\n \t\t\t     void *data UNUSED)\n {\n \tif (!strcmp(var, \"core.pager\"))\n@@ -224,7 +225,9 @@ struct pager_command_config_data {\n \tchar *value;\n };\n \n-static int pager_command_config(const char *var, const char *value, void *vdata)\n+static int pager_command_config(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi UNUSED,\n+\t\t\t\tvoid *vdata)\n {\n \tstruct pager_command_config_data *data = vdata;\n \tconst char *cmd;\ndiff --git a/pretty.c b/pretty.c\nindex 76fc4f61e40..7f57379c44e 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -55,6 +55,7 @@ static void save_user_format(struct rev_info *rev, const char *cp, int is_tforma\n }\n \n static int git_pretty_formats_config(const char *var, const char *value,\n+\t\t\t\t     struct key_value_info *kvi UNUSED,\n \t\t\t\t     void *cb UNUSED)\n {\n \tstruct cmt_fmt_map *commit_format = NULL;\ndiff --git a/promisor-remote.c b/promisor-remote.c\nindex a8dbb788e8f..ea68df91209 100644\n--- a/promisor-remote.c\n+++ b/promisor-remote.c\n@@ -99,7 +99,9 @@ static void promisor_remote_move_to_tail(struct promisor_remote_config *config,\n \tconfig->promisors_tail = &r->next;\n }\n \n-static int promisor_remote_config(const char *var, const char *value, void *data)\n+static int promisor_remote_config(const char *var, const char *value,\n+\t\t\t\t  struct key_value_info *kvi UNUSED,\n+\t\t\t\t  void *data)\n {\n \tstruct promisor_remote_config *config = data;\n \tconst char *name;\ndiff --git a/remote.c b/remote.c\nindex 3a831cb5304..10868a963f2 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -348,7 +348,8 @@ static void read_branches_file(struct remote_state *remote_state,\n \tremote->fetch_tags = 1; /* always auto-follow */\n }\n \n-static int handle_config(const char *key, const char *value, void *cb)\n+static int handle_config(const char *key, const char *value,\n+\t\t\t struct key_value_info *kvi UNUSED, void *cb)\n {\n \tconst char *name;\n \tsize_t namelen;\ndiff --git a/revision.c b/revision.c\nindex 106ca1ce6c9..6de0132d719 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1569,7 +1569,8 @@ struct exclude_hidden_refs_cb {\n \tconst char *section;\n };\n \n-static int hide_refs_config(const char *var, const char *value, void *cb_data)\n+static int hide_refs_config(const char *var, const char *value,\n+\t\t\t    struct key_value_info *kvi UNUSED, void *cb_data)\n {\n \tstruct exclude_hidden_refs_cb *cb = cb_data;\n \tcb->exclusions->hidden_refs_configured = 1;\ndiff --git a/scalar.c b/scalar.c\nindex de07c37d210..1c44df2ec7a 100644\n--- a/scalar.c\n+++ b/scalar.c\n@@ -593,7 +593,8 @@ static int cmd_register(int argc, const char **argv)\n \treturn register_dir();\n }\n \n-static int get_scalar_repos(const char *key, const char *value, void *data)\n+static int get_scalar_repos(const char *key, const char *value,\n+\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct string_list *list = data;\n \ndiff --git a/sequencer.c b/sequencer.c\nindex 6985ca526ae..171561c2cdb 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -214,7 +214,8 @@ static struct update_ref_record *init_update_ref_record(const char *ref)\n \treturn rec;\n }\n \n-static int git_sequencer_config(const char *k, const char *v, void *cb)\n+static int git_sequencer_config(const char *k, const char *v,\n+\t\t\t\tstruct key_value_info *kvi, void *cb)\n {\n \tstruct replay_opts *opts = cb;\n \tint status;\n@@ -269,7 +270,7 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)\n \tif (opts->action == REPLAY_REVERT && !strcmp(k, \"revert.reference\"))\n \t\topts->commit_use_reference = git_config_bool(k, v);\n \n-\treturn git_diff_basic_config(k, v, NULL);\n+\treturn git_diff_basic_config(k, v,kvi, NULL);\n }\n \n void sequencer_init_config(struct replay_opts *opts)\n@@ -2876,7 +2877,8 @@ static int git_config_string_dup(char **dest,\n \treturn 0;\n }\n \n-static int populate_opts_cb(const char *key, const char *value, void *data)\n+static int populate_opts_cb(const char *key, const char *value,\n+\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct replay_opts *opts = data;\n \tint error_flag = 1;\ndiff --git a/setup.c b/setup.c\nindex 6c5b85e96c1..a461dd15233 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -514,7 +514,9 @@ no_prevention_needed:\n \tstartup_info->original_cwd = NULL;\n }\n \n-static int read_worktree_config(const char *var, const char *value, void *vdata)\n+static int read_worktree_config(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi UNUSED,\n+\t\t\t\tvoid *vdata)\n {\n \tstruct repository_format *data = vdata;\n \n@@ -585,7 +587,8 @@ static enum extension_result handle_extension(const char *var,\n \treturn EXTENSION_UNKNOWN;\n }\n \n-static int check_repo_format(const char *var, const char *value, void *vdata)\n+static int check_repo_format(const char *var, const char *value,\n+\t\t\t     struct key_value_info *kvi, void *vdata)\n {\n \tstruct repository_format *data = vdata;\n \tconst char *ext;\n@@ -614,7 +617,7 @@ static int check_repo_format(const char *var, const char *value, void *vdata)\n \t\t}\n \t}\n \n-\treturn read_worktree_config(var, value, vdata);\n+\treturn read_worktree_config(var, value,kvi, vdata);\n }\n \n static int check_repository_format_gently(const char *gitdir, struct repository_format *candidate, int *nongit_ok)\n@@ -1113,7 +1116,8 @@ struct safe_directory_data {\n \tint is_safe;\n };\n \n-static int safe_directory_cb(const char *key, const char *value, void *d)\n+static int safe_directory_cb(const char *key, const char *value,\n+\t\t\t     struct key_value_info *kvi UNUSED, void *d)\n {\n \tstruct safe_directory_data *data = d;\n \n@@ -1169,7 +1173,8 @@ static int ensure_valid_ownership(const char *gitfile,\n \treturn data.is_safe;\n }\n \n-static int allowed_bare_repo_cb(const char *key, const char *value, void *d)\n+static int allowed_bare_repo_cb(const char *key, const char *value,\n+\t\t\t\tstruct key_value_info *kvi UNUSED, void *d)\n {\n \tenum allowed_bare_repo *allowed_bare_repo = d;\n \ndiff --git a/submodule-config.c b/submodule-config.c\nindex ecf0fcf0074..fb12d8040e8 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -424,7 +424,8 @@ struct parse_config_parameter {\n  * config store (.git/config, etc).  Callers are responsible for\n  * checking for overrides in the main config store when appropriate.\n  */\n-static int parse_config(const char *var, const char *value, void *data)\n+static int parse_config(const char *var, const char *value,\n+\t\t\tstruct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct parse_config_parameter *me = data;\n \tstruct submodule *submodule;\n@@ -673,7 +674,8 @@ out:\n \t}\n }\n \n-static int gitmodules_cb(const char *var, const char *value, void *data)\n+static int gitmodules_cb(const char *var, const char *value,\n+\t\t\t struct key_value_info *kvi UNUSED, void *data)\n {\n \tstruct repository *repo = data;\n \tstruct parse_config_parameter parameter;\n@@ -800,7 +802,9 @@ void submodule_free(struct repository *r)\n \t\tsubmodule_cache_clear(r->submodule_cache);\n }\n \n-static int config_print_callback(const char *var, const char *value, void *cb_data)\n+static int config_print_callback(const char *var, const char *value,\n+\t\t\t\t struct key_value_info *kvi UNUSED,\n+\t\t\t\t void *cb_data)\n {\n \tchar *wanted_key = cb_data;\n \n@@ -842,7 +846,9 @@ struct fetch_config {\n \tint *recurse_submodules;\n };\n \n-static int gitmodules_fetch_config(const char *var, const char *value, void *cb)\n+static int gitmodules_fetch_config(const char *var, const char *value,\n+\t\t\t\t   struct key_value_info *kvi UNUSED,\n+\t\t\t\t   void *cb)\n {\n \tstruct fetch_config *config = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\")) {\n@@ -870,6 +876,7 @@ void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)\n }\n \n static int gitmodules_update_clone_config(const char *var, const char *value,\n+\t\t\t\t\t  struct key_value_info *kvi UNUSED,\n \t\t\t\t\t  void *cb)\n {\n \tint *max_jobs = cb;\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex ad78fc17683..00cd49e5145 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -42,7 +42,8 @@\n  *\n  */\n \n-static int iterate_cb(const char *var, const char *value, void *data UNUSED)\n+static int iterate_cb(const char *var, const char *value,\n+\t\t      struct key_value_info *kvi UNUSED, void *data UNUSED)\n {\n \tstatic int nr;\n \n@@ -59,7 +60,8 @@ static int iterate_cb(const char *var, const char *value, void *data UNUSED)\n \treturn 0;\n }\n \n-static int parse_int_cb(const char *var, const char *value, void *data)\n+static int parse_int_cb(const char *var, const char *value,\n+\t\t\tstruct key_value_info *kvi UNUSED, void *data)\n {\n \tconst char *key_to_match = data;\n \n@@ -70,7 +72,8 @@ static int parse_int_cb(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n-static int early_config_cb(const char *var, const char *value, void *vdata)\n+static int early_config_cb(const char *var, const char *value,\n+\t\t\t   struct key_value_info *kvi UNUSED, void *vdata)\n {\n \tconst char *key = vdata;\n \ndiff --git a/t/helper/test-userdiff.c b/t/helper/test-userdiff.c\nindex 680124a6760..33dd3a65241 100644\n--- a/t/helper/test-userdiff.c\n+++ b/t/helper/test-userdiff.c\n@@ -12,7 +12,9 @@ static int driver_cb(struct userdiff_driver *driver,\n \treturn 0;\n }\n \n-static int cmd__userdiff_config(const char *var, const char *value, void *cb UNUSED)\n+static int cmd__userdiff_config(const char *var, const char *value,\n+\t\t\t\tstruct key_value_info *kvi UNUSED,\n+\t\t\t\tvoid *cb UNUSED)\n {\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 78cfc15d52d..6871258d468 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -99,7 +99,8 @@ struct tr2_cfg_data {\n /*\n  * See if the given config key matches any of our patterns of interest.\n  */\n-static int tr2_cfg_cb(const char *key, const char *value, void *d)\n+static int tr2_cfg_cb(const char *key, const char *value,\n+\t\t      struct key_value_info *kvi UNUSED, void *d)\n {\n \tstruct strbuf **s;\n \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\ndiff --git a/trace2/tr2_sysenv.c b/trace2/tr2_sysenv.c\nindex 069786cb927..d17a08b0f50 100644\n--- a/trace2/tr2_sysenv.c\n+++ b/trace2/tr2_sysenv.c\n@@ -57,7 +57,8 @@ static struct tr2_sysenv_entry tr2_sysenv_settings[] = {\n };\n /* clang-format on */\n \n-static int tr2_sysenv_cb(const char *key, const char *value, void *d)\n+static int tr2_sysenv_cb(const char *key, const char *value,\n+\t\t\t struct key_value_info *kvi UNUSED, void *d)\n {\n \tint k;\n \ndiff --git a/trailer.c b/trailer.c\nindex a2c3ed6f28c..fac7e2cfe6e 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -482,6 +482,7 @@ static struct {\n };\n \n static int git_trailer_default_config(const char *conf_key, const char *value,\n+\t\t\t\t      struct key_value_info *kvi UNUSED,\n \t\t\t\t      void *cb UNUSED)\n {\n \tconst char *trailer_item, *variable_name;\n@@ -514,6 +515,7 @@ static int git_trailer_default_config(const char *conf_key, const char *value,\n }\n \n static int git_trailer_config(const char *conf_key, const char *value,\n+\t\t\t      struct key_value_info *kvi UNUSED,\n \t\t\t      void *cb UNUSED)\n {\n \tconst char *trailer_item, *variable_name;\ndiff --git a/upload-pack.c b/upload-pack.c\nindex e23f16dfdd2..5f8232ff078 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1298,7 +1298,9 @@ static int parse_object_filter_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-static int upload_pack_config(const char *var, const char *value, void *cb_data)\n+static int upload_pack_config(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi UNUSED,\n+\t\t\t      void *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n \n@@ -1339,7 +1341,9 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data)\n \treturn parse_hide_refs_config(var, value, \"uploadpack\", &data->hidden_refs);\n }\n \n-static int upload_pack_protected_config(const char *var, const char *value, void *cb_data)\n+static int upload_pack_protected_config(const char *var, const char *value,\n+\t\t\t\t\tstruct key_value_info *kvi UNUSED,\n+\t\t\t\t\tvoid *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n \ndiff --git a/urlmatch.c b/urlmatch.c\nindex eba0bdd77fe..47683974d8c 100644\n--- a/urlmatch.c\n+++ b/urlmatch.c\n@@ -551,7 +551,8 @@ static int cmp_matches(const struct urlmatch_item *a,\n \treturn 0;\n }\n \n-int urlmatch_config_entry(const char *var, const char *value, void *cb)\n+int urlmatch_config_entry(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n {\n \tstruct string_list_item *item;\n \tstruct urlmatch_config *collect = cb;\ndiff --git a/urlmatch.h b/urlmatch.h\nindex bee374a642c..f6eac4af9ea 100644\n--- a/urlmatch.h\n+++ b/urlmatch.h\n@@ -71,7 +71,8 @@ struct urlmatch_config {\n \t.vars = STRING_LIST_INIT_DUP, \\\n }\n \n-int urlmatch_config_entry(const char *var, const char *value, void *cb);\n+int urlmatch_config_entry(const char *var, const char *value,\n+\t\t\t  struct key_value_info *kvi, void *cb);\n void urlmatch_config_release(struct urlmatch_config *config);\n \n #endif /* URL_MATCH_H */\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex 0460e03f5ed..f1aac104285 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -307,7 +307,8 @@ int xdiff_compare_lines(const char *l1, long s1,\n \n int git_xmerge_style = -1;\n \n-int git_xmerge_config(const char *var, const char *value, void *cb)\n+int git_xmerge_config(const char *var, const char *value,\n+\t\t      struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"merge.conflictstyle\")) {\n \t\tif (!value)\n@@ -327,5 +328,5 @@ int git_xmerge_config(const char *var, const char *value, void *cb)\n \t\t\t    value, var);\n \t\treturn 0;\n \t}\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value,kvi, cb);\n }\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex 3750794afe9..c1676b11702 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -50,7 +50,8 @@ int buffer_is_binary(const char *ptr, unsigned long size);\n \n void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n-int git_xmerge_config(const char *var, const char *value, void *cb);\n+int git_xmerge_config(const char *var, const char *value,\n+\t\t      struct key_value_info *kvi, void *cb);\n extern int git_xmerge_style;\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"477815","messageId":"3760015d2c000c59d354bae872c431ee3266e9da.1685472134.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 09/14] config.c: provide kvi with CLI config","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:42:07Z","receivedAt":"2023-05-30T18:43:04Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nRefactor out kvi_from_param() from the logic that caches CLI config in\nconfigsets, and use it to pass the \"kvi\" arg to config callbacks when\nparsing CLI config. Now that \"kvi\" is always present when config\nmachinery calls config callbacks, plumb \"kvi\" so that we can replace\nnearly all calls to current_config_*(). (The exception is an edge case\nwhere trace2/*.c calls current_config_scope(). That will be handled in a\nlater commit.) Note that this results in \"kvi\" containing a different,\nmore complete set of information than the mocked up \"struct\nconfig_source\" in git_config_from_parameters().\n\nPlumbing \"kvi\" reveals a few places where we've been doing the wrong\nthing:\n\n* git_config_parse_parameter() hasn't been setting config source\n  information, so plumb \"kvi\" there too.\n\n* \"git config --get-urlmatch --show-scope\" iterates config to collect\n  values, but then attempts to display the scope after config iteration.\n  Fix this by copying the \"kvi\" arg in the collection phase so that it\n  can be read back later. This means that we can now support \"git config\n  --get-urlmatch --show-origin\" (we don't allow this combination of args\n  because of this bug), but that is left unchanged for now.\n\n* \"git config --default\" doesn't have config source metadata when\n  displaying the default value. Fix this by treating the default value\n  as if it came from the command line (e.g. like we do with \"git -c\" or\n  \"git config --file\"), using kvi_from_param().\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/config.c  | 41 ++++++++++++++-----------\n config.c          | 78 ++++++++++++++++++++++-------------------------\n config.h          |  3 +-\n t/t1300-config.sh | 10 +++---\n 4 files changed, 68 insertions(+), 64 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex b2ad7351d0a..2fcad601a7b 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -191,37 +191,37 @@ static void check_argc(int argc, int min, int max)\n \tusage_builtin_config();\n }\n \n-static void show_config_origin(struct strbuf *buf)\n+static void show_config_origin(struct key_value_info *kvi, struct strbuf *buf)\n {\n \tconst char term = end_nul ? '\\0' : '\\t';\n \n-\tstrbuf_addstr(buf, current_config_origin_type());\n+\tstrbuf_addstr(buf, config_origin_type_name(kvi->origin_type));\n \tstrbuf_addch(buf, ':');\n \tif (end_nul)\n-\t\tstrbuf_addstr(buf, current_config_name());\n+\t\tstrbuf_addstr(buf, kvi->filename ? kvi->filename : \"\");\n \telse\n-\t\tquote_c_style(current_config_name(), buf, NULL, 0);\n+\t\tquote_c_style(kvi->filename ? kvi->filename : \"\", buf, NULL, 0);\n \tstrbuf_addch(buf, term);\n }\n \n-static void show_config_scope(struct strbuf *buf)\n+static void show_config_scope(struct key_value_info *kvi, struct strbuf *buf)\n {\n \tconst char term = end_nul ? '\\0' : '\\t';\n-\tconst char *scope = config_scope_name(current_config_scope());\n+\tconst char *scope = config_scope_name(kvi->scope);\n \n \tstrbuf_addstr(buf, N_(scope));\n \tstrbuf_addch(buf, term);\n }\n \n static int show_all_config(const char *key_, const char *value_,\n-\t\t\t   struct key_value_info *kvi UNUSED, void *cb UNUSED)\n+\t\t\t   struct key_value_info *kvi, void *cb UNUSED)\n {\n \tif (show_origin || show_scope) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \t\tif (show_scope)\n-\t\t\tshow_config_scope(&buf);\n+\t\t\tshow_config_scope(kvi, &buf);\n \t\tif (show_origin)\n-\t\t\tshow_config_origin(&buf);\n+\t\t\tshow_config_origin(kvi, &buf);\n \t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n \t\tfwrite(buf.buf, 1, buf.len, stdout);\n \t\tstrbuf_release(&buf);\n@@ -239,12 +239,13 @@ struct strbuf_list {\n \tint alloc;\n };\n \n-static int format_config(struct strbuf *buf, const char *key_, const char *value_)\n+static int format_config(struct strbuf *buf, const char *key_,\n+\t\t\t const char *value_, struct key_value_info *kvi)\n {\n \tif (show_scope)\n-\t\tshow_config_scope(buf);\n+\t\tshow_config_scope(kvi, buf);\n \tif (show_origin)\n-\t\tshow_config_origin(buf);\n+\t\tshow_config_origin(kvi, buf);\n \tif (show_keys)\n \t\tstrbuf_addstr(buf, key_);\n \tif (!omit_values) {\n@@ -299,7 +300,7 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value\n }\n \n static int collect_config(const char *key_, const char *value_,\n-\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n+\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tstruct strbuf_list *values = cb;\n \n@@ -316,7 +317,7 @@ static int collect_config(const char *key_, const char *value_,\n \tALLOC_GROW(values->items, values->nr + 1, values->alloc);\n \tstrbuf_init(&values->items[values->nr], 0);\n \n-\treturn format_config(&values->items[values->nr++], key_, value_);\n+\treturn format_config(&values->items[values->nr++], key_, value_, kvi);\n }\n \n static int get_value(const char *key_, const char *regex_, unsigned flags)\n@@ -377,11 +378,14 @@ static int get_value(const char *key_, const char *regex_, unsigned flags)\n \t\t\t    &given_config_source, &config_options);\n \n \tif (!values.nr && default_value) {\n+\t\tstruct key_value_info kvi = { 0 };\n \t\tstruct strbuf *item;\n+\n+\t\tkvi_from_param(&kvi);\n \t\tALLOC_GROW(values.items, values.nr + 1, values.alloc);\n \t\titem = &values.items[values.nr++];\n \t\tstrbuf_init(item, 0);\n-\t\tif (format_config(item, key_, default_value) < 0)\n+\t\tif (format_config(item, key_, default_value, &kvi) < 0)\n \t\t\tdie(_(\"failed to format default config value: %s\"),\n \t\t\t\tdefault_value);\n \t}\n@@ -556,10 +560,11 @@ static void check_write(void)\n struct urlmatch_current_candidate_value {\n \tchar value_is_null;\n \tstruct strbuf value;\n+\tstruct key_value_info kvi;\n };\n \n static int urlmatch_collect_fn(const char *var, const char *value,\n-\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n+\t\t\t       struct key_value_info *kvi, void *cb)\n {\n \tstruct string_list *values = cb;\n \tstruct string_list_item *item = string_list_insert(values, var);\n@@ -572,6 +577,7 @@ static int urlmatch_collect_fn(const char *var, const char *value,\n \t} else {\n \t\tstrbuf_reset(&matched->value);\n \t}\n+\tmemcpy(&matched->kvi, kvi, sizeof(struct key_value_info));\n \n \tif (value) {\n \t\tstrbuf_addstr(&matched->value, value);\n@@ -618,7 +624,8 @@ static int get_urlmatch(const char *var, const char *url)\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n \t\tformat_config(&buf, item->string,\n-\t\t\t      matched->value_is_null ? NULL : matched->value.buf);\n+\t\t\t      matched->value_is_null ? NULL : matched->value.buf,\n+\t\t\t      &matched->kvi);\n \t\tfwrite(buf.buf, 1, buf.len, stdout);\n \t\tstrbuf_release(&buf);\n \ndiff --git a/config.c b/config.c\nindex 4b6830fcc79..1779fe62f89 100644\n--- a/config.c\n+++ b/config.c\n@@ -211,7 +211,9 @@ static const char include_depth_advice[] = N_(\n \"from\\n\"\n \"\t%s\\n\"\n \"This might be due to circular includes.\");\n-static int handle_path_include(struct config_source *cs, const char *path,\n+static int handle_path_include(struct config_source *cs,\n+\t\t\t       struct key_value_info *kvi,\n+\t\t\t       const char *path,\n \t\t\t       struct config_include_data *inc)\n {\n \tint ret = 0;\n@@ -252,8 +254,7 @@ static int handle_path_include(struct config_source *cs, const char *path,\n \t\t\t    cs->name ? cs->name :\n \t\t\t    \"the command line\");\n \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n-\t\t\t\t\t\t\tcurrent_config_scope(),\n-\t\t\t\t\t\t\tNULL);\n+\t\t\t\t\t\t\tkvi->scope, NULL);\n \t\tinc->depth--;\n \t}\n cleanup:\n@@ -500,7 +501,7 @@ static int git_config_include(const char *var, const char *value,\n \t\treturn ret;\n \n \tif (!strcmp(var, \"include.path\"))\n-\t\tret = handle_path_include(cs, value, inc);\n+\t\tret = handle_path_include(cs, kvi, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n \t    cond && include_condition_is_true(cs, inc, cond, cond_len) &&\n@@ -509,7 +510,7 @@ static int git_config_include(const char *var, const char *value,\n \n \t\tif (inc->opts->unconditional_remote_url)\n \t\t\tinc->fn = forbid_remote_url;\n-\t\tret = handle_path_include(cs, value, inc);\n+\t\tret = handle_path_include(cs, kvi, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -667,7 +668,8 @@ out_free_ret_1:\n }\n \n static int config_parse_pair(const char *key, const char *value,\n-\t\t\t  config_fn_t fn, void *data)\n+\t\t\t     struct key_value_info *kvi,\n+\t\t\t     config_fn_t fn, void *data)\n {\n \tchar *canonical_name;\n \tint ret;\n@@ -677,17 +679,30 @@ static int config_parse_pair(const char *key, const char *value,\n \tif (git_config_parse_key(key, &canonical_name, NULL))\n \t\treturn -1;\n \n-\tret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;\n+\tret = (fn(canonical_name, value, kvi, data) < 0) ? -1 : 0;\n \tfree(canonical_name);\n \treturn ret;\n }\n \n+\n+/* for values read from `git_config_from_parameters()` */\n+void kvi_from_param(struct key_value_info *out)\n+{\n+\tout->filename = NULL;\n+\tout->linenr = -1;\n+\tout->origin_type = CONFIG_ORIGIN_CMDLINE;\n+\tout->scope = CONFIG_SCOPE_COMMAND;\n+}\n+\n int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n \tconst char *value;\n \tstruct strbuf **pair;\n \tint ret;\n+\tstruct key_value_info kvi = { 0 };\n+\n+\tkvi_from_param(&kvi);\n \n \tpair = strbuf_split_str(text, '=', 2);\n \tif (!pair[0])\n@@ -706,12 +721,13 @@ int git_config_parse_parameter(const char *text,\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \t}\n \n-\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n+\tret = config_parse_pair(pair[0]->buf, value, &kvi, fn, data);\n \tstrbuf_list_free(pair);\n \treturn ret;\n }\n \n-static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n+static int parse_config_env_list(char *env, struct key_value_info *kvi,\n+\t\t\t\t config_fn_t fn, void *data)\n {\n \tchar *cur = env;\n \twhile (cur && *cur) {\n@@ -745,7 +761,7 @@ static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n \t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n \t\t\t}\n \n-\t\t\tif (config_parse_pair(key, value, fn, data) < 0)\n+\t\t\tif (config_parse_pair(key, value, kvi, fn, data) < 0)\n \t\t\t\treturn -1;\n \t\t}\n \t\telse {\n@@ -770,10 +786,13 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tint ret = 0;\n \tchar *envw = NULL;\n \tstruct config_source source = CONFIG_SOURCE_INIT;\n+\tstruct key_value_info kvi = { 0 };\n \n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n \tconfig_reader_push_source(&the_reader, &source);\n \n+\tkvi_from_param(&kvi);\n+\n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n \t\tunsigned long count;\n@@ -809,7 +828,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \t\t\t}\n \t\t\tstrbuf_reset(&envvar);\n \n-\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n+\t\t\tif (config_parse_pair(key, value, &kvi, fn, data) < 0) {\n \t\t\t\tret = -1;\n \t\t\t\tgoto out;\n \t\t\t}\n@@ -820,7 +839,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tif (env) {\n \t\t/* sq_dequote will write over it */\n \t\tenvw = xstrdup(env);\n-\t\tif (parse_config_env_list(envw, fn, data) < 0) {\n+\t\tif (parse_config_env_list(envw, &kvi, fn, data) < 0) {\n \t\t\tret = -1;\n \t\t\tgoto out;\n \t\t}\n@@ -2422,7 +2441,8 @@ static int configset_find_element(struct config_set *set, const char *key,\n \treturn 0;\n }\n \n-static int configset_add_value(struct config_reader *reader,\n+static int configset_add_value(struct key_value_info *kvi_p,\n+\t\t\t       struct config_reader *reader,\n \t\t\t       struct config_set *set, const char *key,\n \t\t\t       const char *value)\n {\n@@ -2453,16 +2473,10 @@ static int configset_add_value(struct config_reader *reader,\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (!reader->source)\n-\t\tBUG(\"configset_add_value has no source\");\n \tif (reader->source->name) {\n-\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n+\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n \t} else {\n-\t\t/* for values read from `git_config_from_parameters()` */\n-\t\tkv_info->filename = NULL;\n-\t\tkv_info->linenr = -1;\n-\t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n-\t\tkv_info->scope = reader->parsing_scope;\n+\t\tkvi_from_param(kv_info);\n \t}\n \tsi->util = kv_info;\n \n@@ -2518,10 +2532,10 @@ struct configset_add_data {\n #define CONFIGSET_ADD_INIT { 0 }\n \n static int config_set_callback(const char *key, const char *value,\n-\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n+\t\t\t       struct key_value_info *kvi, void *cb)\n {\n \tstruct configset_add_data *data = cb;\n-\tconfigset_add_value(data->config_reader, data->config_set, key, value);\n+\tconfigset_add_value(kvi, data->config_reader, data->config_set, key, value);\n \treturn 0;\n }\n \n@@ -4000,16 +4014,6 @@ const char *config_origin_type_name(enum config_origin_type type)\n \t}\n }\n \n-const char *current_config_origin_type(void)\n-{\n-\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n-\n-\tif (reader_origin_type(&the_reader, &type))\n-\t\tBUG(\"current_config_origin_type called outside config callback\");\n-\n-\treturn config_origin_type_name(type);\n-}\n-\n const char *config_scope_name(enum config_scope scope)\n {\n \tswitch (scope) {\n@@ -4041,14 +4045,6 @@ static int reader_config_name(struct config_reader *reader, const char **out)\n \treturn 0;\n }\n \n-const char *current_config_name(void)\n-{\n-\tconst char *name;\n-\tif (reader_config_name(&the_reader, &name))\n-\t\tBUG(\"current_config_name called outside config callback\");\n-\treturn name ? name : \"\";\n-}\n-\n enum config_scope current_config_scope(void)\n {\n \tif (the_reader.config_kvi)\ndiff --git a/config.h b/config.h\nindex ca5aac62980..fe7c1b79f80 100644\n--- a/config.h\n+++ b/config.h\n@@ -367,9 +367,8 @@ void git_global_config(char **user, char **xdg);\n int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n \n enum config_scope current_config_scope(void);\n-const char *current_config_origin_type(void);\n-const char *current_config_name(void);\n const char *config_origin_type_name(enum config_origin_type type);\n+void kvi_from_param(struct key_value_info *out);\n \n /*\n  * Match and parse a config key of the form:\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 57fe250b78f..028998dadc4 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1635,8 +1635,8 @@ test_expect_success 'urlmatch with --show-scope' '\n \tEOF\n \n \tcat >expect <<-EOF &&\n-\tunknown\thttp.cookiefile /tmp/cookie.txt\n-\tunknown\thttp.sslverify false\n+\tlocal\thttp.cookiefile /tmp/cookie.txt\n+\tlocal\thttp.sslverify false\n \tEOF\n \tgit config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&\n \ttest_cmp expect actual\n@@ -2030,7 +2030,9 @@ test_expect_success '--show-origin blob ref' '\n '\n \n test_expect_success '--show-origin with --default' '\n-\ttest_must_fail git config --show-origin --default foo some.key\n+\tgit config --show-origin --default foo some.key >actual &&\n+\techo \"command line:\tfoo\" >expect &&\n+\ttest_cmp expect actual\n '\n \n test_expect_success '--show-scope with --list' '\n@@ -2103,7 +2105,7 @@ test_expect_success '--show-scope with --show-origin' '\n \n test_expect_success '--show-scope with --default' '\n \tgit config --show-scope --default foo some.key >actual &&\n-\techo \"unknown\tfoo\" >expect &&\n+\techo \"command\tfoo\" >expect &&\n \ttest_cmp expect actual\n '\n \n-- \ngitgitgadget\n\n"},{"id":"477816","messageId":"7dc0c46b864665dad87a5e0e32c6a8c36afa4673.1685472134.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 10/14] trace2: plumb config kvi","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:42:08Z","receivedAt":"2023-05-30T18:43:07Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThere is a code path starting from trace2_def_param_fl() that eventually\ncalls current_config_scope(), and thus it needs to have \"kvi\" plumbed\nthrough it. Additional plumbing is also needed to get \"kvi\" to\ntrace2_def_param_fl(), which gets called by two code paths:\n\n- Through tr2_cfg_cb(), which is a config callback, so it trivially\n  receives \"kvi\".\n\n- Through tr2_list_env_vars_fl(), which is a high level function that\n  lists environment variables for tracing. This has been secretly\n  behaving like git_config_from_parameters() (in that it parses config\n  from environment variables/the CLI), but does not set config source\n  information.\n\n  Teach tr2_list_env_vars_fl() to be well-behaved by using\n  kvi_from_param(), which is used elsewhere for CLI/environment\n  variable-based config.\n\nAs a result, current_config_scope() has no more callers, so remove it.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c                | 46 -----------------------------------------\n config.h                |  1 -\n trace2.c                |  4 ++--\n trace2.h                |  3 ++-\n trace2/tr2_cfg.c        |  9 +++++---\n trace2/tr2_tgt.h        |  4 +++-\n trace2/tr2_tgt_event.c  |  4 ++--\n trace2/tr2_tgt_normal.c |  4 ++--\n trace2/tr2_tgt_perf.c   |  4 ++--\n 9 files changed, 19 insertions(+), 60 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 1779fe62f89..819e9e5966c 100644\n--- a/config.c\n+++ b/config.c\n@@ -78,16 +78,6 @@ struct config_reader {\n \t */\n \tstruct config_source *source;\n \tstruct key_value_info *config_kvi;\n-\t/*\n-\t * The \"scope\" of the current config source being parsed (repo, global,\n-\t * etc). Like \"source\", this is only set when parsing a config source.\n-\t * It's not part of \"source\" because it transcends a single file (i.e.,\n-\t * a file included from .git/config is still in \"repo\" scope).\n-\t *\n-\t * When iterating through a configset, the equivalent value is\n-\t * \"config_kvi.scope\" (see above).\n-\t */\n-\tenum config_scope parsing_scope;\n };\n /*\n  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n@@ -118,19 +108,9 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\n static inline void config_reader_set_kvi(struct config_reader *reader,\n \t\t\t\t\t struct key_value_info *kvi)\n {\n-\tif (kvi && (reader->source || reader->parsing_scope))\n-\t\tBUG(\"kvi should not be set while parsing a config source\");\n \treader->config_kvi = kvi;\n }\n \n-static inline void config_reader_set_scope(struct config_reader *reader,\n-\t\t\t\t\t   enum config_scope scope)\n-{\n-\tif (scope && reader->config_kvi)\n-\t\tBUG(\"scope should only be set when iterating through a config source\");\n-\treader->parsing_scope = scope;\n-}\n-\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -404,18 +384,12 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tenum config_scope store_scope = inc->config_reader->parsing_scope;\n-\n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tconfig_reader_set_scope(inc->config_reader, 0);\n-\n \tinc->remote_urls = xmalloc(sizeof(*inc->remote_urls));\n \tstring_list_init_dup(inc->remote_urls);\n \tconfig_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);\n-\n-\tconfig_reader_set_scope(inc->config_reader, store_scope);\n }\n \n static int forbid_remote_url(const char *var, const char *value UNUSED,\n@@ -2242,7 +2216,6 @@ static int do_git_config_sequence(struct config_reader *reader,\n \tchar *xdg_config = NULL;\n \tchar *user_config = NULL;\n \tchar *repo_config;\n-\tenum config_scope prev_parsing_scope = reader->parsing_scope;\n \n \tif (opts->commondir)\n \t\trepo_config = mkpathdup(\"%s/config\", opts->commondir);\n@@ -2251,7 +2224,6 @@ static int do_git_config_sequence(struct config_reader *reader,\n \telse\n \t\trepo_config = NULL;\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_SYSTEM);\n \tif (git_config_system() && system_config &&\n \t    !access_or_die(system_config, R_OK,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n@@ -2259,7 +2231,6 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n \t\t\t\t\t\t\t NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);\n \tgit_global_config(&user_config, &xdg_config);\n \n \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n@@ -2270,13 +2241,11 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\tret += git_config_from_file_with_options(fn, user_config, data,\n \t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);\n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n \t\tret += git_config_from_file_with_options(fn, repo_config, data,\n \t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n \tif (!opts->ignore_worktree && repository_format_worktree_config) {\n \t\tchar *path = git_pathdup(\"config.worktree\");\n \t\tif (!access_or_die(path, R_OK, 0))\n@@ -2286,11 +2255,9 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\tfree(path);\n \t}\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);\n \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n \t\tdie(_(\"unable to parse command-line config\"));\n \n-\tconfig_reader_set_scope(reader, prev_parsing_scope);\n \tfree(system_config);\n \tfree(xdg_config);\n \tfree(user_config);\n@@ -2303,7 +2270,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\t\tconst struct config_options *opts)\n {\n \tstruct config_include_data inc = CONFIG_INCLUDE_INIT;\n-\tenum config_scope prev_scope = the_reader.parsing_scope;\n \tint ret;\n \n \tif (opts->respect_includes) {\n@@ -2316,9 +2282,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tdata = &inc;\n \t}\n \n-\tif (config_source)\n-\t\tconfig_reader_set_scope(&the_reader, config_source->scope);\n-\n \t/*\n \t * If we have a specific filename, use it. Otherwise, follow the\n \t * regular lookup sequence.\n@@ -2342,7 +2305,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tstring_list_clear(inc.remote_urls, 0);\n \t\tFREE_AND_NULL(inc.remote_urls);\n \t}\n-\tconfig_reader_set_scope(&the_reader, prev_scope);\n \treturn ret;\n }\n \n@@ -4045,14 +4007,6 @@ static int reader_config_name(struct config_reader *reader, const char **out)\n \treturn 0;\n }\n \n-enum config_scope current_config_scope(void)\n-{\n-\tif (the_reader.config_kvi)\n-\t\treturn the_reader.config_kvi->scope;\n-\telse\n-\t\treturn the_reader.parsing_scope;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex fe7c1b79f80..9e3c2bdb429 100644\n--- a/config.h\n+++ b/config.h\n@@ -366,7 +366,6 @@ void git_global_config(char **user, char **xdg);\n \n int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n \n-enum config_scope current_config_scope(void);\n const char *config_origin_type_name(enum config_origin_type type);\n void kvi_from_param(struct key_value_info *out);\n \ndiff --git a/trace2.c b/trace2.c\nindex e8ba62c0c3d..f519a3514b6 100644\n--- a/trace2.c\n+++ b/trace2.c\n@@ -632,7 +632,7 @@ void trace2_thread_exit_fl(const char *file, int line)\n }\n \n void trace2_def_param_fl(const char *file, int line, const char *param,\n-\t\t\t const char *value)\n+\t\t\t const char *value, struct key_value_info *kvi)\n {\n \tstruct tr2_tgt *tgt_j;\n \tint j;\n@@ -642,7 +642,7 @@ void trace2_def_param_fl(const char *file, int line, const char *param,\n \n \tfor_each_wanted_builtin (j, tgt_j)\n \t\tif (tgt_j->pfn_param_fl)\n-\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n+\t\t\ttgt_j->pfn_param_fl(file, line, param, value, kvi);\n }\n \n void trace2_def_repo_fl(const char *file, int line, struct repository *repo)\ndiff --git a/trace2.h b/trace2.h\nindex 4ced30c0db3..af06f66739e 100644\n--- a/trace2.h\n+++ b/trace2.h\n@@ -325,6 +325,7 @@ void trace2_thread_exit_fl(const char *file, int line);\n \n #define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)\n \n+struct key_value_info;\n /*\n  * Emits a \"def_param\" message containing a key/value pair.\n  *\n@@ -334,7 +335,7 @@ void trace2_thread_exit_fl(const char *file, int line);\n  * `core.abbrev`, `status.showUntrackedFiles`, or `--no-ahead-behind`.\n  */\n void trace2_def_param_fl(const char *file, int line, const char *param,\n-\t\t\t const char *value);\n+\t\t\t const char *value, struct key_value_info *kvi);\n \n #define trace2_def_param(param, value) \\\n \ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 8ed139c69f4..1450c9bec71 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -100,7 +100,7 @@ struct tr2_cfg_data {\n  * See if the given config key matches any of our patterns of interest.\n  */\n static int tr2_cfg_cb(const char *key, const char *value,\n-\t\t      struct key_value_info *kvi UNUSED, void *d)\n+\t\t      struct key_value_info *kvi, void *d)\n {\n \tstruct strbuf **s;\n \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n@@ -109,7 +109,8 @@ static int tr2_cfg_cb(const char *key, const char *value,\n \t\tstruct strbuf *buf = *s;\n \t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n \t\tif (wm == WM_MATCH) {\n-\t\t\ttrace2_def_param_fl(data->file, data->line, key, value);\n+\t\t\ttrace2_def_param_fl(data->file, data->line, key, value,\n+\t\t\t\t\t    kvi);\n \t\t\treturn 0;\n \t\t}\n \t}\n@@ -127,8 +128,10 @@ void tr2_cfg_list_config_fl(const char *file, int line)\n \n void tr2_list_env_vars_fl(const char *file, int line)\n {\n+\tstruct key_value_info kvi = { 0 };\n \tstruct strbuf **s;\n \n+\tkvi_from_param(&kvi);\n \tif (tr2_load_env_vars() <= 0)\n \t\treturn;\n \n@@ -136,7 +139,7 @@ void tr2_list_env_vars_fl(const char *file, int line)\n \t\tstruct strbuf *buf = *s;\n \t\tconst char *val = getenv(buf->buf);\n \t\tif (val && *val)\n-\t\t\ttrace2_def_param_fl(file, line, buf->buf, val);\n+\t\t\ttrace2_def_param_fl(file, line, buf->buf, val, &kvi);\n \t}\n }\n \ndiff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h\nindex bf8745c4f05..9c88ca9beed 100644\n--- a/trace2/tr2_tgt.h\n+++ b/trace2/tr2_tgt.h\n@@ -69,8 +69,10 @@ typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, int line,\n \t\t\t\t\t   uint64_t us_elapsed_absolute,\n \t\t\t\t\t   int exec_id, int code);\n \n+struct key_value_info;\n typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,\n-\t\t\t\t     const char *param, const char *value);\n+\t\t\t\t     const char *param, const char *value,\n+\t\t\t\t     struct key_value_info *kvi);\n \n typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,\n \t\t\t\t    const struct repository *repo);\ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nindex 9e7aab6d510..83db3c755bd 100644\n--- a/trace2/tr2_tgt_event.c\n+++ b/trace2/tr2_tgt_event.c\n@@ -476,11 +476,11 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, struct key_value_info *kvi)\n {\n \tconst char *event_name = \"def_param\";\n \tstruct json_writer jw = JSON_WRITER_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tjw_object_begin(&jw, 0);\ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nindex 8672c2c2d04..65e9be9c5a4 100644\n--- a/trace2/tr2_tgt_normal.c\n+++ b/trace2/tr2_tgt_normal.c\n@@ -296,10 +296,10 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, struct key_value_info *kvi)\n {\n \tstruct strbuf buf_payload = STRBUF_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tstrbuf_addf(&buf_payload, \"def_param scope:%s %s=%s\", scope_name, param,\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nindex 3f2b2d53118..f402f6e3813 100644\n--- a/trace2/tr2_tgt_perf.c\n+++ b/trace2/tr2_tgt_perf.c\n@@ -438,12 +438,12 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, struct key_value_info *kvi)\n {\n \tconst char *event_name = \"def_param\";\n \tstruct strbuf buf_payload = STRBUF_INIT;\n \tstruct strbuf scope_payload = STRBUF_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tstrbuf_addf(&buf_payload, \"%s:%s\", param, value);\n-- \ngitgitgadget\n\n"},{"id":"477817","messageId":"52db0d3be828e9a410bd3535419d756ecc247c33.1685472134.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 12/14] config.c: remove config_reader from configsets","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:42:10Z","receivedAt":"2023-05-30T18:43:10Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nRemove the last usage of \"struct config_reader\" from configsets by\ncopying the \"kvi\" arg instead of recomputing \"kvi\" from\nconfig_reader.source. Since we no longer need to pass both \"struct\nconfig_reader\" and \"struct config_set\" in a single \"void *cb\", remove\n\"struct configset_add_data\" too.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 41 ++++++++++-------------------------------\n 1 file changed, 10 insertions(+), 31 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 34a111b2e1c..4d381f93bcc 100644\n--- a/config.c\n+++ b/config.c\n@@ -2285,8 +2285,7 @@ int config_with_options(config_fn_t fn, void *data,\n \treturn ret;\n }\n \n-static void configset_iter(struct config_reader *reader, struct config_set *set,\n-\t\t\t   config_fn_t fn, void *data)\n+static void configset_iter(struct config_set *set, config_fn_t fn, void *data)\n {\n \tint i, value_index;\n \tstruct string_list *values;\n@@ -2378,7 +2377,6 @@ static int configset_find_element(struct config_set *set, const char *key,\n }\n \n static int configset_add_value(struct key_value_info *kvi_p,\n-\t\t\t       struct config_reader *reader,\n \t\t\t       struct config_set *set, const char *key,\n \t\t\t       const char *value)\n {\n@@ -2409,11 +2407,7 @@ static int configset_add_value(struct key_value_info *kvi_p,\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (reader->source->name) {\n-\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n-\t} else {\n-\t\tkvi_from_param(kv_info);\n-\t}\n+\tmemcpy(kv_info, kvi_p, sizeof(struct key_value_info));\n \tsi->util = kv_info;\n \n \treturn 0;\n@@ -2461,26 +2455,17 @@ void git_configset_clear(struct config_set *set)\n \tset->list.items = NULL;\n }\n \n-struct configset_add_data {\n-\tstruct config_set *config_set;\n-\tstruct config_reader *config_reader;\n-};\n-#define CONFIGSET_ADD_INIT { 0 }\n-\n static int config_set_callback(const char *key, const char *value,\n \t\t\t       struct key_value_info *kvi, void *cb)\n {\n-\tstruct configset_add_data *data = cb;\n-\tconfigset_add_value(kvi, data->config_reader, data->config_set, key, value);\n+\tstruct config_set *set = cb;\n+\tconfigset_add_value(kvi, set, key, value);\n \treturn 0;\n }\n \n int git_configset_add_file(struct config_set *set, const char *filename)\n {\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n-\tdata.config_reader = &the_reader;\n-\tdata.config_set = set;\n-\treturn git_config_from_file(config_set_callback, filename, &data);\n+\treturn git_config_from_file(config_set_callback, filename, set);\n }\n \n int git_configset_get_value(struct config_set *set, const char *key,\n@@ -2646,7 +2631,6 @@ int git_configset_get_pathname(struct config_set *set, const char *key, const ch\n static void repo_read_config(struct repository *repo)\n {\n \tstruct config_options opts = { 0 };\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n \n \topts.respect_includes = 1;\n \topts.commondir = repo->commondir;\n@@ -2658,10 +2642,8 @@ static void repo_read_config(struct repository *repo)\n \t\tgit_configset_clear(repo->config);\n \n \tgit_configset_init(repo->config);\n-\tdata.config_set = repo->config;\n-\tdata.config_reader = &the_reader;\n-\n-\tif (config_with_options(config_set_callback, &data, NULL, &opts) < 0)\n+\tif (config_with_options(config_set_callback, repo->config, NULL,\n+\t\t\t\t&opts) < 0)\n \t\t/*\n \t\t * config_with_options() normally returns only\n \t\t * zero, as most errors are fatal, and\n@@ -2693,7 +2675,7 @@ static void repo_config_clear(struct repository *repo)\n void repo_config(struct repository *repo, config_fn_t fn, void *data)\n {\n \tgit_config_check_init(repo);\n-\tconfigset_iter(&the_reader, repo->config, fn, data);\n+\tconfigset_iter(repo->config, fn, data);\n }\n \n int repo_config_get(struct repository *repo, const char *key)\n@@ -2800,19 +2782,16 @@ static void read_protected_config(void)\n \t\t.ignore_worktree = 1,\n \t\t.system_gently = 1,\n \t};\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n \n \tgit_configset_init(&protected_config);\n-\tdata.config_set = &protected_config;\n-\tdata.config_reader = &the_reader;\n-\tconfig_with_options(config_set_callback, &data, NULL, &opts);\n+\tconfig_with_options(config_set_callback, &protected_config, NULL, &opts);\n }\n \n void git_protected_config(config_fn_t fn, void *data)\n {\n \tif (!protected_config.hash_initialized)\n \t\tread_protected_config();\n-\tconfigset_iter(&the_reader, &protected_config, fn, data);\n+\tconfigset_iter(&protected_config, fn, data);\n }\n \n /* Functions used historically to read configuration from 'the_repository' */\n-- \ngitgitgadget\n\n"},{"id":"477818","messageId":"7d9b9eefc7860959dbb6ed86d8c3d165a978a0b5.1685472134.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 13/14] config: add kvi.path, use it to evaluate includes","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:42:11Z","receivedAt":"2023-05-30T18:43:13Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nInclude directives are evaluated using the path of the config file. To\nreduce the dependence on \"config_reader.source\", add a new\n\"key_value_info.path\" member and use that instead of\n\"config_source.path\".\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 41 +++++++++++++++++++----------------------\n config.h |  1 +\n 2 files changed, 20 insertions(+), 22 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 4d381f93bcc..09950b46127 100644\n--- a/config.c\n+++ b/config.c\n@@ -154,7 +154,6 @@ struct config_include_data {\n \tvoid *data;\n \tconst struct config_options *opts;\n \tstruct git_config_source *config_source;\n-\tstruct config_reader *config_reader;\n \n \t/*\n \t * All remote URLs discovered when reading all config files.\n@@ -173,9 +172,7 @@ static const char include_depth_advice[] = N_(\n \"from\\n\"\n \"\t%s\\n\"\n \"This might be due to circular includes.\");\n-static int handle_path_include(struct config_source *cs,\n-\t\t\t       struct key_value_info *kvi,\n-\t\t\t       const char *path,\n+static int handle_path_include(struct key_value_info *kvi, const char *path,\n \t\t\t       struct config_include_data *inc)\n {\n \tint ret = 0;\n@@ -197,14 +194,14 @@ static int handle_path_include(struct config_source *cs,\n \tif (!is_absolute_path(path)) {\n \t\tchar *slash;\n \n-\t\tif (!cs || !cs->path) {\n+\t\tif (!kvi || !kvi->path) {\n \t\t\tret = error(_(\"relative config includes must come from files\"));\n \t\t\tgoto cleanup;\n \t\t}\n \n-\t\tslash = find_last_dir_sep(cs->path);\n+\t\tslash = find_last_dir_sep(kvi->path);\n \t\tif (slash)\n-\t\t\tstrbuf_add(&buf, cs->path, slash - cs->path + 1);\n+\t\t\tstrbuf_add(&buf, kvi->path, slash - kvi->path + 1);\n \t\tstrbuf_addstr(&buf, path);\n \t\tpath = buf.buf;\n \t}\n@@ -212,8 +209,8 @@ static int handle_path_include(struct config_source *cs,\n \tif (!access_or_die(path, R_OK, 0)) {\n \t\tif (++inc->depth > MAX_INCLUDE_DEPTH)\n \t\t\tdie(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,\n-\t\t\t    !cs ? \"<unknown>\" :\n-\t\t\t    cs->name ? cs->name :\n+\t\t\t    !kvi ? \"<unknown>\" :\n+\t\t\t    kvi->filename ? kvi->filename :\n \t\t\t    \"the command line\");\n \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n \t\t\t\t\t\t\tkvi->scope, NULL);\n@@ -231,7 +228,7 @@ static void add_trailing_starstar_for_dir(struct strbuf *pat)\n \t\tstrbuf_addstr(pat, \"**\");\n }\n \n-static int prepare_include_condition_pattern(struct config_source *cs,\n+static int prepare_include_condition_pattern(struct key_value_info *kvi,\n \t\t\t\t\t     struct strbuf *pat)\n {\n \tstruct strbuf path = STRBUF_INIT;\n@@ -248,11 +245,11 @@ static int prepare_include_condition_pattern(struct config_source *cs,\n \tif (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {\n \t\tconst char *slash;\n \n-\t\tif (!cs || !cs->path)\n+\t\tif (!kvi || !kvi->path)\n \t\t\treturn error(_(\"relative config include \"\n \t\t\t\t       \"conditionals must come from files\"));\n \n-\t\tstrbuf_realpath(&path, cs->path, 1);\n+\t\tstrbuf_realpath(&path, kvi->path, 1);\n \t\tslash = find_last_dir_sep(path.buf);\n \t\tif (!slash)\n \t\t\tBUG(\"how is this possible?\");\n@@ -267,7 +264,7 @@ static int prepare_include_condition_pattern(struct config_source *cs,\n \treturn prefix;\n }\n \n-static int include_by_gitdir(struct config_source *cs,\n+static int include_by_gitdir(struct key_value_info *kvi,\n \t\t\t     const struct config_options *opts,\n \t\t\t     const char *cond, size_t cond_len, int icase)\n {\n@@ -284,7 +281,7 @@ static int include_by_gitdir(struct config_source *cs,\n \n \tstrbuf_realpath(&text, git_dir, 1);\n \tstrbuf_add(&pattern, cond, cond_len);\n-\tprefix = prepare_include_condition_pattern(cs, &pattern);\n+\tprefix = prepare_include_condition_pattern(kvi, &pattern);\n \n again:\n \tif (prefix < 0)\n@@ -419,16 +416,16 @@ static int include_by_remote_url(struct config_include_data *inc,\n \t\t\t\t\t     inc->remote_urls);\n }\n \n-static int include_condition_is_true(struct config_source *cs,\n+static int include_condition_is_true(struct key_value_info *kvi,\n \t\t\t\t     struct config_include_data *inc,\n \t\t\t\t     const char *cond, size_t cond_len)\n {\n \tconst struct config_options *opts = inc->opts;\n \n \tif (skip_prefix_mem(cond, cond_len, \"gitdir:\", &cond, &cond_len))\n-\t\treturn include_by_gitdir(cs, opts, cond, cond_len, 0);\n+\t\treturn include_by_gitdir(kvi, opts, cond, cond_len, 0);\n \telse if (skip_prefix_mem(cond, cond_len, \"gitdir/i:\", &cond, &cond_len))\n-\t\treturn include_by_gitdir(cs, opts, cond, cond_len, 1);\n+\t\treturn include_by_gitdir(kvi, opts, cond, cond_len, 1);\n \telse if (skip_prefix_mem(cond, cond_len, \"onbranch:\", &cond, &cond_len))\n \t\treturn include_by_branch(cond, cond_len);\n \telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.*.url:\", &cond,\n@@ -443,7 +440,6 @@ static int git_config_include(const char *var, const char *value,\n \t\t\t      struct key_value_info *kvi, void *data)\n {\n \tstruct config_include_data *inc = data;\n-\tstruct config_source *cs = inc->config_reader->source;\n \tconst char *cond, *key;\n \tsize_t cond_len;\n \tint ret;\n@@ -457,16 +453,16 @@ static int git_config_include(const char *var, const char *value,\n \t\treturn ret;\n \n \tif (!strcmp(var, \"include.path\"))\n-\t\tret = handle_path_include(cs, kvi, value, inc);\n+\t\tret = handle_path_include(kvi, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n-\t    cond && include_condition_is_true(cs, inc, cond, cond_len) &&\n+\t    cond && include_condition_is_true(kvi, inc, cond, cond_len) &&\n \t    !strcmp(key, \"path\")) {\n \t\tconfig_fn_t old_fn = inc->fn;\n \n \t\tif (inc->opts->unconditional_remote_url)\n \t\t\tinc->fn = forbid_remote_url;\n-\t\tret = handle_path_include(cs, kvi, value, inc);\n+\t\tret = handle_path_include(kvi, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -648,6 +644,7 @@ void kvi_from_param(struct key_value_info *out)\n \tout->linenr = -1;\n \tout->origin_type = CONFIG_ORIGIN_CMDLINE;\n \tout->scope = CONFIG_SCOPE_COMMAND;\n+\tout->path = NULL;\n }\n \n int git_config_parse_parameter(const char *text,\n@@ -1048,6 +1045,7 @@ static void kvi_from_source(struct config_source *cs,\n \tout->origin_type = cs->origin_type;\n \tout->linenr = cs->linenr;\n \tout->scope = scope;\n+\tout->path = cs->path;\n }\n \n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n@@ -2254,7 +2252,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tinc.data = data;\n \t\tinc.opts = opts;\n \t\tinc.config_source = config_source;\n-\t\tinc.config_reader = &the_reader;\n \t\tfn = git_config_include;\n \t\tdata = &inc;\n \t}\ndiff --git a/config.h b/config.h\nindex 0fe56a4d650..123917d1b0a 100644\n--- a/config.h\n+++ b/config.h\n@@ -116,6 +116,7 @@ struct key_value_info {\n \tint linenr;\n \tenum config_origin_type origin_type;\n \tenum config_scope scope;\n+\tconst char *path;\n };\n \n /**\n-- \ngitgitgadget\n\n"},{"id":"477819","messageId":"9e35b5b1f4dbdeafca5be7516477dc033b7643a6.1685472134.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 14/14] config: pass source to config_parser_event_fn_t","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:42:12Z","receivedAt":"2023-05-30T18:43:16Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\n..so that the callback can use a \"struct config_source\" parameter\ninstead of \"config_reader.source\". \"struct config_source\" is internal to\nconfig.c, but this refactor is okay because this function has only ever\nbeen (and probably ever will be) used internally by config.c.\n\nAs a result, the_reader isn't used anywhere, so \"struct config_reader\"\nis obsolete (it was only intended to be used with the_reader). Remove\nthem.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 77 ++++++++++----------------------------------------------\n config.h |  6 +++++\n 2 files changed, 19 insertions(+), 64 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 09950b46127..c8d941ca627 100644\n--- a/config.c\n+++ b/config.c\n@@ -59,40 +59,6 @@ struct config_source {\n };\n #define CONFIG_SOURCE_INIT { 0 }\n \n-struct config_reader {\n-\t/*\n-\t * These members record the \"current\" config source, which can be\n-\t * accessed by parsing callbacks.\n-\t *\n-\t * The \"source\" variable will be non-NULL only when we are actually\n-\t * parsing a real config source (file, blob, cmdline, etc).\n-\t */\n-\tstruct config_source *source;\n-};\n-/*\n- * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n- * \"the_reader\". \"the_reader\" should only be used if that is infeasible, e.g. in\n- * a public function.\n- */\n-static struct config_reader the_reader;\n-\n-static inline void config_reader_push_source(struct config_reader *reader,\n-\t\t\t\t\t     struct config_source *top)\n-{\n-\ttop->prev = reader->source;\n-\treader->source = top;\n-}\n-\n-static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n-{\n-\tstruct config_source *ret;\n-\tif (!reader->source)\n-\t\tBUG(\"tried to pop config source, but we weren't reading config\");\n-\tret = reader->source;\n-\treader->source = reader->source->prev;\n-\treturn ret;\n-}\n-\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -738,14 +704,9 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tstruct strvec to_free = STRVEC_INIT;\n \tint ret = 0;\n \tchar *envw = NULL;\n-\tstruct config_source source = CONFIG_SOURCE_INIT;\n \tstruct key_value_info kvi = { 0 };\n \n-\tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n-\tconfig_reader_push_source(&the_reader, &source);\n-\n \tkvi_from_param(&kvi);\n-\n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n \t\tunsigned long count;\n@@ -802,7 +763,6 @@ out:\n \tstrbuf_release(&envvar);\n \tstrvec_clear(&to_free);\n \tfree(envw);\n-\tconfig_reader_pop_source(&the_reader);\n \treturn ret;\n }\n \n@@ -1028,7 +988,7 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \n \tif (data->previous_type != CONFIG_EVENT_EOF &&\n \t    data->opts->event_fn(data->previous_type, data->previous_offset,\n-\t\t\t\t offset, data->opts->event_fn_data) < 0)\n+\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n \t\treturn -1;\n \n \tdata->previous_type = type;\n@@ -1986,8 +1946,7 @@ int git_default_config(const char *var, const char *value,\n  * fgetc, ungetc, ftell of top need to be initialized before calling\n  * this function.\n  */\n-static int do_config_from(struct config_reader *reader,\n-\t\t\t  struct config_source *top, config_fn_t fn,\n+static int do_config_from(struct config_source *top, config_fn_t fn,\n \t\t\t  void *data, enum config_scope scope,\n \t\t\t  const struct config_options *opts)\n {\n@@ -2000,21 +1959,17 @@ static int do_config_from(struct config_reader *reader,\n \ttop->total_len = 0;\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n-\tconfig_reader_push_source(reader, top);\n \tkvi_from_source(top, scope, &kvi);\n \n \tret = git_parse_source(top, fn, &kvi, data, opts);\n \n-\t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n \tstrbuf_release(&top->var);\n-\tconfig_reader_pop_source(reader);\n \n \treturn ret;\n }\n \n-static int do_config_from_file(struct config_reader *reader,\n-\t\t\t       config_fn_t fn,\n+static int do_config_from_file(config_fn_t fn,\n \t\t\t       const enum config_origin_type origin_type,\n \t\t\t       const char *name, const char *path, FILE *f,\n \t\t\t       void *data, enum config_scope scope,\n@@ -2033,7 +1988,7 @@ static int do_config_from_file(struct config_reader *reader,\n \ttop.do_ftell = config_file_ftell;\n \n \tflockfile(f);\n-\tret = do_config_from(reader, &top, fn, data, scope, opts);\n+\tret = do_config_from(&top, fn, data, scope, opts);\n \tfunlockfile(f);\n \treturn ret;\n }\n@@ -2041,8 +1996,8 @@ static int do_config_from_file(struct config_reader *reader,\n static int git_config_from_stdin(config_fn_t fn, void *data,\n \t\t\t\t enum config_scope scope)\n {\n-\treturn do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, \"\",\n-\t\t\t\t   NULL, stdin, data, scope, NULL);\n+\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n+\t\t\t\t   data, scope, NULL);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2056,9 +2011,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \t\tBUG(\"filename cannot be NULL\");\n \tf = fopen_or_warn(filename, \"r\");\n \tif (f) {\n-\t\tret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,\n-\t\t\t\t\t  filename, filename, f, data, scope,\n-\t\t\t\t\t  opts);\n+\t\tret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,\n+\t\t\t\t\t  filename, f, data, scope, opts);\n \t\tfclose(f);\n \t}\n \treturn ret;\n@@ -2089,7 +2043,7 @@ int git_config_from_mem(config_fn_t fn,\n \ttop.do_ungetc = config_buf_ungetc;\n \ttop.do_ftell = config_buf_ftell;\n \n-\treturn do_config_from(&the_reader, &top, fn, data, scope, opts);\n+\treturn do_config_from(&top, fn, data, scope, opts);\n }\n \n int git_config_from_blob_oid(config_fn_t fn,\n@@ -2182,8 +2136,7 @@ int git_config_system(void)\n \treturn !git_env_bool(\"GIT_CONFIG_NOSYSTEM\", 0);\n }\n \n-static int do_git_config_sequence(struct config_reader *reader,\n-\t\t\t\t  const struct config_options *opts,\n+static int do_git_config_sequence(const struct config_options *opts,\n \t\t\t\t  config_fn_t fn, void *data)\n {\n \tint ret = 0;\n@@ -2272,7 +2225,7 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n \t\t\t\t\t       data, config_source->scope);\n \t} else {\n-\t\tret = do_git_config_sequence(&the_reader, opts, fn, data);\n+\t\tret = do_git_config_sequence(opts, fn, data);\n \t}\n \n \tif (inc.remote_urls) {\n@@ -2978,7 +2931,6 @@ void git_die_config(const char *key, const char *err, ...)\n  */\n \n struct config_store_data {\n-\tstruct config_reader *config_reader;\n \tsize_t baselen;\n \tchar *key;\n \tint do_not_match;\n@@ -3024,11 +2976,10 @@ static int matches(const char *key, const char *value,\n \t\t(value && !regexec(store->value_pattern, value, 0, NULL, 0));\n }\n \n-static int store_aux_event(enum config_event_t type,\n-\t\t\t   size_t begin, size_t end, void *data)\n+static int store_aux_event(enum config_event_t type, size_t begin, size_t end,\n+\t\t\t   struct config_source *cs, void *data)\n {\n \tstruct config_store_data *store = data;\n-\tstruct config_source *cs = store->config_reader->source;\n \n \tALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);\n \tstore->parsed[store->parsed_nr].begin = begin;\n@@ -3349,8 +3300,6 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \tsize_t contents_sz;\n \tstruct config_store_data store = CONFIG_STORE_INIT;\n \n-\tstore.config_reader = &the_reader;\n-\n \t/* parse-key returns negative; flip the sign to feed exit(3) */\n \tret = 0 - git_config_parse_key(key, &store.key, &store.baselen);\n \tif (ret)\ndiff --git a/config.h b/config.h\nindex 123917d1b0a..eaf24aba25d 100644\n--- a/config.h\n+++ b/config.h\n@@ -73,6 +73,7 @@ enum config_event_t {\n \tCONFIG_EVENT_ERROR\n };\n \n+struct config_source;\n /*\n  * The parser event function (if not NULL) is called with the event type and\n  * the begin/end offsets of the parsed elements.\n@@ -82,6 +83,7 @@ enum config_event_t {\n  */\n typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n \t\t\t\t\tsize_t begin_offset, size_t end_offset,\n+\t\t\t\t\tstruct config_source *cs,\n \t\t\t\t\tvoid *event_fn_data);\n \n struct config_options {\n@@ -101,6 +103,10 @@ struct config_options {\n \n \tconst char *commondir;\n \tconst char *git_dir;\n+\t/*\n+\t * event_fn and event_fn_data are for internal use only. Handles events\n+\t * emitted by the config parser.\n+\t */\n \tconfig_parser_event_fn_t event_fn;\n \tvoid *event_fn_data;\n \tenum config_error_action {\n-- \ngitgitgadget\n"},{"id":"477820","messageId":"504eb206b5a1ac12676752d68b2e2569dc6737ef.1685472134.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v2 11/14] config: pass kvi to die_bad_number()","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-05-30T18:42:09Z","receivedAt":"2023-05-30T18:43:18Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPlumb \"struct key_value_info\" through all code paths that end in\ndie_bad_number(), which lets us remove the helper functions that read\nanalogous values from \"struct config_reader\". As a result, nothing reads\nconfig_reader.config_kvi any more, so remove that too.\n\nIn config.c, this requires changing the signature of\ngit_configset_get_value() to 'return' \"kvi\" in an out parameter so that\ngit_configset_get_<type>() can pass it to git_config_<type>().\n\nOutside of config.c, config callbacks now need to pass \"kvi\" to any of\nthe git_config_<type>() functions that parse a config string into a\nnumber type. Included is a .cocci patch to make that refactor. In cases\nwhere \"kvi\" would never be used, pass NULL, e.g.:\n\n- In config.c, when we are parsing a boolean instead of a number\n- In builtin/config.c, when calling normalize_value() before setting\n  config to something the user gave us.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n archive-tar.c                              |   4 +-\n builtin/commit-graph.c                     |   4 +-\n builtin/commit.c                           |  10 +-\n builtin/config.c                           |  20 +--\n builtin/fetch.c                            |   4 +-\n builtin/fsmonitor--daemon.c                |   6 +-\n builtin/grep.c                             |   2 +-\n builtin/index-pack.c                       |   4 +-\n builtin/log.c                              |   2 +-\n builtin/pack-objects.c                     |  14 +-\n builtin/receive-pack.c                     |  10 +-\n builtin/submodule--helper.c                |   4 +-\n config.c                                   | 153 ++++++++-------------\n config.h                                   |  14 +-\n contrib/coccinelle/git_config_number.cocci |  27 ++++\n diff.c                                     |   9 +-\n fmt-merge-msg.c                            |   2 +-\n help.c                                     |   5 +-\n http.c                                     |  10 +-\n imap-send.c                                |   2 +-\n sequencer.c                                |  22 +--\n setup.c                                    |   2 +-\n submodule-config.c                         |  15 +-\n submodule-config.h                         |   3 +-\n t/helper/test-config.c                     |   6 +-\n upload-pack.c                              |  12 +-\n worktree.c                                 |   2 +-\n 27 files changed, 182 insertions(+), 186 deletions(-)\n create mode 100644 contrib/coccinelle/git_config_number.cocci\n\ndiff --git a/archive-tar.c b/archive-tar.c\nindex dcfbce5225a..1cd6d72d21e 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -411,14 +411,14 @@ static int tar_filter_config(const char *var, const char *value,\n }\n \n static int git_tar_config(const char *var, const char *value,\n-\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n+\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, \"tar.umask\")) {\n \t\tif (value && !strcmp(value, \"user\")) {\n \t\t\ttar_umask = umask(0);\n \t\t\tumask(tar_umask);\n \t\t} else {\n-\t\t\ttar_umask = git_config_int(var, value);\n+\t\t\ttar_umask = git_config_int(var, value, kvi);\n \t\t}\n \t\treturn 0;\n \t}\ndiff --git a/builtin/commit-graph.c b/builtin/commit-graph.c\nindex e811866b5dd..c99804abc7e 100644\n--- a/builtin/commit-graph.c\n+++ b/builtin/commit-graph.c\n@@ -185,11 +185,11 @@ static int write_option_max_new_filters(const struct option *opt,\n }\n \n static int git_commit_graph_write_config(const char *var, const char *value,\n-\t\t\t\t\t struct key_value_info *kvi UNUSED,\n+\t\t\t\t\t struct key_value_info *kvi,\n \t\t\t\t\t void *cb UNUSED)\n {\n \tif (!strcmp(var, \"commitgraph.maxnewfilters\"))\n-\t\twrite_opts.max_new_filters = git_config_int(var, value);\n+\t\twrite_opts.max_new_filters = git_config_int(var, value, kvi);\n \t/*\n \t * No need to fall-back to 'git_default_config', since this was already\n \t * called in 'cmd_commit_graph()'.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex ec468e87039..e846355ec39 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1412,7 +1412,8 @@ static int git_status_config(const char *k, const char *v,\n \t\treturn git_column_config(k, v, \"status\", &s->colopts);\n \tif (!strcmp(k, \"status.submodulesummary\")) {\n \t\tint is_bool;\n-\t\ts->submodule_summary = git_config_bool_or_int(k, v, &is_bool);\n+\t\ts->submodule_summary = git_config_bool_or_int(k, v, kvi,\n+\t\t\t\t\t\t\t      &is_bool);\n \t\tif (is_bool && s->submodule_summary)\n \t\t\ts->submodule_summary = -1;\n \t\treturn 0;\n@@ -1472,11 +1473,11 @@ static int git_status_config(const char *k, const char *v,\n \t}\n \tif (!strcmp(k, \"diff.renamelimit\")) {\n \t\tif (s->rename_limit == -1)\n-\t\t\ts->rename_limit = git_config_int(k, v);\n+\t\t\ts->rename_limit = git_config_int(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"status.renamelimit\")) {\n-\t\ts->rename_limit = git_config_int(k, v);\n+\t\ts->rename_limit = git_config_int(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"diff.renames\")) {\n@@ -1622,7 +1623,8 @@ static int git_commit_config(const char *k, const char *v,\n \t}\n \tif (!strcmp(k, \"commit.verbose\")) {\n \t\tint is_bool;\n-\t\tconfig_commit_verbose = git_config_bool_or_int(k, v, &is_bool);\n+\t\tconfig_commit_verbose = git_config_bool_or_int(k, v, kvi,\n+\t\t\t\t\t\t\t       &is_bool);\n \t\treturn 0;\n \t}\n \ndiff --git a/builtin/config.c b/builtin/config.c\nindex 2fcad601a7b..0a33bea26c0 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -254,13 +254,14 @@ static int format_config(struct strbuf *buf, const char *key_,\n \n \t\tif (type == TYPE_INT)\n \t\t\tstrbuf_addf(buf, \"%\"PRId64,\n-\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\"));\n+\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\", kvi));\n \t\telse if (type == TYPE_BOOL)\n \t\t\tstrbuf_addstr(buf, git_config_bool(key_, value_) ?\n \t\t\t\t      \"true\" : \"false\");\n \t\telse if (type == TYPE_BOOL_OR_INT) {\n \t\t\tint is_bool, v;\n-\t\t\tv = git_config_bool_or_int(key_, value_, &is_bool);\n+\t\t\tv = git_config_bool_or_int(key_, value_, kvi,\n+\t\t\t\t\t\t   &is_bool);\n \t\t\tif (is_bool)\n \t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n \t\t\telse\n@@ -414,7 +415,8 @@ free_strings:\n \treturn ret;\n }\n \n-static char *normalize_value(const char *key, const char *value)\n+static char *normalize_value(const char *key, const char *value,\n+\t\t\t     struct key_value_info *kvi)\n {\n \tif (!value)\n \t\treturn NULL;\n@@ -429,12 +431,12 @@ static char *normalize_value(const char *key, const char *value)\n \t\t */\n \t\treturn xstrdup(value);\n \tif (type == TYPE_INT)\n-\t\treturn xstrfmt(\"%\"PRId64, git_config_int64(key, value));\n+\t\treturn xstrfmt(\"%\"PRId64, git_config_int64(key, value, kvi));\n \tif (type == TYPE_BOOL)\n \t\treturn xstrdup(git_config_bool(key, value) ?  \"true\" : \"false\");\n \tif (type == TYPE_BOOL_OR_INT) {\n \t\tint is_bool, v;\n-\t\tv = git_config_bool_or_int(key, value, &is_bool);\n+\t\tv = git_config_bool_or_int(key, value, kvi, &is_bool);\n \t\tif (!is_bool)\n \t\t\treturn xstrfmt(\"%d\", v);\n \t\telse\n@@ -876,7 +878,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_SET) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 2);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], NULL);\n \t\tret = git_config_set_in_file_gently(given_config_source.file, argv[0], value);\n \t\tif (ret == CONFIG_NOTHING_SET)\n \t\t\terror(_(\"cannot overwrite multiple values with a single value\\n\"\n@@ -885,7 +887,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_SET_ALL) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 3);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], NULL);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value, argv[2],\n \t\t\t\t\t\t\t     flags);\n@@ -893,7 +895,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_ADD) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 2);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], NULL);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value,\n \t\t\t\t\t\t\t     CONFIG_REGEX_NONE,\n@@ -902,7 +904,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_REPLACE_ALL) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 3);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], NULL);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value, argv[2],\n \t\t\t\t\t\t\t     flags | CONFIG_FLAGS_MULTI_REPLACE);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex aa688291613..04cf5518d2c 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -125,7 +125,7 @@ static int git_fetch_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"submodule.fetchjobs\")) {\n-\t\tsubmodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v);\n+\t\tsubmodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v, kvi);\n \t\treturn 0;\n \t} else if (!strcmp(k, \"fetch.recursesubmodules\")) {\n \t\trecurse_submodules = parse_fetch_recurse_submodules_arg(k, v);\n@@ -133,7 +133,7 @@ static int git_fetch_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"fetch.parallel\")) {\n-\t\tfetch_parallel_config = git_config_int(k, v);\n+\t\tfetch_parallel_config = git_config_int(k, v, kvi);\n \t\tif (fetch_parallel_config < 0)\n \t\t\tdie(_(\"fetch.parallel cannot be negative\"));\n \t\tif (!fetch_parallel_config)\ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex a7375d61d02..cde4a575836 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -40,7 +40,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \t\t\t    struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(var, FSMONITOR__IPC_THREADS)) {\n-\t\tint i = git_config_int(var, value);\n+\t\tint i = git_config_int(var, value, kvi);\n \t\tif (i < 1)\n \t\t\treturn error(_(\"value of '%s' out of range: %d\"),\n \t\t\t\t     FSMONITOR__IPC_THREADS, i);\n@@ -49,7 +49,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, FSMONITOR__START_TIMEOUT)) {\n-\t\tint i = git_config_int(var, value);\n+\t\tint i = git_config_int(var, value, kvi);\n \t\tif (i < 0)\n \t\t\treturn error(_(\"value of '%s' out of range: %d\"),\n \t\t\t\t     FSMONITOR__START_TIMEOUT, i);\n@@ -59,7 +59,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \n \tif (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) {\n \t\tint is_bool;\n-\t\tint i = git_config_bool_or_int(var, value, &is_bool);\n+\t\tint i = git_config_bool_or_int(var, value, kvi, &is_bool);\n \t\tif (i < 0)\n \t\t\treturn error(_(\"value of '%s' not bool or int: %d\"),\n \t\t\t\t     var, i);\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 6e795f9f3a2..edb57f048ef 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -298,7 +298,7 @@ static int grep_cmd_config(const char *var, const char *value,\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\n-\t\tnum_threads = git_config_int(var, value);\n+\t\tnum_threads = git_config_int(var, value, kvi);\n \t\tif (num_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d) for %s\"),\n \t\t\t    num_threads, var);\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 4450510ddfc..e7685fa9a6f 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1584,13 +1584,13 @@ static int git_index_pack_config(const char *k, const char *v,\n \tstruct pack_idx_option *opts = cb;\n \n \tif (!strcmp(k, \"pack.indexversion\")) {\n-\t\topts->version = git_config_int(k, v);\n+\t\topts->version = git_config_int(k, v, kvi);\n \t\tif (opts->version > 2)\n \t\t\tdie(_(\"bad pack.indexVersion=%\"PRIu32), opts->version);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.threads\")) {\n-\t\tnr_threads = git_config_int(k, v);\n+\t\tnr_threads = git_config_int(k, v, kvi);\n \t\tif (nr_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d)\"),\n \t\t\t    nr_threads);\ndiff --git a/builtin/log.c b/builtin/log.c\nindex f8e61330491..805320a1abf 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -569,7 +569,7 @@ static int git_log_config(const char *var, const char *value,\n \tif (!strcmp(var, \"format.subjectprefix\"))\n \t\treturn git_config_string(&fmt_patch_subject_prefix, var, value);\n \tif (!strcmp(var, \"format.filenamemaxlength\")) {\n-\t\tfmt_patch_name_max = git_config_int(var, value);\n+\t\tfmt_patch_name_max = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"format.encodeemailheaders\")) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex ca023000cc0..cde11f83f81 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3138,23 +3138,23 @@ static int git_pack_config(const char *k, const char *v,\n \t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tif (!strcmp(k, \"pack.window\")) {\n-\t\twindow = git_config_int(k, v);\n+\t\twindow = git_config_int(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.windowmemory\")) {\n-\t\twindow_memory_limit = git_config_ulong(k, v);\n+\t\twindow_memory_limit = git_config_ulong(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.depth\")) {\n-\t\tdepth = git_config_int(k, v);\n+\t\tdepth = git_config_int(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.deltacachesize\")) {\n-\t\tmax_delta_cache_size = git_config_int(k, v);\n+\t\tmax_delta_cache_size = git_config_int(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.deltacachelimit\")) {\n-\t\tcache_max_small_delta_size = git_config_int(k, v);\n+\t\tcache_max_small_delta_size = git_config_int(k, v, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.writebitmaphashcache\")) {\n@@ -3180,7 +3180,7 @@ static int git_pack_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.threads\")) {\n-\t\tdelta_search_threads = git_config_int(k, v);\n+\t\tdelta_search_threads = git_config_int(k, v, kvi);\n \t\tif (delta_search_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d)\"),\n \t\t\t    delta_search_threads);\n@@ -3191,7 +3191,7 @@ static int git_pack_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.indexversion\")) {\n-\t\tpack_idx_opts.version = git_config_int(k, v);\n+\t\tpack_idx_opts.version = git_config_int(k, v, kvi);\n \t\tif (pack_idx_opts.version > 2)\n \t\t\tdie(_(\"bad pack.indexVersion=%\"PRIu32),\n \t\t\t    pack_idx_opts.version);\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 2f5fd2abbc3..d2bc0fead9f 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -154,12 +154,12 @@ static int receive_pack_config(const char *var, const char *value,\n \t}\n \n \tif (strcmp(var, \"receive.unpacklimit\") == 0) {\n-\t\treceive_unpack_limit = git_config_int(var, value);\n+\t\treceive_unpack_limit = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \n \tif (strcmp(var, \"transfer.unpacklimit\") == 0) {\n-\t\ttransfer_unpack_limit = git_config_int(var, value);\n+\t\ttransfer_unpack_limit = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \n@@ -227,7 +227,7 @@ static int receive_pack_config(const char *var, const char *value,\n \t\treturn git_config_string(&cert_nonce_seed, var, value);\n \n \tif (strcmp(var, \"receive.certnonceslop\") == 0) {\n-\t\tnonce_stamp_slop_limit = git_config_ulong(var, value);\n+\t\tnonce_stamp_slop_limit = git_config_ulong(var, value, kvi);\n \t\treturn 0;\n \t}\n \n@@ -242,12 +242,12 @@ static int receive_pack_config(const char *var, const char *value,\n \t}\n \n \tif (strcmp(var, \"receive.keepalive\") == 0) {\n-\t\tkeepalive_in_sec = git_config_int(var, value);\n+\t\tkeepalive_in_sec = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \n \tif (strcmp(var, \"receive.maxinputsize\") == 0) {\n-\t\tmax_input_size = git_config_int64(var, value);\n+\t\tmax_input_size = git_config_int64(var, value, kvi);\n \t\treturn 0;\n \t}\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 8570effbf0d..bda10764db5 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2187,13 +2187,13 @@ static int update_clone_task_finished(int result,\n }\n \n static int git_update_clone_config(const char *var, const char *value,\n-\t\t\t\t   struct key_value_info *kvi UNUSED,\n+\t\t\t\t   struct key_value_info *kvi,\n \t\t\t\t   void *cb)\n {\n \tint *max_jobs = cb;\n \n \tif (!strcmp(var, \"submodule.fetchjobs\"))\n-\t\t*max_jobs = parse_submodule_fetchjobs(var, value);\n+\t\t*max_jobs = parse_submodule_fetchjobs(var, value, kvi);\n \treturn 0;\n }\n \ndiff --git a/config.c b/config.c\nindex 819e9e5966c..34a111b2e1c 100644\n--- a/config.c\n+++ b/config.c\n@@ -66,18 +66,8 @@ struct config_reader {\n \t *\n \t * The \"source\" variable will be non-NULL only when we are actually\n \t * parsing a real config source (file, blob, cmdline, etc).\n-\t *\n-\t * The \"config_kvi\" variable will be non-NULL only when we are feeding\n-\t * cached config from a configset into a callback.\n-\t *\n-\t * They cannot be non-NULL at the same time. If they are both NULL, then\n-\t * we aren't parsing anything (and depending on the function looking at\n-\t * the variables, it's either a bug for it to be called in the first\n-\t * place, or it's a function which can be reused for non-config\n-\t * purposes, and should fall back to some sane behavior).\n \t */\n \tstruct config_source *source;\n-\tstruct key_value_info *config_kvi;\n };\n /*\n  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n@@ -89,8 +79,6 @@ static struct config_reader the_reader;\n static inline void config_reader_push_source(struct config_reader *reader,\n \t\t\t\t\t     struct config_source *top)\n {\n-\tif (reader->config_kvi)\n-\t\tBUG(\"source should not be set while iterating a config set\");\n \ttop->prev = reader->source;\n \treader->source = top;\n }\n@@ -105,12 +93,6 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\n \treturn ret;\n }\n \n-static inline void config_reader_set_kvi(struct config_reader *reader,\n-\t\t\t\t\t struct key_value_info *kvi)\n-{\n-\treader->config_kvi = kvi;\n-}\n-\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -1330,80 +1312,74 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)\n \treturn 1;\n }\n \n-static int reader_config_name(struct config_reader *reader, const char **out);\n-static int reader_origin_type(struct config_reader *reader,\n-\t\t\t      enum config_origin_type *type);\n NORETURN\n-static void die_bad_number(struct config_reader *reader, const char *name,\n-\t\t\t   const char *value)\n+static void die_bad_number(const char *name, const char *value,\n+\t\t\t   struct key_value_info *kvi)\n {\n \tconst char *error_type = (errno == ERANGE) ?\n \t\tN_(\"out of range\") : N_(\"invalid unit\");\n \tconst char *bad_numeric = N_(\"bad numeric config value '%s' for '%s': %s\");\n-\tconst char *config_name = NULL;\n-\tenum config_origin_type config_origin = CONFIG_ORIGIN_UNKNOWN;\n \n \tif (!value)\n \t\tvalue = \"\";\n \n-\t/* Ignoring the return value is okay since we handle missing values. */\n-\treader_config_name(reader, &config_name);\n-\treader_origin_type(reader, &config_origin);\n-\n-\tif (!config_name)\n+\tif (!kvi || !kvi->filename)\n \t\tdie(_(bad_numeric), value, name, _(error_type));\n \n-\tswitch (config_origin) {\n+\tswitch (kvi->origin_type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in blob %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_FILE:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in file %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_STDIN:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in standard input: %s\"),\n \t\t    value, name, _(error_type));\n \tcase CONFIG_ORIGIN_SUBMODULE_BLOB:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in submodule-blob %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_CMDLINE:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in command line %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tdefault:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \t}\n }\n \n-int git_config_int(const char *name, const char *value)\n+int git_config_int(const char *name, const char *value,\n+\t\t   struct key_value_info *kvi)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-int64_t git_config_int64(const char *name, const char *value)\n+int64_t git_config_int64(const char *name, const char *value, struct key_value_info *kvi)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-unsigned long git_config_ulong(const char *name, const char *value)\n+unsigned long git_config_ulong(const char *name, const char *value,\n+\t\t\t       struct key_value_info *kvi)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-ssize_t git_config_ssize_t(const char *name, const char *value)\n+ssize_t git_config_ssize_t(const char *name, const char *value,\n+\t\t\t   struct key_value_info *kvi)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n@@ -1508,7 +1484,8 @@ int git_parse_maybe_bool(const char *value)\n \treturn -1;\n }\n \n-int git_config_bool_or_int(const char *name, const char *value, int *is_bool)\n+int git_config_bool_or_int(const char *name, const char *value,\n+\t\t\t   struct key_value_info *kvi, int *is_bool)\n {\n \tint v = git_parse_maybe_bool_text(value);\n \tif (0 <= v) {\n@@ -1516,7 +1493,7 @@ int git_config_bool_or_int(const char *name, const char *value, int *is_bool)\n \t\treturn v;\n \t}\n \t*is_bool = 0;\n-\treturn git_config_int(name, value);\n+\treturn git_config_int(name, value, kvi);\n }\n \n int git_config_bool(const char *name, const char *value)\n@@ -1642,7 +1619,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t\telse if (!git_parse_maybe_bool_text(value))\n \t\t\tdefault_abbrev = the_hash_algo->hexsz;\n \t\telse {\n-\t\t\tint abbrev = git_config_int(var, value);\n+\t\t\tint abbrev = git_config_int(var, value, kvi);\n \t\t\tif (abbrev < minimum_abbrev || abbrev > the_hash_algo->hexsz)\n \t\t\t\treturn error(_(\"abbrev length out of range: %d\"), abbrev);\n \t\t\tdefault_abbrev = abbrev;\n@@ -1654,7 +1631,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t\treturn set_disambiguate_hint_config(var, value);\n \n \tif (!strcmp(var, \"core.loosecompression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -1665,7 +1642,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.compression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -1679,7 +1656,7 @@ static int git_default_core_config(const char *var, const char *value,\n \n \tif (!strcmp(var, \"core.packedgitwindowsize\")) {\n \t\tint pgsz_x2 = getpagesize() * 2;\n-\t\tpacked_git_window_size = git_config_ulong(var, value);\n+\t\tpacked_git_window_size = git_config_ulong(var, value, kvi);\n \n \t\t/* This value must be multiple of (pagesize * 2) */\n \t\tpacked_git_window_size /= pgsz_x2;\n@@ -1690,17 +1667,17 @@ static int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.bigfilethreshold\")) {\n-\t\tbig_file_threshold = git_config_ulong(var, value);\n+\t\tbig_file_threshold = git_config_ulong(var, value, kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"core.packedgitlimit\")) {\n-\t\tpacked_git_limit = git_config_ulong(var, value);\n+\t\tpacked_git_limit = git_config_ulong(var, value, kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"core.deltabasecachelimit\")) {\n-\t\tdelta_base_cache_limit = git_config_ulong(var, value);\n+\t\tdelta_base_cache_limit = git_config_ulong(var, value, kvi);\n \t\treturn 0;\n \t}\n \n@@ -1984,12 +1961,12 @@ int git_default_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"pack.packsizelimit\")) {\n-\t\tpack_size_limit_cfg = git_config_ulong(var, value);\n+\t\tpack_size_limit_cfg = git_config_ulong(var, value, kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"pack.compression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -2323,11 +2300,8 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \t\tvalues = &entry->value_list;\n \t\tkvi = values->items[value_index].util;\n \n-\t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n-\n \t\tif (fn(entry->key, values->items[value_index].string, kvi, data) < 0)\n \t\t\tgit_die_config_linenr(entry->key, kvi->filename, kvi->linenr);\n-\t\tconfig_reader_set_kvi(reader, NULL);\n \t}\n }\n \n@@ -2509,11 +2483,12 @@ int git_configset_add_file(struct config_set *set, const char *filename)\n \treturn git_config_from_file(config_set_callback, filename, &data);\n }\n \n-int git_configset_get_value(struct config_set *set, const char *key, const char **value)\n+int git_configset_get_value(struct config_set *set, const char *key,\n+\t\t\t    const char **value, struct key_value_info *kvi)\n {\n \tconst struct string_list *values = NULL;\n \tint ret;\n-\n+\tstruct string_list_item item;\n \t/*\n \t * Follows \"last one wins\" semantic, i.e., if there are multiple matches for the\n \t * queried key in the files of the configset, the value returned will be the last\n@@ -2523,7 +2498,10 @@ int git_configset_get_value(struct config_set *set, const char *key, const char\n \t\treturn ret;\n \n \tassert(values->nr > 0);\n-\t*value = values->items[values->nr - 1].string;\n+\titem = values->items[values->nr - 1];\n+\t*value = item.string;\n+\tif (kvi)\n+\t\t*kvi = *((struct key_value_info *)item.util);\n \treturn 0;\n }\n \n@@ -2576,7 +2554,7 @@ int git_configset_get(struct config_set *set, const char *key)\n int git_configset_get_string(struct config_set *set, const char *key, char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value))\n+\tif (!git_configset_get_value(set, key, &value, NULL))\n \t\treturn git_config_string((const char **)dest, key, value);\n \telse\n \t\treturn 1;\n@@ -2586,7 +2564,7 @@ static int git_configset_get_string_tmp(struct config_set *set, const char *key,\n \t\t\t\t\tconst char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(key);\n \t\t*dest = value;\n@@ -2599,8 +2577,10 @@ static int git_configset_get_string_tmp(struct config_set *set, const char *key,\n int git_configset_get_int(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_int(key, value);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_int(key, value, &kvi);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2609,8 +2589,10 @@ int git_configset_get_int(struct config_set *set, const char *key, int *dest)\n int git_configset_get_ulong(struct config_set *set, const char *key, unsigned long *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_ulong(key, value);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_ulong(key, value, &kvi);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2619,7 +2601,7 @@ int git_configset_get_ulong(struct config_set *set, const char *key, unsigned lo\n int git_configset_get_bool(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\t*dest = git_config_bool(key, value);\n \t\treturn 0;\n \t} else\n@@ -2630,8 +2612,10 @@ int git_configset_get_bool_or_int(struct config_set *set, const char *key,\n \t\t\t\tint *is_bool, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_bool_or_int(key, value, is_bool);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_bool_or_int(key, value, &kvi, is_bool);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2640,7 +2624,7 @@ int git_configset_get_bool_or_int(struct config_set *set, const char *key,\n int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\t*dest = git_parse_maybe_bool(value);\n \t\tif (*dest == -1)\n \t\t\treturn -1;\n@@ -2652,7 +2636,7 @@ int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *d\n int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value))\n+\tif (!git_configset_get_value(set, key, &value, NULL))\n \t\treturn git_config_pathname(dest, key, value);\n \telse\n \t\treturn 1;\n@@ -2722,7 +2706,7 @@ int repo_config_get_value(struct repository *repo,\n \t\t\t  const char *key, const char **value)\n {\n \tgit_config_check_init(repo);\n-\treturn git_configset_get_value(repo->config, key, value);\n+\treturn git_configset_get_value(repo->config, key, value, NULL);\n }\n \n int repo_config_get_value_multi(struct repository *repo, const char *key,\n@@ -3946,18 +3930,6 @@ int parse_config_key(const char *var,\n \treturn 0;\n }\n \n-static int reader_origin_type(struct config_reader *reader,\n-\t\t\t      enum config_origin_type *type)\n-{\n-\tif (the_reader.config_kvi)\n-\t\t*type = reader->config_kvi->origin_type;\n-\telse if(the_reader.source)\n-\t\t*type = reader->source->origin_type;\n-\telse\n-\t\treturn 1;\n-\treturn 0;\n-}\n-\n const char *config_origin_type_name(enum config_origin_type type)\n {\n \tswitch (type) {\n@@ -3996,17 +3968,6 @@ const char *config_scope_name(enum config_scope scope)\n \t}\n }\n \n-static int reader_config_name(struct config_reader *reader, const char **out)\n-{\n-\tif (the_reader.config_kvi)\n-\t\t*out = reader->config_kvi->filename;\n-\telse if (the_reader.source)\n-\t\t*out = reader->source->name;\n-\telse\n-\t\treturn 1;\n-\treturn 0;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex 9e3c2bdb429..0fe56a4d650 100644\n--- a/config.h\n+++ b/config.h\n@@ -229,22 +229,23 @@ int git_parse_maybe_bool(const char *);\n  * Parse the string to an integer, including unit factors. Dies on error;\n  * otherwise, returns the parsed result.\n  */\n-int git_config_int(const char *, const char *);\n+int git_config_int(const char *, const char *, struct key_value_info *);\n \n-int64_t git_config_int64(const char *, const char *);\n+int64_t git_config_int64(const char *, const char *, struct key_value_info *);\n \n /**\n  * Identical to `git_config_int`, but for unsigned longs.\n  */\n-unsigned long git_config_ulong(const char *, const char *);\n+unsigned long git_config_ulong(const char *, const char *, struct key_value_info *);\n \n-ssize_t git_config_ssize_t(const char *, const char *);\n+ssize_t git_config_ssize_t(const char *, const char *, struct key_value_info *);\n \n /**\n  * Same as `git_config_bool`, except that integers are returned as-is, and\n  * an `is_bool` flag is unset.\n  */\n-int git_config_bool_or_int(const char *, const char *, int *);\n+int git_config_bool_or_int(const char *, const char *, struct key_value_info *,\n+\t\t\t   int *);\n \n /**\n  * Parse a string into a boolean value, respecting keywords like \"true\" and\n@@ -509,7 +510,8 @@ int git_configset_get(struct config_set *cs, const char *key);\n  * touching `value`. The caller should not free or modify `value`, as it\n  * is owned by the cache.\n  */\n-int git_configset_get_value(struct config_set *cs, const char *key, const char **dest);\n+int git_configset_get_value(struct config_set *cs, const char *key,\n+\t\t\t    const char **dest, struct key_value_info *kvi);\n \n int git_configset_get_string(struct config_set *cs, const char *key, char **dest);\n int git_configset_get_int(struct config_set *cs, const char *key, int *dest);\ndiff --git a/contrib/coccinelle/git_config_number.cocci b/contrib/coccinelle/git_config_number.cocci\nnew file mode 100644\nindex 00000000000..f46c74dd23c\n--- /dev/null\n+++ b/contrib/coccinelle/git_config_number.cocci\n@@ -0,0 +1,27 @@\n+@@\n+identifier C1, C2, C3;\n+@@\n+(\n+(\n+git_config_int\n+|\n+git_config_int64\n+|\n+git_config_ulong\n+|\n+git_config_ssize_t\n+)\n+  (C1, C2\n++ , kvi\n+  )\n+|\n+(\n+git_configset_get_value\n+|\n+git_config_bool_or_int\n+)\n+  (C1, C2,\n++ kvi,\n+  C3\n+  )\n+)\ndiff --git a/diff.c b/diff.c\nindex d7ed2dc900b..da7cd353a6d 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -372,13 +372,14 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.context\")) {\n-\t\tdiff_context_default = git_config_int(var, value);\n+\t\tdiff_context_default = git_config_int(var, value, kvi);\n \t\tif (diff_context_default < 0)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.interhunkcontext\")) {\n-\t\tdiff_interhunk_context_default = git_config_int(var, value);\n+\t\tdiff_interhunk_context_default = git_config_int(var, value,\n+\t\t\t\t\t\t\t\tkvi);\n \t\tif (diff_interhunk_context_default < 0)\n \t\t\treturn -1;\n \t\treturn 0;\n@@ -404,7 +405,7 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.statgraphwidth\")) {\n-\t\tdiff_stat_graph_width = git_config_int(var, value);\n+\t\tdiff_stat_graph_width = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.external\"))\n@@ -443,7 +444,7 @@ int git_diff_basic_config(const char *var, const char *value,\n \tconst char *name;\n \n \tif (!strcmp(var, \"diff.renamelimit\")) {\n-\t\tdiff_rename_limit_default = git_config_int(var, value);\n+\t\tdiff_rename_limit_default = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 97358034fa0..d1b59af44bb 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -24,7 +24,7 @@ int fmt_merge_msg_config(const char *key, const char *value,\n {\n \tif (!strcmp(key, \"merge.log\") || !strcmp(key, \"merge.summary\")) {\n \t\tint is_bool;\n-\t\tmerge_log_config = git_config_bool_or_int(key, value, &is_bool);\n+\t\tmerge_log_config = git_config_bool_or_int(key, value, kvi, &is_bool);\n \t\tif (!is_bool && merge_log_config < 0)\n \t\t\treturn error(\"%s: negative length %s\", key, value);\n \t\tif (is_bool && merge_log_config)\ndiff --git a/help.c b/help.c\nindex 43d1eb702cd..08f0b953736 100644\n--- a/help.c\n+++ b/help.c\n@@ -545,8 +545,7 @@ static struct cmdnames aliases;\n #define AUTOCORRECT_IMMEDIATELY (-1)\n \n static int git_unknown_cmd_config(const char *var, const char *value,\n-\t\t\t\t  struct key_value_info *kvi UNUSED,\n-\t\t\t\t  void *cb UNUSED)\n+\t\t\t\t  struct key_value_info *kvi, void *cb UNUSED)\n {\n \tconst char *p;\n \n@@ -560,7 +559,7 @@ static int git_unknown_cmd_config(const char *var, const char *value,\n \t\t} else if (!strcmp(value, \"prompt\")) {\n \t\t\tautocorrect = AUTOCORRECT_PROMPT;\n \t\t} else {\n-\t\t\tint v = git_config_int(var, value);\n+\t\t\tint v = git_config_int(var, value, kvi);\n \t\t\tautocorrect = (v < 0)\n \t\t\t\t? AUTOCORRECT_IMMEDIATELY : v;\n \t\t}\ndiff --git a/http.c b/http.c\nindex 3d4292eba6a..a26c3dff827 100644\n--- a/http.c\n+++ b/http.c\n@@ -412,21 +412,21 @@ static int http_options(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(\"http.minsessions\", var)) {\n-\t\tmin_curl_sessions = git_config_int(var, value);\n+\t\tmin_curl_sessions = git_config_int(var, value, kvi);\n \t\tif (min_curl_sessions > 1)\n \t\t\tmin_curl_sessions = 1;\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.maxrequests\", var)) {\n-\t\tmax_requests = git_config_int(var, value);\n+\t\tmax_requests = git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.lowspeedlimit\", var)) {\n-\t\tcurl_low_speed_limit = (long)git_config_int(var, value);\n+\t\tcurl_low_speed_limit = (long)git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.lowspeedtime\", var)) {\n-\t\tcurl_low_speed_time = (long)git_config_int(var, value);\n+\t\tcurl_low_speed_time = (long)git_config_int(var, value, kvi);\n \t\treturn 0;\n \t}\n \n@@ -462,7 +462,7 @@ static int http_options(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(\"http.postbuffer\", var)) {\n-\t\thttp_post_buffer = git_config_ssize_t(var, value);\n+\t\thttp_post_buffer = git_config_ssize_t(var, value, kvi);\n \t\tif (http_post_buffer < 0)\n \t\t\twarning(_(\"negative value for http.postBuffer; defaulting to %d\"), LARGE_PACKET_MAX);\n \t\tif (http_post_buffer < LARGE_PACKET_MAX)\ndiff --git a/imap-send.c b/imap-send.c\nindex 3cc98f1a0a5..3c391a52c5a 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1341,7 +1341,7 @@ static int git_imap_config(const char *var, const char *val,\n \telse if (!strcmp(\"imap.authmethod\", var))\n \t\treturn git_config_string(&server.auth_method, var, val);\n \telse if (!strcmp(\"imap.port\", var))\n-\t\tserver.port = git_config_int(var, val);\n+\t\tserver.port = git_config_int(var, val, kvi);\n \telse if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\tgit_die_config(\"imap.host\", \"Missing value for 'imap.host'\");\ndiff --git a/sequencer.c b/sequencer.c\nindex 171561c2cdb..76b4750b4bd 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2878,7 +2878,7 @@ static int git_config_string_dup(char **dest,\n }\n \n static int populate_opts_cb(const char *key, const char *value,\n-\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n+\t\t\t    struct key_value_info *kvi, void *data)\n {\n \tstruct replay_opts *opts = data;\n \tint error_flag = 1;\n@@ -2886,26 +2886,26 @@ static int populate_opts_cb(const char *key, const char *value,\n \tif (!value)\n \t\terror_flag = 0;\n \telse if (!strcmp(key, \"options.no-commit\"))\n-\t\topts->no_commit = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->no_commit = git_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.edit\"))\n-\t\topts->edit = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->edit = git_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-empty\"))\n \t\topts->allow_empty =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-empty-message\"))\n \t\topts->allow_empty_message =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.keep-redundant-commits\"))\n \t\topts->keep_redundant_commits =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.signoff\"))\n-\t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->signoff = git_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.record-origin\"))\n-\t\topts->record_origin = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->record_origin = git_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-ff\"))\n-\t\topts->allow_ff = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->allow_ff = git_config_bool_or_int(key, value, kvi, &error_flag);\n \telse if (!strcmp(key, \"options.mainline\"))\n-\t\topts->mainline = git_config_int(key, value);\n+\t\topts->mainline = git_config_int(key, value, kvi);\n \telse if (!strcmp(key, \"options.strategy\"))\n \t\tgit_config_string_dup(&opts->strategy, key, value);\n \telse if (!strcmp(key, \"options.gpg-sign\"))\n@@ -2915,7 +2915,7 @@ static int populate_opts_cb(const char *key, const char *value,\n \t\topts->xopts[opts->xopts_nr++] = xstrdup(value);\n \t} else if (!strcmp(key, \"options.allow-rerere-auto\"))\n \t\topts->allow_rerere_auto =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag) ?\n+\t\t\tgit_config_bool_or_int(key, value, kvi, &error_flag) ?\n \t\t\t\tRERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;\n \telse if (!strcmp(key, \"options.default-msg-cleanup\")) {\n \t\topts->explicit_cleanup = 1;\ndiff --git a/setup.c b/setup.c\nindex a461dd15233..75934d7438f 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -594,7 +594,7 @@ static int check_repo_format(const char *var, const char *value,\n \tconst char *ext;\n \n \tif (strcmp(var, \"core.repositoryformatversion\") == 0)\n-\t\tdata->version = git_config_int(var, value);\n+\t\tdata->version = git_config_int(var, value, kvi);\n \telse if (skip_prefix(var, \"extensions.\", &ext)) {\n \t\tswitch (handle_extension_v0(var, value, ext, data)) {\n \t\tcase EXTENSION_ERROR:\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 7d773f33621..b86547fd1ee 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -302,9 +302,10 @@ static int parse_fetch_recurse(const char *opt, const char *arg,\n \t}\n }\n \n-int parse_submodule_fetchjobs(const char *var, const char *value)\n+int parse_submodule_fetchjobs(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi)\n {\n-\tint fetchjobs = git_config_int(var, value);\n+\tint fetchjobs = git_config_int(var, value, kvi);\n \tif (fetchjobs < 0)\n \t\tdie(_(\"negative values not allowed for submodule.fetchJobs\"));\n \tif (!fetchjobs)\n@@ -848,14 +849,13 @@ struct fetch_config {\n };\n \n static int gitmodules_fetch_config(const char *var, const char *value,\n-\t\t\t\t   struct key_value_info *kvi UNUSED,\n-\t\t\t\t   void *cb)\n+\t\t\t\t   struct key_value_info *kvi, void *cb)\n {\n \tstruct fetch_config *config = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\")) {\n \t\tif (config->max_children)\n \t\t\t*(config->max_children) =\n-\t\t\t\tparse_submodule_fetchjobs(var, value);\n+\t\t\t\tparse_submodule_fetchjobs(var, value, kvi);\n \t\treturn 0;\n \t} else if (!strcmp(var, \"fetch.recursesubmodules\")) {\n \t\tif (config->recurse_submodules)\n@@ -877,12 +877,11 @@ void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)\n }\n \n static int gitmodules_update_clone_config(const char *var, const char *value,\n-\t\t\t\t\t  struct key_value_info *kvi UNUSED,\n-\t\t\t\t\t  void *cb)\n+\t\t\t\t\t  struct key_value_info *kvi, void *cb)\n {\n \tint *max_jobs = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\"))\n-\t\t*max_jobs = parse_submodule_fetchjobs(var, value);\n+\t\t*max_jobs = parse_submodule_fetchjobs(var, value, kvi);\n \treturn 0;\n }\n \ndiff --git a/submodule-config.h b/submodule-config.h\nindex c2045875bbb..944cae75cc9 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -50,7 +50,8 @@ struct repository;\n \n void submodule_cache_free(struct submodule_cache *cache);\n \n-int parse_submodule_fetchjobs(const char *var, const char *value);\n+int parse_submodule_fetchjobs(const char *var, const char *value,\n+\t\t\t      struct key_value_info *kvi);\n int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);\n struct option;\n int option_fetch_parse_recurse_submodules(const struct option *opt,\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 7027ffa187f..737505583d4 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -61,12 +61,12 @@ static int iterate_cb(const char *var, const char *value,\n }\n \n static int parse_int_cb(const char *var, const char *value,\n-\t\t\tstruct key_value_info *kvi UNUSED, void *data)\n+\t\t\tstruct key_value_info *kvi, void *data)\n {\n \tconst char *key_to_match = data;\n \n \tif (!strcmp(key_to_match, var)) {\n-\t\tint parsed = git_config_int(value, value);\n+\t\tint parsed = git_config_int(value, value, kvi);\n \t\tprintf(\"%d\\n\", parsed);\n \t}\n \treturn 0;\n@@ -179,7 +179,7 @@ int cmd__config(int argc, const char **argv)\n \t\t\t\tgoto exit2;\n \t\t\t}\n \t\t}\n-\t\tif (!git_configset_get_value(&cs, argv[2], &v)) {\n+\t\tif (!git_configset_get_value(&cs, argv[2], &v, NULL)) {\n \t\t\tif (!v)\n \t\t\t\tprintf(\"(NULL)\\n\");\n \t\t\telse\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 5f8232ff078..7cf776cde91 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1264,7 +1264,8 @@ static int find_symref(const char *refname,\n }\n \n static int parse_object_filter_config(const char *var, const char *value,\n-\t\t\t\t       struct upload_pack_data *data)\n+\t\t\t\t      struct key_value_info *kvi,\n+\t\t\t\t      struct upload_pack_data *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *sub, *key;\n@@ -1291,7 +1292,8 @@ static int parse_object_filter_config(const char *var, const char *value,\n \t\t}\n \t\tstring_list_insert(&data->allowed_filters, buf.buf)->util =\n \t\t\t(void *)(intptr_t)1;\n-\t\tdata->tree_filter_max_depth = git_config_ulong(var, value);\n+\t\tdata->tree_filter_max_depth = git_config_ulong(var, value,\n+\t\t\t\t\t\t\t       kvi);\n \t}\n \n \tstrbuf_release(&buf);\n@@ -1299,7 +1301,7 @@ static int parse_object_filter_config(const char *var, const char *value,\n }\n \n static int upload_pack_config(const char *var, const char *value,\n-\t\t\t      struct key_value_info *kvi UNUSED,\n+\t\t\t      struct key_value_info *kvi,\n \t\t\t      void *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n@@ -1320,7 +1322,7 @@ static int upload_pack_config(const char *var, const char *value,\n \t\telse\n \t\t\tdata->allow_uor &= ~ALLOW_ANY_SHA1;\n \t} else if (!strcmp(\"uploadpack.keepalive\", var)) {\n-\t\tdata->keepalive = git_config_int(var, value);\n+\t\tdata->keepalive = git_config_int(var, value, kvi);\n \t\tif (!data->keepalive)\n \t\t\tdata->keepalive = -1;\n \t} else if (!strcmp(\"uploadpack.allowfilter\", var)) {\n@@ -1335,7 +1337,7 @@ static int upload_pack_config(const char *var, const char *value,\n \t\tdata->advertise_sid = git_config_bool(var, value);\n \t}\n \n-\tif (parse_object_filter_config(var, value, data) < 0)\n+\tif (parse_object_filter_config(var, value, kvi, data) < 0)\n \t\treturn -1;\n \n \treturn parse_hide_refs_config(var, value, \"uploadpack\", &data->hidden_refs);\ndiff --git a/worktree.c b/worktree.c\nindex b5ee71c5ebd..1fbdbd745fb 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -835,7 +835,7 @@ int init_worktree_config(struct repository *r)\n \t * Relocate that value to avoid breaking all worktrees with this\n \t * upgrade to worktree config.\n \t */\n-\tif (!git_configset_get_value(&cs, \"core.worktree\", &core_worktree)) {\n+\tif (!git_configset_get_value(&cs, \"core.worktree\", &core_worktree, NULL)) {\n \t\tif ((res = move_config_setting(\"core.worktree\", core_worktree,\n \t\t\t\t\t       common_config_file,\n \t\t\t\t\t       main_worktree_file)))\n-- \ngitgitgadget\n\n"},{"id":"477865","messageId":"6faf1b17-a1ca-0c22-2e43-aee121c4e36a@gmail.com","threadId":"59629","inReplyTo":"6834e37066e7877646fc7c37aa79704d14381251.1685472133.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 03/14] (RFC-only) config: add kvi arg to config_fn_t","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-06-01T09:50:55Z","receivedAt":"2023-06-01T09:51:16Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Glen\n\nOn 30/05/2023 19:42, Glen Choo via GitGitGadget wrote:\n> From: Glen Choo <chooglen@google.com>\n> \n> ..without actually changing any of its implementations. This commit does\n> not build - I've split this out for readability, but post-RFC I will\n> squash this with the rest of the refactor + cocci changes.\n\nWhile this ends up with a huge amount of churn, I think that is probably \ninevitable if we're going to get rid of global state (I see Ævar \nsuggested adding a separate set of callbacks but I'm not sure how that \nwould work with chaining up to git_default_config() and it would be nice \nto improve our error messages with filename and line information). I've \nnot been following this thread all that closely but a couple of thoughts \ncrossed by mind.\n\n  - is it worth making struct key_value_info opaque and provide getters\n    for the fields so we can change the implementation in the future\n    without having to modify every user. We could rename it\n    config_context or something generic like that if we think it might\n    grow in scope in the future.\n\n  - (probably impractical) could we stuff the key and value into struct\n    key_value_info so config_fn_t becomes\n    fn(const struct key_value_info, void *data)\n    that would get rid of all the UNUSED annotations but would mean even\n    more churn. The advantage is that one could add functions like\n    kvi_bool_or_int(kvi, &is_bool) and get good error messages because\n    all the config parsing functions would all have access to location\n    information.\n\nBest Wishes\n\nPhillip\n\n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n>   config.c                                      |   8 +-\n>   config.h                                      |  16 +-\n>   .../coccinelle/config_fn_kvi.pending.cocci    | 146 ++++++++++++++++++\n>   3 files changed, 158 insertions(+), 12 deletions(-)\n>   create mode 100644 contrib/coccinelle/config_fn_kvi.pending.cocci\n> \n> diff --git a/config.c b/config.c\n> index 493f47df8ae..945f4f3b77e 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -489,7 +489,7 @@ static int git_config_include(const char *var, const char *value, void *data)\n>   \t * Pass along all values, including \"include\" directives; this makes it\n>   \t * possible to query information on the includes themselves.\n>   \t */\n> -\tret = inc->fn(var, value, inc->data);\n> +\tret = inc->fn(var, value, NULL, inc->data);\n>   \tif (ret < 0)\n>   \t\treturn ret;\n>   \n> @@ -671,7 +671,7 @@ static int config_parse_pair(const char *key, const char *value,\n>   \tif (git_config_parse_key(key, &canonical_name, NULL))\n>   \t\treturn -1;\n>   \n> -\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n> +\tret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;\n>   \tfree(canonical_name);\n>   \treturn ret;\n>   }\n> @@ -959,7 +959,7 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n>   \t * accurate line number in error messages.\n>   \t */\n>   \tcs->linenr--;\n> -\tret = fn(name->buf, value, data);\n> +\tret = fn(name->buf, value, NULL, data);\n>   \tif (ret >= 0)\n>   \t\tcs->linenr++;\n>   \treturn ret;\n> @@ -2303,7 +2303,7 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n>   \n>   \t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n>   \n> -\t\tif (fn(entry->key, values->items[value_index].string, data) < 0)\n> +\t\tif (fn(entry->key, values->items[value_index].string, NULL, data) < 0)\n>   \t\t\tgit_die_config_linenr(entry->key,\n>   \t\t\t\t\t      reader->config_kvi->filename,\n>   \t\t\t\t\t      reader->config_kvi->linenr);\n> diff --git a/config.h b/config.h\n> index 247b572b37b..9d052c52c3c 100644\n> --- a/config.h\n> +++ b/config.h\n> @@ -111,6 +111,13 @@ struct config_options {\n>   \t} error_action;\n>   };\n>   \n> +struct key_value_info {\n> +\tconst char *filename;\n> +\tint linenr;\n> +\tenum config_origin_type origin_type;\n> +\tenum config_scope scope;\n> +};\n> +\n>   /**\n>    * A config callback function takes three parameters:\n>    *\n> @@ -129,7 +136,7 @@ struct config_options {\n>    * A config callback should return 0 for success, or -1 if the variable\n>    * could not be parsed properly.\n>    */\n> -typedef int (*config_fn_t)(const char *, const char *, void *);\n> +typedef int (*config_fn_t)(const char *, const char *, struct key_value_info *, void *);\n>   \n>   int git_default_config(const char *, const char *, void *);\n>   \n> @@ -667,13 +674,6 @@ int git_config_get_expiry(const char *key, const char **output);\n>   /* parse either \"this many days\" integer, or \"5.days.ago\" approxidate */\n>   int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t now);\n>   \n> -struct key_value_info {\n> -\tconst char *filename;\n> -\tint linenr;\n> -\tenum config_origin_type origin_type;\n> -\tenum config_scope scope;\n> -};\n> -\n>   /**\n>    * First prints the error message specified by the caller in `err` and then\n>    * dies printing the line number and the file name of the highest priority\n> diff --git a/contrib/coccinelle/config_fn_kvi.pending.cocci b/contrib/coccinelle/config_fn_kvi.pending.cocci\n> new file mode 100644\n> index 00000000000..d4c84599afa\n> --- /dev/null\n> +++ b/contrib/coccinelle/config_fn_kvi.pending.cocci\n> @@ -0,0 +1,146 @@\n> +// These are safe to apply to *.c *.h builtin/*.c\n> +\n> +@ get_fn @\n> +identifier fn, R;\n> +@@\n> +(\n> +(\n> +git_config_from_file\n> +|\n> +git_config_from_file_with_options\n> +|\n> +git_config_from_mem\n> +|\n> +git_config_from_blob_oid\n> +|\n> +read_early_config\n> +|\n> +read_very_early_config\n> +|\n> +config_with_options\n> +|\n> +git_config\n> +|\n> +git_protected_config\n> +|\n> +config_from_gitmodules\n> +)\n> +  (fn, ...)\n> +|\n> +repo_config(R, fn, ...)\n> +)\n> +\n> +@ extends get_fn @\n> +identifier C1, C2, D;\n> +@@\n> +int fn(const char *C1, const char *C2,\n> ++  struct key_value_info *kvi,\n> +  void *D);\n> +\n> +@ extends get_fn @\n> +@@\n> +int fn(const char *, const char *,\n> ++  struct key_value_info *,\n> +  void *);\n> +\n> +@ extends get_fn @\n> +// Don't change fns that look like callback fns but aren't\n> +identifier fn2 != tar_filter_config && != git_diff_heuristic_config &&\n> +  != git_default_submodule_config && != git_color_config &&\n> +  != bundle_list_update && != parse_object_filter_config;\n> +identifier C1, C2, D1, D2, S;\n> +attribute name UNUSED;\n> +@@\n> +int fn(const char *C1, const char *C2,\n> ++  struct key_value_info *kvi,\n> +  void *D1) {\n> +<+...\n> +(\n> +fn2(C1, C2,\n> ++ kvi,\n> +D2);\n> +|\n> +if(fn2(C1, C2,\n> ++ kvi,\n> +D2) < 0) { ... }\n> +|\n> +return fn2(C1, C2,\n> ++ kvi,\n> +D2);\n> +|\n> +S = fn2(C1, C2,\n> ++ kvi,\n> +D2);\n> +)\n> +...+>\n> +  }\n> +\n> +@ extends get_fn@\n> +identifier C1, C2, D;\n> +attribute name UNUSED;\n> +@@\n> +int fn(const char *C1, const char *C2,\n> ++  struct key_value_info *kvi UNUSED,\n> +  void *D) {...}\n> +\n> +\n> +// The previous rules don't catch all callbacks, especially if they're defined\n> +// in a separate file from the git_config() call. Fix these manually.\n> +@@\n> +identifier C1, C2, D;\n> +attribute name UNUSED;\n> +@@\n> +int\n> +(\n> +git_ident_config\n> +|\n> +urlmatch_collect_fn\n> +|\n> +write_one_config\n> +|\n> +forbid_remote_url\n> +|\n> +credential_config_callback\n> +)\n> +  (const char *C1, const char *C2,\n> ++  struct key_value_info *kvi UNUSED,\n> +  void *D) {...}\n> +\n> +@@\n> +identifier C1, C2, D, D2, S, fn2;\n> +@@\n> +int\n> +(\n> +http_options\n> +|\n> +git_status_config\n> +|\n> +git_commit_config\n> +|\n> +git_default_core_config\n> +|\n> +grep_config\n> +)\n> +  (const char *C1, const char *C2,\n> ++  struct key_value_info *kvi,\n> +  void *D) {\n> +<+...\n> +(\n> +fn2(C1, C2,\n> ++ kvi,\n> +D2);\n> +|\n> +if(fn2(C1, C2,\n> ++ kvi,\n> +D2) < 0) { ... }\n> +|\n> +return fn2(C1, C2,\n> ++ kvi,\n> +D2);\n> +|\n> +S = fn2(C1, C2,\n> ++ kvi,\n> +D2);\n> +)\n> +...+>\n> +  }\n"},{"id":"477882","messageId":"kl6l4jnr17am.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59629","inReplyTo":"6faf1b17-a1ca-0c22-2e43-aee121c4e36a@gmail.com","subject":"Re: [PATCH v2 03/14] (RFC-only) config: add kvi arg to config_fn_t","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-01T16:22:41Z","receivedAt":"2023-06-01T16:22:47Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>   - is it worth making struct key_value_info opaque and provide getters\n>     for the fields so we can change the implementation in the future\n>     without having to modify every user. We could rename it\n>     config_context or something generic like that if we think it might\n>     grow in scope in the future.\n\nYes! I planned to do the key_value_info -> config_context conversion\nwhen I send the first non-RFC version for this exact reason.\n\n>   - (probably impractical) could we stuff the key and value into struct\n>     key_value_info so config_fn_t becomes\n>     fn(const struct key_value_info, void *data)\n>     that would get rid of all the UNUSED annotations but would mean even\n>     more churn.\n\nSome of my colleagues also suggested this off-list. I think it is\nimpractical for this series because I don't think anyone could\nreasonably review with all of the added churn. At least its current\nform, the churn is mostly concentrated in the signatures, but performing\n^this change would make the bodies quite churny too.\n\nAfter this series, I think it becomes somewhat feasible with coccinelle.\nMy .cocci files were difficult to craft because we couldn't rely on the\nsignature of config_fn_t alone to tell us if the function is actually\nused as config_fn_t, but after this series, we can just use the\nsignature since config_fn_t has a struct key_value_info param.\n\n>     The advantage is that one could add functions like\n>     kvi_bool_or_int(kvi, &is_bool) and get good error messages because\n>     all the config parsing functions would all have access to location\n>     information.\n\nInteresting, I hadn't considered this possibility. This seems like a\npretty good abstraction to me, though I worry about the feasibility\nsince this is yet again more churn.\n"},{"id":"477903","messageId":"20230601221741.420194-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"f363b16025996f6cf16a683f5e2b3fe3721f4d93.1685472133.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 05/14] (RFC-only) config: finish config_fn_t refactor","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-01T22:17:41Z","receivedAt":"2023-06-01T22:17:48Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> From: Glen Choo <chooglen@google.com>\n> \n> Here's an exhaustive list of all of the changes:\n\nUp to here the changes are all mechanical, and look good.\n"},{"id":"477904","messageId":"20230601222102.420798-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"f57c1007cad106a6387c77ac4d06208d39bfb9f8.1685472133.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 06/14] config.c: pass kvi in configsets","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-01T22:21:02Z","receivedAt":"2023-06-01T22:21:09Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> From: Glen Choo <chooglen@google.com>\n> \n> Trivially pass \"struct key_value_info\" to config callbacks in\n> configset_iter(). Then, in config callbacks that are only used with\n> configsets, use the \"kvi\" arg to replace calls to current_config_*(),\n\nOK, so the end result of this patch is, indeed, that configset_iter()\npasses kvi, and this is proven by some callbacks being changed to use\nthe kvi provided, and still functioning. I did not verify that the\ncallbacks in this patch were the *only* callbacks that are only used\nwith configsets, but I don't think that is important for the purpose of\nreviewing this patch - as long as current_config_* has all been removed\nat the end of this patchset, then things are fine.\n\n> and delete current_config_line() because it has no remaining callers.\n\nAh, great that we can remove one of the global-reliant functions as a\nresult of this.\n \n"},{"id":"477906","messageId":"20230601224102.423442-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"641a56f0b40a88b0127819989ed1f81f844a090e.1685472133.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 07/14] config: provide kvi with config files","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-01T22:41:02Z","receivedAt":"2023-06-01T22:41:11Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> From: Glen Choo <chooglen@google.com>\n\nFor some reason, I was having difficulty understanding the commit\nmessage, so I looked at the code and I think I could reason out the\npurpose of this patch. It is 2-fold:\n\n - teach git_config_from_file_with_options() to supply information to\n   its callbacks through a kvi argument\n - its kvi argument requires a scope; get it not through\n   current_config_scope() but through an argument\n\nAnd as a corollary of the latter point, the scope is UNKNOWN when no\nglobal scope is set by the surrounding code (if a scope is set by the\nsurrounding code, use that instead).\n\nUnlike the previous patch, since no callers were changed, the only\nway we can see that this has happened is through reading the code of\ngit_config_from_file_with_options(), but I've read it and it looks good.\n(Again, as long as current_config_* is all gone at the end of the patch\nset, then things are fine.)\n\n> Refactor out the configset logic that caches \"struct config_source\" and\n> \"enum config_scope\" as a \"struct key_value_info\", and use it to pass the\n> \"kvi\" arg to config callbacks when parsing config files. Get the \"enum\n> config_scope\" value by plumbing an additional arg through\n> git_config_from_file_with_options() and the underlying machinery.\n> \n> We do not exercise the \"kvi\" arg yet because the remaining\n> current_config_*() callers may be used with config_with_options(), which\n> may read config from parameters, but parameters don't pass \"kvi\" yet.\n> \n> Signed-off-by: Glen Choo <chooglen@google.com>\n\nNow that I reread this, I can see that it hit all the points that I\nwrote at the top. So I think no change is needed in the commit message,\nunless another reviewer also found it unclear.\n\nThe code itself looks good.\n \n"},{"id":"477909","messageId":"20230601233550.429921-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"3760015d2c000c59d354bae872c431ee3266e9da.1685472134.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 09/14] config.c: provide kvi with CLI config","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-01T23:35:50Z","receivedAt":"2023-06-01T23:35:57Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> From: Glen Choo <chooglen@google.com>\n> \n> plumb \"kvi\" so that we can replace\n> nearly all calls to current_config_*(). (The exception is an edge case\n> where trace2/*.c calls current_config_scope().\n\nProbably clearer to say:\n\n  plumb \"kvi\" so that we can remove all calls of\n  current_config_scope() except from trace2/*.c (which will be handled in\n  a later commit), and remove all other current_config_*() (the\n  functions themselves and their calls).\n\n> Plumbing \"kvi\" reveals a few places where we've been doing the wrong\n> thing:\n> \n> * git_config_parse_parameter() hasn't been setting config source\n>   information, so plumb \"kvi\" there too.\n> \n> * \"git config --get-urlmatch --show-scope\" iterates config to collect\n>   values, but then attempts to display the scope after config iteration.\n>   Fix this by copying the \"kvi\" arg in the collection phase so that it\n>   can be read back later. This means that we can now support \"git config\n>   --get-urlmatch --show-origin\" (we don't allow this combination of args\n>   because of this bug), but that is left unchanged for now.\n> \n> * \"git config --default\" doesn't have config source metadata when\n>   displaying the default value. Fix this by treating the default value\n>   as if it came from the command line (e.g. like we do with \"git -c\" or\n>   \"git config --file\"), using kvi_from_param().\n> \n> Signed-off-by: Glen Choo <chooglen@google.com>\n\nThanks for noticing and fixing these.\n\n> +\tmemcpy(&matched->kvi, kvi, sizeof(struct key_value_info));\n\nCan this just be\n\n  matched->kvi = *kvi;\n\n?\n\nIf not, for the sizeof, use *kvi as the argument instead of struct\nkey_value_info.\n \n"},{"id":"477910","messageId":"20230601233847.430492-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"7dc0c46b864665dad87a5e0e32c6a8c36afa4673.1685472134.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 10/14] trace2: plumb config kvi","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-01T23:38:47Z","receivedAt":"2023-06-01T23:38:53Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> From: Glen Choo <chooglen@google.com>\n\nThanks - a mechanical code change that allows for quite some code to be\nremoved. Looks good.\n \n"},{"id":"477911","messageId":"20230601234829.432150-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"504eb206b5a1ac12676752d68b2e2569dc6737ef.1685472134.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 11/14] config: pass kvi to die_bad_number()","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-01T23:48:29Z","receivedAt":"2023-06-01T23:48:36Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> Outside of config.c, config callbacks now need to pass \"kvi\" to any of\n> the git_config_<type>() functions that parse a config string into a\n> number type. Included is a .cocci patch to make that refactor. In cases\n> where \"kvi\" would never be used, pass NULL, e.g.:\n> \n> - In config.c, when we are parsing a boolean instead of a number\n> - In builtin/config.c, when calling normalize_value() before setting\n>   config to something the user gave us.\n\nIn these cases, could we synthesize a kvi instead of using NULL? I\nbelieve there are already code paths that use an UNKNOWN scope - these\nseem similar to that.\n\nOtherwise looks good - a straightforward, mostly mechanical, change.\n \n"},{"id":"477912","messageId":"20230601235401.432962-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"641a56f0b40a88b0127819989ed1f81f844a090e.1685472133.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 07/14] config: provide kvi with config files","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-01T23:54:01Z","receivedAt":"2023-06-01T23:54:07Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> @@ -963,7 +965,8 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n>  \t * accurate line number in error messages.\n>  \t */\n>  \tcs->linenr--;\n> -\tret = fn(name->buf, value, NULL, data);\n> +\tkvi->linenr = cs->linenr;\n> +\tret = fn(name->buf, value, kvi, data);\n\nForgot to mention in my other email...it's a pity that we have to bump\nthe kvi->linenr like this because the original kvi generated from the\ncs is now out-to-date w.r.t. the cs in terms of line number (you can\nsee in the context how the cs linenr is also updated), so there are now\n2 sources of truth with regards to the line number. I can't think of a\nbetter way to do this, though.\n \n"},{"id":"477913","messageId":"20230602000615.434180-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"7d9b9eefc7860959dbb6ed86d8c3d165a978a0b5.1685472134.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 13/14] config: add kvi.path, use it to evaluate includes","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-02T00:06:15Z","receivedAt":"2023-06-02T00:06:22Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> From: Glen Choo <chooglen@google.com>\n> \n> Include directives are evaluated using the path of the config file. To\n> reduce the dependence on \"config_reader.source\", add a new\n> \"key_value_info.path\" member and use that instead of\n> \"config_source.path\".\n\nMaybe add:\n\n  This allows us to remove a \"struct config_reader *\" field from \"struct\n  config_include_data\", which will subsequently allow us to remove \"struct\n  config_reader\" entirely.\n   \nThe code change looks good.\n"},{"id":"477914","messageId":"20230602000841.434702-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"9e35b5b1f4dbdeafca5be7516477dc033b7643a6.1685472134.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 14/14] config: pass source to config_parser_event_fn_t","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-02T00:08:41Z","receivedAt":"2023-06-02T00:08:47Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> From: Glen Choo <chooglen@google.com>\n> \n> ..so that the callback can use a \"struct config_source\" parameter\n> instead of \"config_reader.source\". \"struct config_source\" is internal to\n> config.c, but this refactor is okay because this function has only ever\n> been (and probably ever will be) used internally by config.c.\n\nMaybe s/this refactor/adding a pointer to a struct defined in a .c into\na public function signature defined in a .h/\n\nSo this means that callers cannot instantiate that public function\nunless they are in config.c, but I see that it has been appropriately\ndocumented, so it should be fine.\n\nThanks for this series. Overall I had some minor comments, but things\nlook good overall.\n \n"},{"id":"477925","messageId":"4de362b4-dcce-3b5a-0011-73dc7dec79c3@gmail.com","threadId":"59629","inReplyTo":"kl6l4jnr17am.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v2 03/14] (RFC-only) config: add kvi arg to config_fn_t","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-06-02T09:54:25Z","receivedAt":"2023-06-02T09:54:32Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 01/06/2023 17:22, Glen Choo wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>>    - is it worth making struct key_value_info opaque and provide getters\n>>      for the fields so we can change the implementation in the future\n>>      without having to modify every user. We could rename it\n>>      config_context or something generic like that if we think it might\n>>      grow in scope in the future.\n> \n> Yes! I planned to do the key_value_info -> config_context conversion\n> when I send the first non-RFC version for this exact reason.\n\nThat's great\n\n>>    - (probably impractical) could we stuff the key and value into struct\n>>      key_value_info so config_fn_t becomes\n>>      fn(const struct key_value_info, void *data)\n>>      that would get rid of all the UNUSED annotations but would mean even\n>>      more churn.\n> \n> Some of my colleagues also suggested this off-list. I think it is\n> impractical for this series because I don't think anyone could\n> reasonably review with all of the added churn. At least its current\n> form, the churn is mostly concentrated in the signatures, but performing\n> ^this change would make the bodies quite churny too.\n\nI agree that keeping the churn to the function signatures makes it \nbearable. I wonder though if we could make the change by doing\n\n-git_default_config(const char *key, const char *value, void *data)\n+git_default_config(const struct key_value_info *kvi, void *data)\n  {\n+\tconst char *key = kvi_key(kvi);\n+\tconst char *value = kvi_value(kvi);\n+\n\nThat would add to the diffstat but I think it wouldn't really be any \nharder to review than just changing the signature as we're not modifying \nany existing lines in the function body, just adding a couple of \nvariable declarations to the start of the function. If there is an error \nin either of the variable declarations then the compiler will complain \nas \"key\" or \"value\" will end up not being declared. It would pave the \nway for gradually changing the function bodies to use \"kvi\" directly and \nremoving \"key\" and \"value\"\n\n> After this series, I think it becomes somewhat feasible with coccinelle.\n> My .cocci files were difficult to craft because we couldn't rely on the\n> signature of config_fn_t alone to tell us if the function is actually\n> used as config_fn_t, but after this series, we can just use the\n> signature since config_fn_t has a struct key_value_info param.\n\nThat's an interesting possibility, I worry though that two huge changes \nto the config callbacks might be one too many though.\n\n>>      The advantage is that one could add functions like\n>>      kvi_bool_or_int(kvi, &is_bool) and get good error messages because\n>>      all the config parsing functions would all have access to location\n>>      information.\n> \n> Interesting, I hadn't considered this possibility. This seems like a\n> pretty good abstraction to me, though I worry about the feasibility\n> since this is yet again more churn.\n\nIt could be done gradually though, converting one config callback at a \ntime once the relevant changes have been made to config.c.\n\nAs an aside, I think we'd also want a couple of helpers for matching \nkeys so we can just write kvi_match_key(kvi, \"user.name\") or \nkvi_skip_key_prefix(kvi, \"core.\", &p) rather than having to extract the \nkey name first.\n\nBest Wishes\n\nPhillip\n\n"},{"id":"477970","messageId":"kl6lv8g5zu9v.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59629","inReplyTo":"4de362b4-dcce-3b5a-0011-73dc7dec79c3@gmail.com","subject":"Re: [PATCH v2 03/14] (RFC-only) config: add kvi arg to config_fn_t","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-02T16:46:52Z","receivedAt":"2023-06-02T16:46:57Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n>> Some of my colleagues also suggested this off-list. I think it is\n>> impractical for this series because I don't think anyone could\n>> reasonably review with all of the added churn. At least its current\n>> form, the churn is mostly concentrated in the signatures, but performing\n>> ^this change would make the bodies quite churny too.\n>\n> I agree that keeping the churn to the function signatures makes it \n> bearable. I wonder though if we could make the change by doing\n>\n> -git_default_config(const char *key, const char *value, void *data)\n> +git_default_config(const struct key_value_info *kvi, void *data)\n>   {\n> +\tconst char *key = kvi_key(kvi);\n> +\tconst char *value = kvi_value(kvi);\n\nAh, yes that seems reasonable to review (and most importantly for me, it\nis also doable with coccinelle once I figure out how :P). I also agree\nthat it's better to do it all in one change than two.\n\n> As an aside, I think we'd also want a couple of helpers for matching \n> keys so we can just write kvi_match_key(kvi, \"user.name\") or \n> kvi_skip_key_prefix(kvi, \"core.\", &p) rather than having to extract the \n> key name first.\n\nYes, and that would also abstract over implementation details like\nmatching keys using strcasecmp() and not strcmp(). For reasons like\nthis, I think your proposal paves the way for a harder-to-misuse API.\n\nI still have some nagging, probably irrational fear that consolidating\nall of the config_fn_t args is trickier to manage than adding a single\nkey_value_info arg. It definitely *sounds* trickier, but I can't really\nthink of a real downside.\n\nMaybe I just have to try it and send the result for others to consider.\n"},{"id":"477971","messageId":"kl6lsfb9zsq0.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59629","inReplyTo":"20230602000841.434702-1-jonathantanmy@google.com","subject":"Re: [PATCH v2 14/14] config: pass source to config_parser_event_fn_t","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-02T17:20:23Z","receivedAt":"2023-06-02T17:20:38Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Thanks for this series. Overall I had some minor comments, but things\n> look good overall.\n\nThank you so much. I appreciate the thorough effort and all of the\nsuggestions.\n"},{"id":"477972","messageId":"kl6lpm6dzskk.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59629","inReplyTo":"20230601234829.432150-1-jonathantanmy@google.com","subject":"Re: [PATCH v2 11/14] config: pass kvi to die_bad_number()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-02T17:23:39Z","receivedAt":"2023-06-02T17:23:44Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> \"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> Outside of config.c, config callbacks now need to pass \"kvi\" to any of\n>> the git_config_<type>() functions that parse a config string into a\n>> number type. Included is a .cocci patch to make that refactor. In cases\n>> where \"kvi\" would never be used, pass NULL, e.g.:\n>> \n>> - In config.c, when we are parsing a boolean instead of a number\n>> - In builtin/config.c, when calling normalize_value() before setting\n>>   config to something the user gave us.\n>\n> In these cases, could we synthesize a kvi instead of using NULL? I\n> believe there are already code paths that use an UNKNOWN scope - these\n> seem similar to that.\n\nOkay, that sounds reasonable. This has echoes of Philip Wood's\nsuggestion (elsewhere in the thread) of combining all of the config_fn_t\nargs into a single struct, which means we can no longer use NULL as the\ndefault.\n"},{"id":"477973","messageId":"kl6lmt1hzsfp.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59629","inReplyTo":"20230601233550.429921-1-jonathantanmy@google.com","subject":"Re: [PATCH v2 09/14] config.c: provide kvi with CLI config","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-02T17:26:34Z","receivedAt":"2023-06-02T17:26:39Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n>> +\tmemcpy(&matched->kvi, kvi, sizeof(struct key_value_info));\n>\n> Can this just be\n>\n>   matched->kvi = *kvi;\n>\n> ?\n\nIf I remember correctly (big if), we have to copy the memory because the\nconfig machinery allocates kvi on the stack, and the relevant functions\nhave returned by then. Hm, does this suggest that kvi should be const?\n\n> If not, for the sizeof, use *kvi as the argument instead of struct\n> key_value_info.\n\nMakes sense.\n"},{"id":"478039","messageId":"1ca04355-8faf-e7f9-051f-a6a568ff99c5@gmail.com","threadId":"59629","inReplyTo":"kl6lv8g5zu9v.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v2 03/14] (RFC-only) config: add kvi arg to config_fn_t","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-06-05T09:38:41Z","receivedAt":"2023-06-05T09:38:51Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 02/06/2023 17:46, Glen Choo wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n>> As an aside, I think we'd also want a couple of helpers for matching\n>> keys so we can just write kvi_match_key(kvi, \"user.name\") or\n>> kvi_skip_key_prefix(kvi, \"core.\", &p) rather than having to extract the\n>> key name first.\n> \n> Yes, and that would also abstract over implementation details like\n> matching keys using strcasecmp() and not strcmp(). For reasons like\n> this, I think your proposal paves the way for a harder-to-misuse API.\n> \n> I still have some nagging, probably irrational fear that consolidating\n> all of the config_fn_t args is trickier to manage than adding a single\n> key_value_info arg. It definitely *sounds* trickier, but I can't really\n> think of a real downside.\n>\n> Maybe I just have to try it and send the result for others to consider.\n\nThat's probably the best way to see if it is an improvement - you can \nalways blame me if it turns out not to be! Hopefully it isn't too much \nwork to add enough api to be able to convert a couple of config_fn_t \nfunctions to see how it pans out.\n\nBest Wishes\n\nPhillip\n"},{"id":"478184","messageId":"kl6l3530gr6h.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59629","inReplyTo":"1ca04355-8faf-e7f9-051f-a6a568ff99c5@gmail.com","subject":"Re: [PATCH v2 03/14] (RFC-only) config: add kvi arg to config_fn_t","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-09T23:19:02Z","receivedAt":"2023-06-09T23:19:08Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 02/06/2023 17:46, Glen Choo wrote:\n>> Phillip Wood <phillip.wood123@gmail.com> writes:\n>>> As an aside, I think we'd also want a couple of helpers for matching\n>>> keys so we can just write kvi_match_key(kvi, \"user.name\") or\n>>> kvi_skip_key_prefix(kvi, \"core.\", &p) rather than having to extract the\n>>> key name first.\n>> \n>> Yes, and that would also abstract over implementation details like\n>> matching keys using strcasecmp() and not strcmp(). For reasons like\n>> this, I think your proposal paves the way for a harder-to-misuse API.\n>> \n>> I still have some nagging, probably irrational fear that consolidating\n>> all of the config_fn_t args is trickier to manage than adding a single\n>> key_value_info arg. It definitely *sounds* trickier, but I can't really\n>> think of a real downside.\n>>\n>> Maybe I just have to try it and send the result for others to consider.\n>\n> That's probably the best way to see if it is an improvement - you can \n> always blame me if it turns out not to be! Hopefully it isn't too much \n> work to add enough api to be able to convert a couple of config_fn_t \n> functions to see how it pans out.\n>\nI was preparing a reroll that incorporated some of the suggestions here,\nbut when I was done, I didn't really feel like it was better (or at the\nvery least, worth the extra churn) compared to the original approach.\n\nThe 3 versions I was comparing are:\n\n1. Adding a new \"struct config_context\" arg to config_fn_t that only\n  contains a .kvi member. This is basically the approach in v1 and v2\n  (adding an extra args) but wrapping it in a \"struct config_context\" so\n  that we can grow it later.\n\n2. Stuffing \"key\" and \"value\" into \"struct key_value_info\" and removing\n  \"key\" and \"value\" from config_fn_t.\n\n3. Creating a \"struct config_context\" that contains \"key\", \"value\" and\n  \"struct key_value_info\" as separate members, and removing\n  \"key\" and \"value\" from config_fn_t.\n\nI didn't try using opaque getters - I think that's nice to have, but\nisn't crucial to this series and introduces a lot of churn.\n\nI've uploaded an implementation of option 3. [1]. The result is quite\nugly in some places. In particular, config callbacks are used to calling\nother config callbacks with slightly different args (e.g. massaging the\n\"key\" but keeping the \"value\" the same, see patches 3,5/14 for\nexamples.), so to make this work, I ended up creating a new\n\"config_context\", copying over the relevant members, then changing the\nmembers that need to be different. I didn't pursue the refactor to its\nend state (e.g. some of the CLI config machinery in the earlier patches\nand git_config_int()/ functions that end in die_bad_number() in later\npatches, take \"key\", \"value\", \"key_value_info\" as separate args instead\nof a single \"config_context\" arg) so I haven't reaped all of the\nbenefits, but I thought the range-diff was getting churny enough that I\nwasn't interested in pursuing the idea further.\n\nI toyed with option 2 for a bit. Unsurprisingly, it requires even more\ncopying of members, though in some ways, it's better than option 3\nbecause callers only have to initialize one \"struct key_value_info\"\ninstead of \"struct key_value_info\" _and_ \"struct config_context\".\nHowever, it requires reworking the machinery quite a lot because\n\"key_value_info\" is currently used _just_ for config source information,\nand there are several cases where we need to propagate the config source\ninformation separately from the key/value (e.g. the configsets store\n\"key\" in a hashmap key, \"value\" in a string_list_item.string, and\n\"key_value_info\" in a string_list_item.util). I'm convinced that this is\nbetter interface-wise than option 3, but the required effort is high\nenough that I don't think we should do this unless we see a real need\nfor it.\n\nWith that in mind, I'm tempted to continue with option 1 for now, but\nlet me know if I'm missing something.\n\n[1] https://github.com/git/git/compare/master...chooglen:git:config/no-global-combined-kv-context?expand=1\n"},{"id":"478578","messageId":"1aeffcb13956988dfb7b925f89bbe9c8503cb143.1687290232.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"[PATCH v3 02/12] urlmatch.h: use config_fn_t type","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-20T19:43:41Z","receivedAt":"2023-06-20T19:44:03Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThese are actually used as config callbacks, so use the typedef-ed type\nand make future refactors easier.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n urlmatch.h | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/urlmatch.h b/urlmatch.h\nindex 9f40b00bfb8..bee374a642c 100644\n--- a/urlmatch.h\n+++ b/urlmatch.h\n@@ -2,6 +2,7 @@\n #define URL_MATCH_H\n \n #include \"string-list.h\"\n+#include \"config.h\"\n \n struct url_info {\n \t/* normalized url on success, must be freed, otherwise NULL */\n@@ -48,8 +49,8 @@ struct urlmatch_config {\n \tconst char *key;\n \n \tvoid *cb;\n-\tint (*collect_fn)(const char *var, const char *value, void *cb);\n-\tint (*cascade_fn)(const char *var, const char *value, void *cb);\n+\tconfig_fn_t collect_fn;\n+\tconfig_fn_t cascade_fn;\n \t/*\n \t * Compare the two matches, the one just discovered and the existing\n \t * best match and return a negative value if the found item is to be\n-- \ngitgitgadget\n\n"},{"id":"478579","messageId":"26109b65142d2a325201940262a6c0bf183ec4bd.1687290232.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"[PATCH v3 01/12] config: inline git_color_default_config","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-20T19:43:40Z","receivedAt":"2023-06-20T19:44:04Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\ngit_color_default_config() is a shorthand for calling two other config\ncallbacks. There are no other non-static functions that do this and it\nwill complicate our refactoring of config_fn_t so inline it instead.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/branch.c      | 5 ++++-\n builtin/clean.c       | 6 ++++--\n builtin/grep.c        | 5 ++++-\n builtin/show-branch.c | 5 ++++-\n builtin/tag.c         | 6 +++++-\n color.c               | 8 --------\n color.h               | 6 +-----\n 7 files changed, 22 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex e6c2655af68..df99e38847b 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -117,7 +117,10 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static const char *branch_get_color(enum color_branch ix)\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 78852d28cec..57e7f7cac64 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -130,8 +130,10 @@ static int git_clean_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\t/* inspect the color.ui config variable and others */\n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static const char *clean_get_color(enum color_clean ix)\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex b86c754defb..76cf999d310 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -293,7 +293,10 @@ static int wait_all(void)\n static int grep_cmd_config(const char *var, const char *value, void *cb)\n {\n \tint st = grep_config(var, value, cb);\n-\tif (git_color_default_config(var, value, NULL) < 0)\n+\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\tst = -1;\n+\telse if (git_default_config(var, value, cb) < 0)\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex 7ef4a642c17..a2461270d4b 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -579,7 +579,10 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static int omit_in_dense(struct commit *commit, struct commit **rev, int n)\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 49b64c7a288..1acf5f7a59f 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -209,7 +209,11 @@ static int git_tag_config(const char *var, const char *value, void *cb)\n \n \tif (starts_with(var, \"column.\"))\n \t\treturn git_column_config(var, value, \"tag\", &colopts);\n-\treturn git_color_default_config(var, value, cb);\n+\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static void write_tag_body(int fd, const struct object_id *oid)\ndiff --git a/color.c b/color.c\nindex 83abb11eda0..b24b19566b9 100644\n--- a/color.c\n+++ b/color.c\n@@ -430,14 +430,6 @@ int git_color_config(const char *var, const char *value, void *cb UNUSED)\n \treturn 0;\n }\n \n-int git_color_default_config(const char *var, const char *value, void *cb)\n-{\n-\tif (git_color_config(var, value, cb) < 0)\n-\t\treturn -1;\n-\n-\treturn git_default_config(var, value, cb);\n-}\n-\n void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb)\n {\n \tif (*color)\ndiff --git a/color.h b/color.h\nindex cfc8f841b23..bb28343be21 100644\n--- a/color.h\n+++ b/color.h\n@@ -88,12 +88,8 @@ extern const int column_colors_ansi_max;\n  */\n extern int color_stdout_is_tty;\n \n-/*\n- * Use the first one if you need only color config; the second is a convenience\n- * if you are just going to change to git_default_config, too.\n- */\n+/* Parse color config. */\n int git_color_config(const char *var, const char *value, void *cb);\n-int git_color_default_config(const char *var, const char *value, void *cb);\n \n /*\n  * Parse a config option, which can be a boolean or one of\n-- \ngitgitgadget\n\n"},{"id":"478580","messageId":"c5051ddc10dfa9665fbcbed30875369c4e972d17.1687290233.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"[PATCH v3 04/12] config.c: pass ctx in configsets","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-20T19:43:43Z","receivedAt":"2023-06-20T19:44:06Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPass config_context to config callbacks in configset_iter(), trivially\nsetting the .kvi member to the cached key_value_info. Then, in config\ncallbacks that are only used with configsets, use the .kvi member to\nreplace calls to current_config_*(), and delete current_config_line()\nbecause it has no remaining callers.\n\nThis leaves builtin/config.c and config.c as the only remaining users of\ncurrent_config_*().\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/remote.c       | 10 ++++++----\n config.c               | 35 ++++++++++++++++-------------------\n config.h               |  2 +-\n remote.c               |  7 ++++---\n t/helper/test-config.c | 11 ++++++-----\n 5 files changed, 33 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 87de81105e2..d47f9ee21cf 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -646,17 +646,19 @@ struct push_default_info\n };\n \n static int config_read_push_default(const char *key, const char *value,\n-\tconst struct config_context *ctx UNUSED, void *cb)\n+\tconst struct config_context *ctx, void *cb)\n {\n+\tconst struct key_value_info *kvi = ctx->kvi;\n+\n \tstruct push_default_info* info = cb;\n \tif (strcmp(key, \"remote.pushdefault\") ||\n \t    !value || strcmp(value, info->old_name))\n \t\treturn 0;\n \n-\tinfo->scope = current_config_scope();\n+\tinfo->scope = kvi->scope;\n \tstrbuf_reset(&info->origin);\n-\tstrbuf_addstr(&info->origin, current_config_name());\n-\tinfo->linenr = current_config_line();\n+\tstrbuf_addstr(&info->origin, config_origin_type_name(kvi->origin_type));\n+\tinfo->linenr = kvi->linenr;\n \n \treturn 0;\n }\ndiff --git a/config.c b/config.c\nindex 670d92cd76b..1e146c861b1 100644\n--- a/config.c\n+++ b/config.c\n@@ -2309,6 +2309,7 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \tstruct string_list *values;\n \tstruct config_set_element *entry;\n \tstruct configset_list *list = &set->list;\n+\tstruct config_context ctx = CONFIG_CONTEXT_INIT;\n \n \tfor (i = 0; i < list->nr; i++) {\n \t\tentry = list->items[i].e;\n@@ -2316,12 +2317,11 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \t\tvalues = &entry->value_list;\n \n \t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n-\n-\t\tif (fn(entry->key, values->items[value_index].string, NULL, data) < 0)\n+\t\tctx.kvi = values->items[value_index].util;\n+\t\tif (fn(entry->key, values->items[value_index].string, &ctx, data) < 0)\n \t\t\tgit_die_config_linenr(entry->key,\n-\t\t\t\t\t      reader->config_kvi->filename,\n-\t\t\t\t\t      reader->config_kvi->linenr);\n-\n+\t\t\t\t\t      ctx.kvi->filename,\n+\t\t\t\t\t      ctx.kvi->linenr);\n \t\tconfig_reader_set_kvi(reader, NULL);\n \t}\n }\n@@ -3974,13 +3974,8 @@ static int reader_origin_type(struct config_reader *reader,\n \treturn 0;\n }\n \n-const char *current_config_origin_type(void)\n+const char *config_origin_type_name(enum config_origin_type type)\n {\n-\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n-\n-\tif (reader_origin_type(&the_reader, &type))\n-\t\tBUG(\"current_config_origin_type called outside config callback\");\n-\n \tswitch (type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\treturn \"blob\";\n@@ -3997,6 +3992,16 @@ const char *current_config_origin_type(void)\n \t}\n }\n \n+const char *current_config_origin_type(void)\n+{\n+\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n+\n+\tif (reader_origin_type(&the_reader, &type))\n+\t\tBUG(\"current_config_origin_type called outside config callback\");\n+\n+\treturn config_origin_type_name(type);\n+}\n+\n const char *config_scope_name(enum config_scope scope)\n {\n \tswitch (scope) {\n@@ -4044,14 +4049,6 @@ enum config_scope current_config_scope(void)\n \t\treturn the_reader.parsing_scope;\n }\n \n-int current_config_line(void)\n-{\n-\tif (the_reader.config_kvi)\n-\t\treturn the_reader.config_kvi->linenr;\n-\telse\n-\t\treturn the_reader.source->linenr;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex bc52ceb72f2..b4236e120a8 100644\n--- a/config.h\n+++ b/config.h\n@@ -387,7 +387,7 @@ int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n enum config_scope current_config_scope(void);\n const char *current_config_origin_type(void);\n const char *current_config_name(void);\n-int current_config_line(void);\n+const char *config_origin_type_name(enum config_origin_type type);\n \n /*\n  * Match and parse a config key of the form:\ndiff --git a/remote.c b/remote.c\nindex dafb1acdc38..37d413e7892 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -350,7 +350,7 @@ static void read_branches_file(struct remote_state *remote_state,\n }\n \n static int handle_config(const char *key, const char *value,\n-\t\t\t const struct config_context *ctx UNUSED, void *cb)\n+\t\t\t const struct config_context *ctx, void *cb)\n {\n \tconst char *name;\n \tsize_t namelen;\n@@ -358,6 +358,7 @@ static int handle_config(const char *key, const char *value,\n \tstruct remote *remote;\n \tstruct branch *branch;\n \tstruct remote_state *remote_state = cb;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \n \tif (parse_config_key(key, \"branch\", &name, &namelen, &subkey) >= 0) {\n \t\t/* There is no subsection. */\n@@ -415,8 +416,8 @@ static int handle_config(const char *key, const char *value,\n \t}\n \tremote = make_remote(remote_state, name, namelen);\n \tremote->origin = REMOTE_CONFIG;\n-\tif (current_config_scope() == CONFIG_SCOPE_LOCAL ||\n-\t    current_config_scope() == CONFIG_SCOPE_WORKTREE)\n+\tif (kvi->scope == CONFIG_SCOPE_LOCAL ||\n+\t    kvi->scope == CONFIG_SCOPE_WORKTREE)\n \t\tremote->configured_in_repo = 1;\n \tif (!strcmp(subkey, \"mirror\"))\n \t\tremote->mirror = git_config_bool(key, value);\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 85ad815358e..3f4c3678318 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -43,9 +43,10 @@\n  */\n \n static int iterate_cb(const char *var, const char *value,\n-\t\t      const struct config_context *ctx UNUSED,\n+\t\t      const struct config_context *ctx,\n \t\t      void *data UNUSED)\n {\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \tstatic int nr;\n \n \tif (nr++)\n@@ -53,10 +54,10 @@ static int iterate_cb(const char *var, const char *value,\n \n \tprintf(\"key=%s\\n\", var);\n \tprintf(\"value=%s\\n\", value ? value : \"(null)\");\n-\tprintf(\"origin=%s\\n\", current_config_origin_type());\n-\tprintf(\"name=%s\\n\", current_config_name());\n-\tprintf(\"lno=%d\\n\", current_config_line());\n-\tprintf(\"scope=%s\\n\", config_scope_name(current_config_scope()));\n+\tprintf(\"origin=%s\\n\", config_origin_type_name(kvi->origin_type));\n+\tprintf(\"name=%s\\n\", kvi->filename ? kvi->filename : \"\");\n+\tprintf(\"lno=%d\\n\", kvi->linenr);\n+\tprintf(\"scope=%s\\n\", config_scope_name(kvi->scope));\n \n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"478581","messageId":"595e7d2e163dc0333300bb20f82cc42177642a9e.1687290233.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"[PATCH v3 05/12] config: pass ctx with config files","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-20T19:43:44Z","receivedAt":"2023-06-20T19:44:11Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPass config_context to config_callbacks when parsing config files. To\nprovide the .kvi member, refactor out the configset logic that caches\n\"struct config_source\" and \"enum config_scope\" as a \"struct\nkey_value_info\". Make the \"enum config_scope\" available to the config\nfile machinery by plumbing an additional arg through\ngit_config_from_file_with_options().\n\nWe do not exercise ctx yet because the remaining current_config_*()\ncallers may be used with config_with_options(), which may read config\nfrom parameters, but parameters don't pass ctx yet.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n bundle-uri.c       |   1 +\n config.c           | 105 ++++++++++++++++++++++++++++++---------------\n config.h           |   8 ++--\n fsck.c             |   3 +-\n submodule-config.c |   5 ++-\n 5 files changed, 81 insertions(+), 41 deletions(-)\n\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex 0d5acc3dc51..64f32387745 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -255,6 +255,7 @@ int bundle_uri_parse_config_format(const char *uri,\n \t}\n \tresult = git_config_from_file_with_options(config_to_bundle_list,\n \t\t\t\t\t\t   filename, list,\n+\t\t\t\t\t\t   CONFIG_SCOPE_UNKNOWN,\n \t\t\t\t\t\t   &opts);\n \n \tif (!result && list->mode == BUNDLE_MODE_NONE) {\ndiff --git a/config.c b/config.c\nindex 1e146c861b1..79a6e7cce7e 100644\n--- a/config.c\n+++ b/config.c\n@@ -258,7 +258,9 @@ static int handle_path_include(struct config_source *cs, const char *path,\n \t\t\t    !cs ? \"<unknown>\" :\n \t\t\t    cs->name ? cs->name :\n \t\t\t    \"the command line\");\n-\t\tret = git_config_from_file(git_config_include, path, inc);\n+\t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n+\t\t\t\t\t\t\tcurrent_config_scope(),\n+\t\t\t\t\t\t\tNULL);\n \t\tinc->depth--;\n \t}\n cleanup:\n@@ -501,7 +503,7 @@ static int git_config_include(const char *var, const char *value,\n \t * Pass along all values, including \"include\" directives; this makes it\n \t * possible to query information on the includes themselves.\n \t */\n-\tret = inc->fn(var, value, NULL, inc->data);\n+\tret = inc->fn(var, value, ctx, inc->data);\n \tif (ret < 0)\n \t\treturn ret;\n \n@@ -937,12 +939,15 @@ static char *parse_value(struct config_source *cs)\n \t}\n }\n \n-static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n-\t\t     struct strbuf *name)\n+static int get_value(struct config_source *cs, struct key_value_info *kvi,\n+\t\t     config_fn_t fn, void *data, struct strbuf *name)\n {\n \tint c;\n \tchar *value;\n \tint ret;\n+\tstruct config_context ctx = {\n+\t\t.kvi = kvi,\n+\t};\n \n \t/* Get the full name */\n \tfor (;;) {\n@@ -971,7 +976,8 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \t * accurate line number in error messages.\n \t */\n \tcs->linenr--;\n-\tret = fn(name->buf, value, NULL, data);\n+\tkvi->linenr = cs->linenr;\n+\tret = fn(name->buf, value, &ctx, data);\n \tif (ret >= 0)\n \t\tcs->linenr++;\n \treturn ret;\n@@ -1070,8 +1076,19 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \treturn 0;\n }\n \n+static void kvi_from_source(struct config_source *cs,\n+\t\t\t    enum config_scope scope,\n+\t\t\t    struct key_value_info *out)\n+{\n+\tout->filename = strintern(cs->name);\n+\tout->origin_type = cs->origin_type;\n+\tout->linenr = cs->linenr;\n+\tout->scope = scope;\n+}\n+\n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n-\t\t\t    void *data, const struct config_options *opts)\n+\t\t\t    struct key_value_info *kvi, void *data,\n+\t\t\t    const struct config_options *opts)\n {\n \tint comment = 0;\n \tsize_t baselen = 0;\n@@ -1155,7 +1172,7 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t */\n \t\tstrbuf_setlen(var, baselen);\n \t\tstrbuf_addch(var, tolower(c));\n-\t\tif (get_value(cs, fn, data, var) < 0)\n+\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n \t\t\tbreak;\n \t}\n \n@@ -2013,9 +2030,11 @@ int git_default_config(const char *var, const char *value,\n  * this function.\n  */\n static int do_config_from(struct config_reader *reader,\n-\t\t\t  struct config_source *top, config_fn_t fn, void *data,\n+\t\t\t  struct config_source *top, config_fn_t fn,\n+\t\t\t  void *data, enum config_scope scope,\n \t\t\t  const struct config_options *opts)\n {\n+\tstruct key_value_info kvi = KVI_INIT;\n \tint ret;\n \n \t/* push config-file parsing state stack */\n@@ -2025,8 +2044,9 @@ static int do_config_from(struct config_reader *reader,\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n \tconfig_reader_push_source(reader, top);\n+\tkvi_from_source(top, scope, &kvi);\n \n-\tret = git_parse_source(top, fn, data, opts);\n+\tret = git_parse_source(top, fn, &kvi, data, opts);\n \n \t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n@@ -2040,7 +2060,8 @@ static int do_config_from_file(struct config_reader *reader,\n \t\t\t       config_fn_t fn,\n \t\t\t       const enum config_origin_type origin_type,\n \t\t\t       const char *name, const char *path, FILE *f,\n-\t\t\t       void *data, const struct config_options *opts)\n+\t\t\t       void *data, enum config_scope scope,\n+\t\t\t       const struct config_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n@@ -2055,19 +2076,20 @@ static int do_config_from_file(struct config_reader *reader,\n \ttop.do_ftell = config_file_ftell;\n \n \tflockfile(f);\n-\tret = do_config_from(reader, &top, fn, data, opts);\n+\tret = do_config_from(reader, &top, fn, data, scope, opts);\n \tfunlockfile(f);\n \treturn ret;\n }\n \n-static int git_config_from_stdin(config_fn_t fn, void *data)\n+static int git_config_from_stdin(config_fn_t fn, void *data,\n+\t\t\t\t enum config_scope scope)\n {\n \treturn do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, \"\",\n-\t\t\t\t   NULL, stdin, data, NULL);\n+\t\t\t\t   NULL, stdin, data, scope, NULL);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n-\t\t\t\t      void *data,\n+\t\t\t\t      void *data, enum config_scope scope,\n \t\t\t\t      const struct config_options *opts)\n {\n \tint ret = -1;\n@@ -2078,7 +2100,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \tf = fopen_or_warn(filename, \"r\");\n \tif (f) {\n \t\tret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,\n-\t\t\t\t\t  filename, filename, f, data, opts);\n+\t\t\t\t\t  filename, filename, f, data, scope,\n+\t\t\t\t\t  opts);\n \t\tfclose(f);\n \t}\n \treturn ret;\n@@ -2086,13 +2109,15 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \n int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n {\n-\treturn git_config_from_file_with_options(fn, filename, data, NULL);\n+\treturn git_config_from_file_with_options(fn, filename, data,\n+\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, NULL);\n }\n \n int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type origin_type,\n \t\t\tconst char *name, const char *buf, size_t len,\n-\t\t\tvoid *data, const struct config_options *opts)\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \n@@ -2107,14 +2132,15 @@ int git_config_from_mem(config_fn_t fn,\n \ttop.do_ungetc = config_buf_ungetc;\n \ttop.do_ftell = config_buf_ftell;\n \n-\treturn do_config_from(&the_reader, &top, fn, data, opts);\n+\treturn do_config_from(&the_reader, &top, fn, data, scope, opts);\n }\n \n int git_config_from_blob_oid(config_fn_t fn,\n \t\t\t      const char *name,\n \t\t\t      struct repository *repo,\n \t\t\t      const struct object_id *oid,\n-\t\t\t      void *data)\n+\t\t\t      void *data,\n+\t\t\t      enum config_scope scope)\n {\n \tenum object_type type;\n \tchar *buf;\n@@ -2130,7 +2156,7 @@ int git_config_from_blob_oid(config_fn_t fn,\n \t}\n \n \tret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size,\n-\t\t\t\t  data, NULL);\n+\t\t\t\t  data, scope, NULL);\n \tfree(buf);\n \n \treturn ret;\n@@ -2139,13 +2165,14 @@ int git_config_from_blob_oid(config_fn_t fn,\n static int git_config_from_blob_ref(config_fn_t fn,\n \t\t\t\t    struct repository *repo,\n \t\t\t\t    const char *name,\n-\t\t\t\t    void *data)\n+\t\t\t\t    void *data,\n+\t\t\t\t    enum config_scope scope)\n {\n \tstruct object_id oid;\n \n \tif (repo_get_oid(repo, name, &oid) < 0)\n \t\treturn error(_(\"unable to resolve config blob '%s'\"), name);\n-\treturn git_config_from_blob_oid(fn, name, repo, &oid, data);\n+\treturn git_config_from_blob_oid(fn, name, repo, &oid, data, scope);\n }\n \n char *git_system_config(void)\n@@ -2220,27 +2247,34 @@ static int do_git_config_sequence(struct config_reader *reader,\n \tif (git_config_system() && system_config &&\n \t    !access_or_die(system_config, R_OK,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n-\t\tret += git_config_from_file(fn, system_config, data);\n+\t\tret += git_config_from_file_with_options(fn, system_config,\n+\t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n+\t\t\t\t\t\t\t NULL);\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);\n \tgit_global_config(&user_config, &xdg_config);\n \n \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file(fn, xdg_config, data);\n+\t\tret += git_config_from_file_with_options(fn, xdg_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n \tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file(fn, user_config, data);\n+\t\tret += git_config_from_file_with_options(fn, user_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);\n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n-\t\tret += git_config_from_file(fn, repo_config, data);\n+\t\tret += git_config_from_file_with_options(fn, repo_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n \tif (!opts->ignore_worktree && repository_format_worktree_config) {\n \t\tchar *path = git_pathdup(\"config.worktree\");\n \t\tif (!access_or_die(path, R_OK, 0))\n-\t\t\tret += git_config_from_file(fn, path, data);\n+\t\t\tret += git_config_from_file_with_options(fn, path, data,\n+\t\t\t\t\t\t\t\t CONFIG_SCOPE_WORKTREE,\n+\t\t\t\t\t\t\t\t NULL);\n \t\tfree(path);\n \t}\n \n@@ -2282,14 +2316,16 @@ int config_with_options(config_fn_t fn, void *data,\n \t * regular lookup sequence.\n \t */\n \tif (config_source && config_source->use_stdin) {\n-\t\tret = git_config_from_stdin(fn, data);\n+\t\tret = git_config_from_stdin(fn, data, config_source->scope);\n \t} else if (config_source && config_source->file) {\n-\t\tret = git_config_from_file(fn, config_source->file, data);\n+\t\tret = git_config_from_file_with_options(fn, config_source->file,\n+\t\t\t\t\t\t\tdata, config_source->scope,\n+\t\t\t\t\t\t\tNULL);\n \t} else if (config_source && config_source->blob) {\n \t\tstruct repository *repo = config_source->repo ?\n \t\t\tconfig_source->repo : the_repository;\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n-\t\t\t\t\t\tdata);\n+\t\t\t\t\t       data, config_source->scope);\n \t} else {\n \t\tret = do_git_config_sequence(&the_reader, opts, fn, data);\n \t}\n@@ -2432,16 +2468,14 @@ static int configset_add_value(struct config_reader *reader,\n \tif (!reader->source)\n \t\tBUG(\"configset_add_value has no source\");\n \tif (reader->source->name) {\n-\t\tkv_info->filename = strintern(reader->source->name);\n-\t\tkv_info->linenr = reader->source->linenr;\n-\t\tkv_info->origin_type = reader->source->origin_type;\n+\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n \t} else {\n \t\t/* for values read from `git_config_from_parameters()` */\n \t\tkv_info->filename = NULL;\n \t\tkv_info->linenr = -1;\n \t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n+\t\tkv_info->scope = reader->parsing_scope;\n \t}\n-\tkv_info->scope = reader->parsing_scope;\n \tsi->util = kv_info;\n \n \treturn 0;\n@@ -3482,7 +3516,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \t\t */\n \t\tif (git_config_from_file_with_options(store_aux,\n \t\t\t\t\t\t      config_filename,\n-\t\t\t\t\t\t      &store, &opts)) {\n+\t\t\t\t\t\t      &store, CONFIG_SCOPE_UNKNOWN,\n+\t\t\t\t\t\t      &opts)) {\n \t\t\terror(_(\"invalid config file %s\"), config_filename);\n \t\t\tret = CONFIG_INVALID_FILE;\n \t\t\tgoto out_free;\ndiff --git a/config.h b/config.h\nindex b4236e120a8..1fc746c3a46 100644\n--- a/config.h\n+++ b/config.h\n@@ -170,16 +170,18 @@ int git_default_config(const char *, const char *,\n int git_config_from_file(config_fn_t fn, const char *, void *);\n \n int git_config_from_file_with_options(config_fn_t fn, const char *,\n-\t\t\t\t      void *,\n+\t\t\t\t      void *, enum config_scope,\n \t\t\t\t      const struct config_options *);\n int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type,\n \t\t\tconst char *name,\n \t\t\tconst char *buf, size_t len,\n-\t\t\tvoid *data, const struct config_options *opts);\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_options *opts);\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     struct repository *repo,\n-\t\t\t     const struct object_id *oid, void *data);\n+\t\t\t     const struct object_id *oid, void *data,\n+\t\t\t     enum config_scope scope);\n void git_config_push_parameter(const char *text);\n void git_config_push_env(const char *spec);\n int git_config_from_parameters(config_fn_t fn, void *data);\ndiff --git a/fsck.c b/fsck.c\nindex 55b6a694853..f92c216fb5c 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1238,7 +1238,8 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\tdata.ret = 0;\n \t\tconfig_opts.error_action = CONFIG_ERROR_SILENT;\n \t\tif (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB,\n-\t\t\t\t\t\".gitmodules\", buf, size, &data, &config_opts))\n+\t\t\t\t\t\".gitmodules\", buf, size, &data,\n+\t\t\t\t\tCONFIG_SCOPE_UNKNOWN, &config_opts))\n \t\t\tdata.ret |= report(options, oid, OBJ_BLOB,\n \t\t\t\t\tFSCK_MSG_GITMODULES_PARSE,\n \t\t\t\t\t\"could not parse gitmodules blob\");\ndiff --git a/submodule-config.c b/submodule-config.c\nindex b364244102e..e19ab593aa4 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -606,7 +606,7 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \tparameter.gitmodules_oid = &oid;\n \tparameter.overwrite = 0;\n \tgit_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf,\n-\t\t\tconfig, config_size, &parameter, NULL);\n+\t\t\t    config, config_size, &parameter, CONFIG_SCOPE_UNKNOWN, NULL);\n \tstrbuf_release(&rev);\n \tfree(config);\n \n@@ -715,7 +715,8 @@ void gitmodules_config_oid(const struct object_id *commit_oid)\n \n \tif (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) {\n \t\tgit_config_from_blob_oid(gitmodules_cb, rev.buf,\n-\t\t\t\t\t the_repository, &oid, the_repository);\n+\t\t\t\t\t the_repository, &oid, the_repository,\n+\t\t\t\t\t CONFIG_SCOPE_UNKNOWN);\n \t}\n \tstrbuf_release(&rev);\n \n-- \ngitgitgadget\n\n"},{"id":"478582","messageId":"a2a891a069f46d3d77cafe61f64402c93cffaae4.1687290233.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"[PATCH v3 06/12] builtin/config.c: test misuse of format_config()","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-20T19:43:45Z","receivedAt":"2023-06-20T19:44:15Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\ncurrent_config_*() functions aren't meant to be called outside of\nconfig callbacks because they read state that is only set when iterating\nthrough config. However, several sites in builtin/config.c are\nindirectly calling these functions outside of config callbacks thanks to\nthe format_config() helper. Show the current, bad behavior via tests\nso that the fixes in a subsequent commit will be clearer.\n\nThe misbehaving cases are:\n\n* \"git config --get-urlmatch --show-scope\" results in an \"unknown\"\n   scope, where it arguably should show the config file's scope. It's\n   clear that this wasn't intended, though: we knew that\n   \"--get-urlmatch\" couldn't show config source metadata, which is why\n   \"--show-origin\" was marked incompatible with \"--get-urlmatch\" when\n   it was introduced [1]. It was most likely a mistake that we allowed\n   \"--show-scope\" to sneak through.\n\n* Similarly, \"git config --default\" doesn't set config source metadata ,\n  so \"--show-scope\" also results in \"unknown\", and \"--show-origin\"\n  results in a BUG().\n\n[1] https://lore.kernel.org/git/20160205112001.GA13397@sigill.intra.peff.net/\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n t/t1300-config.sh | 25 +++++++++++++++++++++++++\n 1 file changed, 25 insertions(+)\n\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 86bfbc2b364..fa6a8df2521 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1668,6 +1668,21 @@ test_expect_success 'urlmatch' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'urlmatch with --show-scope' '\n+\tcat >.git/config <<-\\EOF &&\n+\t[http \"https://weak.example.com\"]\n+\t\tsslVerify = false\n+\t\tcookieFile = /tmp/cookie.txt\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tunknown\thttp.cookiefile /tmp/cookie.txt\n+\tunknown\thttp.sslverify false\n+\tEOF\n+\tgit config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'urlmatch favors more specific URLs' '\n \tcat >.git/config <<-\\EOF &&\n \t[http \"https://example.com/\"]\n@@ -2055,6 +2070,10 @@ test_expect_success '--show-origin blob ref' '\n \ttest_cmp expect output\n '\n \n+test_expect_success '--show-origin with --default' '\n+\ttest_must_fail git config --show-origin --default foo some.key\n+'\n+\n test_expect_success '--show-scope with --list' '\n \tcat >expect <<-EOF &&\n \tglobal\tuser.global=true\n@@ -2123,6 +2142,12 @@ test_expect_success '--show-scope with --show-origin' '\n \ttest_cmp expect output\n '\n \n+test_expect_success '--show-scope with --default' '\n+\tgit config --show-scope --default foo some.key >actual &&\n+\techo \"unknown\tfoo\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'override global and system config' '\n \ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n \tcat >\"$HOME\"/.gitconfig <<-EOF &&\n-- \ngitgitgadget\n\n"},{"id":"478583","messageId":"1fb1708bbd967ed841fcb3d96ea0d1f6b880c60e.1687290233.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"[PATCH v3 07/12] config.c: pass ctx with CLI config","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-20T19:43:46Z","receivedAt":"2023-06-20T19:44:17Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPass config_context when parsing CLI config. To provide the .kvi member,\nrefactor out kvi_from_param() from the logic that caches CLI config in\nconfigsets. Now that config_context and config_context.kvi is always\npresent when config machinery calls config callbacks, plumb \"kvi\" so\nthat we can remove all calls of current_config_scope() except for\ntrace2/*.c (which will be handled in a later commit), and remove all\nother current_config_*() (the functions themselves and their calls).\nNote that this results in .kvi containing a different, more complete\nset of information than the mocked up \"struct config_source\" in\ngit_config_from_parameters().\n\nPlumbing \"kvi\" reveals a few places where we've been doing the wrong\nthing:\n\n* git_config_parse_parameter() hasn't been setting config source\n  information, so plumb \"kvi\" there too.\n\n* \"git config --get-urlmatch --show-scope\" iterates config to collect\n  values, but then attempts to display the scope after config iteration.\n  Fix this by copying the \"kvi\" value in the collection phase so that it\n  can be read back later. This means that we can now support \"git config\n  --get-urlmatch --show-origin\" (we don't allow this combination of args\n  because of this bug), but that is left unchanged for now.\n\n* \"git config --default\" doesn't have config source metadata when\n  displaying the default value. Fix this by treating the default value\n  as if it came from the command line (e.g. like we do with \"git -c\" or\n  \"git config --file\"), using kvi_from_param().\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/config.c  | 47 +++++++++++++++++----------\n config.c          | 82 +++++++++++++++++++++++------------------------\n config.h          |  3 +-\n t/t1300-config.sh | 10 +++---\n 4 files changed, 78 insertions(+), 64 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex e14f967bbea..0c01ded5f05 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -193,38 +193,42 @@ static void check_argc(int argc, int min, int max)\n \tusage_builtin_config();\n }\n \n-static void show_config_origin(struct strbuf *buf)\n+static void show_config_origin(const struct key_value_info *kvi,\n+\t\t\t       struct strbuf *buf)\n {\n \tconst char term = end_nul ? '\\0' : '\\t';\n \n-\tstrbuf_addstr(buf, current_config_origin_type());\n+\tstrbuf_addstr(buf, config_origin_type_name(kvi->origin_type));\n \tstrbuf_addch(buf, ':');\n \tif (end_nul)\n-\t\tstrbuf_addstr(buf, current_config_name());\n+\t\tstrbuf_addstr(buf, kvi->filename ? kvi->filename : \"\");\n \telse\n-\t\tquote_c_style(current_config_name(), buf, NULL, 0);\n+\t\tquote_c_style(kvi->filename ? kvi->filename : \"\", buf, NULL, 0);\n \tstrbuf_addch(buf, term);\n }\n \n-static void show_config_scope(struct strbuf *buf)\n+static void show_config_scope(const struct key_value_info *kvi,\n+\t\t\t      struct strbuf *buf)\n {\n \tconst char term = end_nul ? '\\0' : '\\t';\n-\tconst char *scope = config_scope_name(current_config_scope());\n+\tconst char *scope = config_scope_name(kvi->scope);\n \n \tstrbuf_addstr(buf, N_(scope));\n \tstrbuf_addch(buf, term);\n }\n \n static int show_all_config(const char *key_, const char *value_,\n-\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t   const struct config_context *ctx,\n \t\t\t   void *cb UNUSED)\n {\n+\tconst struct key_value_info *kvi = ctx->kvi;\n+\n \tif (show_origin || show_scope) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \t\tif (show_scope)\n-\t\t\tshow_config_scope(&buf);\n+\t\t\tshow_config_scope(kvi, &buf);\n \t\tif (show_origin)\n-\t\t\tshow_config_origin(&buf);\n+\t\t\tshow_config_origin(kvi, &buf);\n \t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n \t\tfwrite(buf.buf, 1, buf.len, stdout);\n \t\tstrbuf_release(&buf);\n@@ -242,12 +246,13 @@ struct strbuf_list {\n \tint alloc;\n };\n \n-static int format_config(struct strbuf *buf, const char *key_, const char *value_)\n+static int format_config(struct strbuf *buf, const char *key_,\n+\t\t\t const char *value_, const struct key_value_info *kvi)\n {\n \tif (show_scope)\n-\t\tshow_config_scope(buf);\n+\t\tshow_config_scope(kvi, buf);\n \tif (show_origin)\n-\t\tshow_config_origin(buf);\n+\t\tshow_config_origin(kvi, buf);\n \tif (show_keys)\n \t\tstrbuf_addstr(buf, key_);\n \tif (!omit_values) {\n@@ -302,9 +307,10 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value\n }\n \n static int collect_config(const char *key_, const char *value_,\n-\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tstruct strbuf_list *values = cb;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \n \tif (!use_key_regexp && strcmp(key_, key))\n \t\treturn 0;\n@@ -319,7 +325,7 @@ static int collect_config(const char *key_, const char *value_,\n \tALLOC_GROW(values->items, values->nr + 1, values->alloc);\n \tstrbuf_init(&values->items[values->nr], 0);\n \n-\treturn format_config(&values->items[values->nr++], key_, value_);\n+\treturn format_config(&values->items[values->nr++], key_, value_, kvi);\n }\n \n static int get_value(const char *key_, const char *regex_, unsigned flags)\n@@ -380,11 +386,14 @@ static int get_value(const char *key_, const char *regex_, unsigned flags)\n \t\t\t    &given_config_source, &config_options);\n \n \tif (!values.nr && default_value) {\n+\t\tstruct key_value_info kvi = KVI_INIT;\n \t\tstruct strbuf *item;\n+\n+\t\tkvi_from_param(&kvi);\n \t\tALLOC_GROW(values.items, values.nr + 1, values.alloc);\n \t\titem = &values.items[values.nr++];\n \t\tstrbuf_init(item, 0);\n-\t\tif (format_config(item, key_, default_value) < 0)\n+\t\tif (format_config(item, key_, default_value, &kvi) < 0)\n \t\t\tdie(_(\"failed to format default config value: %s\"),\n \t\t\t\tdefault_value);\n \t}\n@@ -559,15 +568,17 @@ static void check_write(void)\n struct urlmatch_current_candidate_value {\n \tchar value_is_null;\n \tstruct strbuf value;\n+\tstruct key_value_info kvi;\n };\n \n static int urlmatch_collect_fn(const char *var, const char *value,\n-\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       const struct config_context *ctx,\n \t\t\t       void *cb)\n {\n \tstruct string_list *values = cb;\n \tstruct string_list_item *item = string_list_insert(values, var);\n \tstruct urlmatch_current_candidate_value *matched = item->util;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \n \tif (!matched) {\n \t\tmatched = xmalloc(sizeof(*matched));\n@@ -576,6 +587,7 @@ static int urlmatch_collect_fn(const char *var, const char *value,\n \t} else {\n \t\tstrbuf_reset(&matched->value);\n \t}\n+\tmatched->kvi = *kvi;\n \n \tif (value) {\n \t\tstrbuf_addstr(&matched->value, value);\n@@ -622,7 +634,8 @@ static int get_urlmatch(const char *var, const char *url)\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n \t\tformat_config(&buf, item->string,\n-\t\t\t      matched->value_is_null ? NULL : matched->value.buf);\n+\t\t\t      matched->value_is_null ? NULL : matched->value.buf,\n+\t\t\t      &matched->kvi);\n \t\tfwrite(buf.buf, 1, buf.len, stdout);\n \t\tstrbuf_release(&buf);\n \ndiff --git a/config.c b/config.c\nindex 79a6e7cce7e..cfc5c7f10b3 100644\n--- a/config.c\n+++ b/config.c\n@@ -218,7 +218,9 @@ static const char include_depth_advice[] = N_(\n \"from\\n\"\n \"\t%s\\n\"\n \"This might be due to circular includes.\");\n-static int handle_path_include(struct config_source *cs, const char *path,\n+static int handle_path_include(struct config_source *cs,\n+\t\t\t       const struct key_value_info *kvi,\n+\t\t\t       const char *path,\n \t\t\t       struct config_include_data *inc)\n {\n \tint ret = 0;\n@@ -259,8 +261,7 @@ static int handle_path_include(struct config_source *cs, const char *path,\n \t\t\t    cs->name ? cs->name :\n \t\t\t    \"the command line\");\n \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n-\t\t\t\t\t\t\tcurrent_config_scope(),\n-\t\t\t\t\t\t\tNULL);\n+\t\t\t\t\t\t\tkvi->scope, NULL);\n \t\tinc->depth--;\n \t}\n cleanup:\n@@ -508,7 +509,7 @@ static int git_config_include(const char *var, const char *value,\n \t\treturn ret;\n \n \tif (!strcmp(var, \"include.path\"))\n-\t\tret = handle_path_include(cs, value, inc);\n+\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n \t    cond && include_condition_is_true(cs, inc, cond, cond_len) &&\n@@ -517,7 +518,7 @@ static int git_config_include(const char *var, const char *value,\n \n \t\tif (inc->opts->unconditional_remote_url)\n \t\t\tinc->fn = forbid_remote_url;\n-\t\tret = handle_path_include(cs, value, inc);\n+\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -675,27 +676,44 @@ out_free_ret_1:\n }\n \n static int config_parse_pair(const char *key, const char *value,\n-\t\t\t  config_fn_t fn, void *data)\n+\t\t\t     struct key_value_info *kvi,\n+\t\t\t     config_fn_t fn, void *data)\n {\n \tchar *canonical_name;\n \tint ret;\n+\tstruct config_context ctx = {\n+\t\t.kvi = kvi,\n+\t};\n \n \tif (!strlen(key))\n \t\treturn error(_(\"empty config key\"));\n \tif (git_config_parse_key(key, &canonical_name, NULL))\n \t\treturn -1;\n \n-\tret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;\n+\tret = (fn(canonical_name, value, &ctx, data) < 0) ? -1 : 0;\n \tfree(canonical_name);\n \treturn ret;\n }\n \n+\n+/* for values read from `git_config_from_parameters()` */\n+void kvi_from_param(struct key_value_info *out)\n+{\n+\tout->filename = NULL;\n+\tout->linenr = -1;\n+\tout->origin_type = CONFIG_ORIGIN_CMDLINE;\n+\tout->scope = CONFIG_SCOPE_COMMAND;\n+}\n+\n int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n \tconst char *value;\n \tstruct strbuf **pair;\n \tint ret;\n+\tstruct key_value_info kvi = KVI_INIT;\n+\n+\tkvi_from_param(&kvi);\n \n \tpair = strbuf_split_str(text, '=', 2);\n \tif (!pair[0])\n@@ -714,12 +732,13 @@ int git_config_parse_parameter(const char *text,\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \t}\n \n-\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n+\tret = config_parse_pair(pair[0]->buf, value, &kvi, fn, data);\n \tstrbuf_list_free(pair);\n \treturn ret;\n }\n \n-static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n+static int parse_config_env_list(char *env, struct key_value_info *kvi,\n+\t\t\t\t config_fn_t fn, void *data)\n {\n \tchar *cur = env;\n \twhile (cur && *cur) {\n@@ -753,7 +772,7 @@ static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n \t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n \t\t\t}\n \n-\t\t\tif (config_parse_pair(key, value, fn, data) < 0)\n+\t\t\tif (config_parse_pair(key, value, kvi, fn, data) < 0)\n \t\t\t\treturn -1;\n \t\t}\n \t\telse {\n@@ -778,10 +797,13 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tint ret = 0;\n \tchar *envw = NULL;\n \tstruct config_source source = CONFIG_SOURCE_INIT;\n+\tstruct key_value_info kvi = KVI_INIT;\n \n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n \tconfig_reader_push_source(&the_reader, &source);\n \n+\tkvi_from_param(&kvi);\n+\n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n \t\tunsigned long count;\n@@ -817,7 +839,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \t\t\t}\n \t\t\tstrbuf_reset(&envvar);\n \n-\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n+\t\t\tif (config_parse_pair(key, value, &kvi, fn, data) < 0) {\n \t\t\t\tret = -1;\n \t\t\t\tgoto out;\n \t\t\t}\n@@ -828,7 +850,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tif (env) {\n \t\t/* sq_dequote will write over it */\n \t\tenvw = xstrdup(env);\n-\t\tif (parse_config_env_list(envw, fn, data) < 0) {\n+\t\tif (parse_config_env_list(envw, &kvi, fn, data) < 0) {\n \t\t\tret = -1;\n \t\t\tgoto out;\n \t\t}\n@@ -2434,7 +2456,8 @@ static int configset_find_element(struct config_set *set, const char *key,\n \treturn 0;\n }\n \n-static int configset_add_value(struct config_reader *reader,\n+static int configset_add_value(const struct key_value_info *kvi_p,\n+\t\t\t       struct config_reader *reader,\n \t\t\t       struct config_set *set, const char *key,\n \t\t\t       const char *value)\n {\n@@ -2465,16 +2488,10 @@ static int configset_add_value(struct config_reader *reader,\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (!reader->source)\n-\t\tBUG(\"configset_add_value has no source\");\n \tif (reader->source->name) {\n-\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n+\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n \t} else {\n-\t\t/* for values read from `git_config_from_parameters()` */\n-\t\tkv_info->filename = NULL;\n-\t\tkv_info->linenr = -1;\n-\t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n-\t\tkv_info->scope = reader->parsing_scope;\n+\t\tkvi_from_param(kv_info);\n \t}\n \tsi->util = kv_info;\n \n@@ -2530,11 +2547,12 @@ struct configset_add_data {\n #define CONFIGSET_ADD_INIT { 0 }\n \n static int config_set_callback(const char *key, const char *value,\n-\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       const struct config_context *ctx,\n \t\t\t       void *cb)\n {\n \tstruct configset_add_data *data = cb;\n-\tconfigset_add_value(data->config_reader, data->config_set, key, value);\n+\tconfigset_add_value(ctx->kvi, data->config_reader, data->config_set,\n+\t\t\t    key, value);\n \treturn 0;\n }\n \n@@ -4027,16 +4045,6 @@ const char *config_origin_type_name(enum config_origin_type type)\n \t}\n }\n \n-const char *current_config_origin_type(void)\n-{\n-\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n-\n-\tif (reader_origin_type(&the_reader, &type))\n-\t\tBUG(\"current_config_origin_type called outside config callback\");\n-\n-\treturn config_origin_type_name(type);\n-}\n-\n const char *config_scope_name(enum config_scope scope)\n {\n \tswitch (scope) {\n@@ -4068,14 +4076,6 @@ static int reader_config_name(struct config_reader *reader, const char **out)\n \treturn 0;\n }\n \n-const char *current_config_name(void)\n-{\n-\tconst char *name;\n-\tif (reader_config_name(&the_reader, &name))\n-\t\tBUG(\"current_config_name called outside config callback\");\n-\treturn name ? name : \"\";\n-}\n-\n enum config_scope current_config_scope(void)\n {\n \tif (the_reader.config_kvi)\ndiff --git a/config.h b/config.h\nindex 1fc746c3a46..7954a8cb104 100644\n--- a/config.h\n+++ b/config.h\n@@ -387,9 +387,8 @@ void git_global_config(char **user, char **xdg);\n int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n \n enum config_scope current_config_scope(void);\n-const char *current_config_origin_type(void);\n-const char *current_config_name(void);\n const char *config_origin_type_name(enum config_origin_type type);\n+void kvi_from_param(struct key_value_info *out);\n \n /*\n  * Match and parse a config key of the form:\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex fa6a8df2521..387d336c91f 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1676,8 +1676,8 @@ test_expect_success 'urlmatch with --show-scope' '\n \tEOF\n \n \tcat >expect <<-EOF &&\n-\tunknown\thttp.cookiefile /tmp/cookie.txt\n-\tunknown\thttp.sslverify false\n+\tlocal\thttp.cookiefile /tmp/cookie.txt\n+\tlocal\thttp.sslverify false\n \tEOF\n \tgit config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&\n \ttest_cmp expect actual\n@@ -2071,7 +2071,9 @@ test_expect_success '--show-origin blob ref' '\n '\n \n test_expect_success '--show-origin with --default' '\n-\ttest_must_fail git config --show-origin --default foo some.key\n+\tgit config --show-origin --default foo some.key >actual &&\n+\techo \"command line:\tfoo\" >expect &&\n+\ttest_cmp expect actual\n '\n \n test_expect_success '--show-scope with --list' '\n@@ -2144,7 +2146,7 @@ test_expect_success '--show-scope with --show-origin' '\n \n test_expect_success '--show-scope with --default' '\n \tgit config --show-scope --default foo some.key >actual &&\n-\techo \"unknown\tfoo\" >expect &&\n+\techo \"command\tfoo\" >expect &&\n \ttest_cmp expect actual\n '\n \n-- \ngitgitgadget\n\n"},{"id":"478584","messageId":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v2.git.git.1685472132.gitgitgadget@gmail.com","subject":"[PATCH v3 00/12] config: remove global state from config iteration","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-20T19:43:39Z","receivedAt":"2023-06-20T19:44:20Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Junio: I rebased this onto a newer \"master\". I assume this is a noop for\nyou - I noticed that the RFC versions weren't applied anyway (good).\n\nThanks for the thoughtful review on the last round, all. This is the\nfirst non-RFC reroll, and hopefully the last ;) This is basically the\nRFC v2 with the post-RFC cleanups I've been talking about, plus review\ncomments. I did a decent amount of experimentation with Phillip's\nalternative config_fn_t signatures, and while I think they make sense in\nthe long term (when we add additional helpers, make structs opaque,\netc), they introduce a lot of distracting churn but don't really change\nthe status quo, so I've left them out of this series.\n\nA major departure from the RFC is that the large refactoring patches\nhave now been squashed together. If you'd would like to meaningfully\nrange-diff those commits against v2, you can use:\n\n  git fetch https://github.com/chooglen/git/ config/no-global-unsquashed-cocci\n\nwhich uses the same base as v1 and v2.\n\n= Changes since v2\n\n* Rebase onto a newer version of \"master\"\n\n* To accomodate future changes to config_fn_t, use \"const struct\n  config_context *ctx\" (which has a .kvi member) instead of directly\n  referencing \"kvi\".\n\n* Add appropriate comments to config.h.\n\n* .kvi is now const.\n\n* Introduce KVI_INIT as a safer default to \"= { 0 }\".\n\n* Incorporate wording suggestions\n\n= Description\n\nThis series removes all global state from config iteration, i.e. parsing\nconfig and iterating configsets, by passing config metadata as a \"struct\nkey_value_info\" to the \"config_fn_t\" callbacks. This allows us to\nget rid of:\n\n * \"the_reader\" (formerly \"cf\", \"current_parsing_scope\",\n   \"current_config_kvi\"), and the config.c machinery that maintained it.\n   This only needed to be global because it was read by...\n * The \"current_*\" functions that read metadata about the 'current'\n   config value (\"current_config_scope\", \"current_config_name\", etc),\n   which are replaced by reading values from the \"struct key_value_info\"\n   passed to the callbacks.\n\nAs a result:\n\n * Config iterating code can be moved to into its own library with few\n   modifications. C.f. libification efforts [1].\n * The config iterating code can be safely called in parallel.\n * We expose and fix instances where the \"current_*\" functions were being\n   called outside of config callbacks.\n\nWe've had this idea of doing this \"config_fn_t\" refactor for a long time\n[2], but we've never attempted it because we wanted to avoid churn. After\nattempting it, though, I'm quite convinced that this is the right way\nforward for config, since the lack of global state makes things much easier\nto reason about. The churn is also quite manageable:\n\n * The vast majority of changes can be handled with cocci.\n * The few cases that aren't covered by cocci have obvious fixes.\n * The change is simple enough for in-flight topics to perform and conflicts\n   will be caught at build time anyway.\n\nTo avoid _future_ churn (if we want to change config_fn_t again), I\nopted to pass a config_context arg instead of using key_value_info\ndirectly. We can make changes we want to that arg while leaving the\nsignature untouched.\n\n= Patch overview\n\n* 1-3/12 add the \"ctx\" parameter to the config_fn_t signature.\n\n* 4-9/12 converts the config.c machinery off \"config_reader.config_kvi\"\n  and \"config_reader.source\" and onto ctx->kvi. We convert the config.c\n  machinery from \"config_reader\" to \"kvi\" one-by-one: configsets, then\n  files, then CLI. To exercise the \"kvi\" arg as soon as possible, we\n  convert from current_*() to \"kvi\" as soon as it is available. For\n  example, in 4/12 \"kvi\" is available only in configsets, so we convert\n  the current_*() call sites that are only reached via configsets and\n  leave the others untouched. This means that we have a mix of\n  current_*() and \"kvi\" in the middle, but auditing the changes is\n  relatively easy, since you only need to verify that a callback isn't\n  relying on the \"kvi\" arg before it is available, and that current_*()\n  and \"kvi\" give the same value.\n\n  * 6-7/12 squashes some bugs where builtin/config.c was calling the\n    current_*() API outside of config callbacks. The \"kvi\" plumbing\n    doesn't just make the bugs apparent, it also provides an obvious way\n    to fix the bugs (by injecting \"kvi\" into the right places in\n    builtin/config.c). These would have been nontrivial to fix if we\n    were still using global state.\n\n* 10-12/12 remove config_reader by taking advantage of \"kvi\" and doing\n  some other light plumbing.\n\n= Alternatives considered\n\nÆvar suggested in [3] that we might be able to do the refactor incrementally\nby having both the old \"config_fn_t\" and the new \"config_fn_t with kvi\",\nwhich lets us convert some of config iterating (e.g. configsets) without\ntouching the others (e.g. config parsing). I experimented with that for a\nbit, and it turned out that doing it all at once is actually less work\nbecause we don't have to worry about the case where the same \"config_fn_t\"\nis used in both git_config() and git_config_from_file().\n\nJonathan Tan suggested in [4] that to reduce churn, we might be able to\nconvert many of the config_fn_t-s to the config set API before attempting\nthis refactor. But (hopefully), these patches show that the churn is\nmanageable even without this preparatory step.\n\n[1]\nhttps://lore.kernel.org/git/CAJoAoZ=Cig_kLocxKGax31sU7Xe4==BGzC__Bg2_pr7krNq6MA@mail.gmail.com/\n[2]\nhttps://lore.kernel.org/git/CAPc5daV6bdUKS-ExHmpT4Ppy2S832NXoyPw7aOLP7fG=WrBPgg@mail.gmail.com/\n[3]\nhttps://lore.kernel.org/git/RFC-patch-5.5-2b80d293c83-20230317T042408Z-avarab@gmail.com\n[4]\nhttps://lore.kernel.org/git/20230306195756.3399115-1-jonathantanmy@google.com/\n\n\nGlen Choo (12):\n  config: inline git_color_default_config\n  urlmatch.h: use config_fn_t type\n  config: add ctx arg to config_fn_t\n  config.c: pass ctx in configsets\n  config: pass ctx with config files\n  builtin/config.c: test misuse of format_config()\n  config.c: pass ctx with CLI config\n  trace2: plumb config kvi\n  config: pass kvi to die_bad_number()\n  config.c: remove config_reader from configsets\n  config: add kvi.path, use it to evaluate includes\n  config: pass source to config_parser_event_fn_t\n\n alias.c                                       |   3 +-\n archive-tar.c                                 |   5 +-\n archive-zip.c                                 |   1 +\n builtin/add.c                                 |   5 +-\n builtin/blame.c                               |   5 +-\n builtin/branch.c                              |   8 +-\n builtin/cat-file.c                            |   5 +-\n builtin/checkout.c                            |  12 +-\n builtin/clean.c                               |   9 +-\n builtin/clone.c                               |  11 +-\n builtin/column.c                              |   3 +-\n builtin/commit-graph.c                        |   3 +-\n builtin/commit.c                              |  20 +-\n builtin/config.c                              |  72 ++-\n builtin/difftool.c                            |   5 +-\n builtin/fetch.c                               |  13 +-\n builtin/fsmonitor--daemon.c                   |  11 +-\n builtin/grep.c                                |  12 +-\n builtin/help.c                                |   5 +-\n builtin/index-pack.c                          |   9 +-\n builtin/log.c                                 |  12 +-\n builtin/merge.c                               |   7 +-\n builtin/multi-pack-index.c                    |   1 +\n builtin/pack-objects.c                        |  19 +-\n builtin/patch-id.c                            |   5 +-\n builtin/pull.c                                |   5 +-\n builtin/push.c                                |   5 +-\n builtin/read-tree.c                           |   5 +-\n builtin/rebase.c                              |   5 +-\n builtin/receive-pack.c                        |  15 +-\n builtin/reflog.c                              |   7 +-\n builtin/remote.c                              |  15 +-\n builtin/repack.c                              |   5 +-\n builtin/reset.c                               |   5 +-\n builtin/send-pack.c                           |   5 +-\n builtin/show-branch.c                         |   8 +-\n builtin/stash.c                               |   5 +-\n builtin/submodule--helper.c                   |   3 +-\n builtin/tag.c                                 |   9 +-\n builtin/var.c                                 |   5 +-\n builtin/worktree.c                            |   5 +-\n bundle-uri.c                                  |   9 +-\n color.c                                       |   8 -\n color.h                                       |   6 +-\n compat/mingw.c                                |   3 +-\n compat/mingw.h                                |   4 +-\n config.c                                      | 551 +++++++-----------\n config.h                                      |  80 ++-\n connect.c                                     |   4 +-\n .../coccinelle/config_fn_ctx.pending.cocci    | 144 +++++\n contrib/coccinelle/git_config_number.cocci    |  27 +\n convert.c                                     |   4 +-\n credential.c                                  |   1 +\n delta-islands.c                               |   4 +-\n diff.c                                        |  19 +-\n diff.h                                        |   7 +-\n fetch-pack.c                                  |   5 +-\n fmt-merge-msg.c                               |   7 +-\n fmt-merge-msg.h                               |   3 +-\n fsck.c                                        |  12 +-\n fsck.h                                        |   4 +-\n git-compat-util.h                             |   2 +\n gpg-interface.c                               |   7 +-\n grep.c                                        |   7 +-\n grep.h                                        |   4 +-\n help.c                                        |   9 +-\n http.c                                        |  15 +-\n ident.c                                       |   4 +-\n ident.h                                       |   4 +-\n imap-send.c                                   |   7 +-\n ll-merge.c                                    |   1 +\n ls-refs.c                                     |   1 +\n mailinfo.c                                    |   5 +-\n notes-utils.c                                 |   4 +-\n notes.c                                       |   4 +-\n pager.c                                       |   5 +-\n pretty.c                                      |   1 +\n promisor-remote.c                             |   4 +-\n remote.c                                      |   8 +-\n revision.c                                    |   4 +-\n scalar.c                                      |   4 +-\n sequencer.c                                   |  29 +-\n setup.c                                       |  18 +-\n submodule-config.c                            |  31 +-\n submodule-config.h                            |   3 +-\n t/helper/test-config.c                        |  24 +-\n t/helper/test-userdiff.c                      |   4 +-\n t/t1300-config.sh                             |  27 +\n trace2.c                                      |   4 +-\n trace2.h                                      |   3 +-\n trace2/tr2_cfg.c                              |  16 +-\n trace2/tr2_sysenv.c                           |   3 +-\n trace2/tr2_tgt.h                              |   4 +-\n trace2/tr2_tgt_event.c                        |   4 +-\n trace2/tr2_tgt_normal.c                       |   4 +-\n trace2/tr2_tgt_perf.c                         |   4 +-\n trailer.c                                     |   2 +\n upload-pack.c                                 |  18 +-\n urlmatch.c                                    |   7 +-\n urlmatch.h                                    |   8 +-\n worktree.c                                    |   2 +-\n xdiff-interface.c                             |   5 +-\n xdiff-interface.h                             |   4 +-\n 103 files changed, 956 insertions(+), 638 deletions(-)\n create mode 100644 contrib/coccinelle/config_fn_ctx.pending.cocci\n create mode 100644 contrib/coccinelle/git_config_number.cocci\n\n\nbase-commit: d7d8841f67f29e6ecbad85a11805c907d0f00d5d\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1497%2Fchooglen%2Fconfig%2Fno-global-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1497/chooglen/config/no-global-v3\nPull-Request: https://github.com/git/git/pull/1497\n\nRange-diff vs v2:\n\n  1:  d5edf7e3fdd =  1:  26109b65142 config: inline git_color_default_config\n  2:  821f0b90580 =  2:  1aeffcb1395 urlmatch.h: use config_fn_t type\n  3:  6834e37066e <  -:  ----------- (RFC-only) config: add kvi arg to config_fn_t\n  4:  bd52c6232ec !  3:  d3eb439aad2 (RFC-only) config: apply cocci to config_fn_t implementations\n     @@ Metadata\n      Author: Glen Choo <chooglen@google.com>\n      \n       ## Commit message ##\n     -    (RFC-only) config: apply cocci to config_fn_t implementations\n     -\n     -    Pass \"struct key_value_info\" to *most* functions that are invoked as\n     -    \"config_fn_t\" callbacks by applying\n     -    contrib/coccinelle/config_fn_kvi.pending.cocci. None of the functions\n     -    actually use the \"kvi\" arg yet (besides propagating it to a function\n     -    that now expects \"kvi\"), but this will be addressed in a later commit.\n     -    When deciding whether or not to propagate \"kvi\" to an inner function,\n     -    only propagate the \"kvi\" arg if the inner function is actually invoked\n     -    elsewhere as a config callback; it does not matter whether the function\n     -    happens have the same signature as config_fn_t.\n     -\n     -    This commit does not build and has several style issues (e.g. a lack of\n     -    spacing around the \"kvi\" arg), but I've split this out for the RFC so\n     -    that it's more obvious which changes are automatic vs manual. Post-RFC I\n     -    will squash this with the rest of the refactor + cocci changes.\n     +    config: add ctx arg to config_fn_t\n     +\n     +    Add a new \"const struct config_context *ctx\" arg to config_fn_t to hold\n     +    additional information about the config iteration operation.\n     +    config_context has a \"struct key_value_info kvi\" member that holds\n     +    metadata about the config source being read (e.g. what kind of config\n     +    source it is, the filename, etc). In this series, we're only interested\n     +    in .kvi, so we could have just used \"struct key_value_info\" as an arg,\n     +    but config_context makes it possible to add/adjust members in the future\n     +    without changing the config_fn_t signature. We could also consider other\n     +    ways of organizing the args (e.g. moving the config name and value into\n     +    config_context or key_value_info), but in my experiments, the\n     +    incremental benefit doesn't justify the added complexity (e.g. a\n     +    config_fn_t will sometimes invoke another config_fn_t but with a\n     +    different config value).\n     +\n     +    In subsequent commits, the .kvi member will replace the global \"struct\n     +    config_reader\" in config.c, making config iteration a global-free\n     +    operation. It requires much more work for the machinery to provide\n     +    meaningful values of .kvi, so for now, merely change the signature and\n     +    call sites, pass NULL as a placeholder value, and don't rely on the arg\n     +    in any meaningful way.\n     +\n     +    Most of the changes are performed by\n     +    contrib/coccinelle/config_fn_ctx.pending.cocci, which, for every\n     +    config_fn_t:\n     +\n     +    - Modifies the signature to accept \"const struct config_context *ctx\"\n     +    - Passes \"ctx\" to any inner config_fn_t, if needed\n     +    - Adds UNUSED attributes to \"ctx\", if needed\n     +\n     +    Most config_fn_t instances are easily identified by seeing if they are\n     +    called by the various config functions. Most of the remaining ones are\n     +    manually named in the .cocci patch. Manual cleanups are still needed,\n     +    but the majority of it is trivial; it's either adjusting config_fn_t\n     +    that the .cocci patch didn't catch, or adding forward declarations of\n     +    \"struct config_context ctx\" to make the signatures make sense.\n     +\n     +    The non-trivial changes are in cases where we are invoking a config_fn_t\n     +    outside of config machinery, and we now need to decide what value of\n     +    \"ctx\" to pass. These cases are:\n     +\n     +    - trace2/tr2_cfg.c:tr2_cfg_set_fl()\n     +\n     +      This is indirectly called by git_config_set() so that the trace2\n     +      machinery can notice the new config values and update its settings\n     +      using the tr2 config parsing function, i.e. tr2_cfg_cb().\n     +\n     +    - builtin/checkout.c:checkout_main()\n     +\n     +      This calls git_xmerge_config() as a shorthand for parsing a CLI arg.\n     +      This might be worth refactoring away in the future, since\n     +      git_xmerge_config() can call git_default_config(), which can do much\n     +      more than just parsing.\n     +\n     +    Handle them by creating a KVI_INIT macro that initializes \"struct\n     +    key_value_info\" to a reasonable default, and use that to construct the\n     +    \"ctx\" arg.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n     @@ alias.c: struct config_alias_data {\n       \n      -static int config_alias_cb(const char *key, const char *value, void *d)\n      +static int config_alias_cb(const char *key, const char *value,\n     -+\t\t\t   struct key_value_info *kvi UNUSED, void *d)\n     ++\t\t\t   const struct config_context *ctx UNUSED, void *d)\n       {\n       \tstruct config_alias_data *data = d;\n       \tconst char *p;\n     @@ archive-tar.c: static int tar_filter_config(const char *var, const char *value,\n       \n      -static int git_tar_config(const char *var, const char *value, void *cb)\n      +static int git_tar_config(const char *var, const char *value,\n     -+\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n       {\n       \tif (!strcmp(var, \"tar.umask\")) {\n       \t\tif (value && !strcmp(value, \"user\")) {\n     @@ archive-zip.c: static void dos_time(timestamp_t *timestamp, int *dos_date, int *\n       }\n       \n       static int archive_zip_config(const char *var, const char *value,\n     -+\t\t\t      struct key_value_info *kvi UNUSED,\n     ++\t\t\t      const struct config_context *ctx UNUSED,\n       \t\t\t      void *data UNUSED)\n       {\n       \treturn userdiff_config(var, value);\n     @@ builtin/add.c: static struct option builtin_add_options[] = {\n       \n      -static int add_config(const char *var, const char *value, void *cb)\n      +static int add_config(const char *var, const char *value,\n     -+\t\t      struct key_value_info *kvi, void *cb)\n     ++\t\t      const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"add.ignoreerrors\") ||\n       \t    !strcmp(var, \"add.ignore-errors\")) {\n     @@ builtin/add.c: static int add_config(const char *var, const char *value, void *c\n       \t}\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static const char embedded_advice[] = N_(\n     @@ builtin/blame.c: static const char *add_prefix(const char *prefix, const char *p\n       \n      -static int git_blame_config(const char *var, const char *value, void *cb)\n      +static int git_blame_config(const char *var, const char *value,\n     -+\t\t\t    struct key_value_info *kvi, void *cb)\n     ++\t\t\t    const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"blame.showroot\")) {\n       \t\tshow_root = git_config_bool(var, value);\n     @@ builtin/blame.c: static int git_blame_config(const char *var, const char *value,\n       \t\treturn -1;\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static int blame_copy_callback(const struct option *option, const char *arg, int unset)\n     @@ builtin/branch.c: static unsigned int colopts;\n       \n      -static int git_branch_config(const char *var, const char *value, void *cb)\n      +static int git_branch_config(const char *var, const char *value,\n     -+\t\t\t     struct key_value_info *kvi, void *cb)\n     ++\t\t\t     const struct config_context *ctx, void *cb)\n       {\n       \tconst char *slot_name;\n       \n     @@ builtin/branch.c: static int git_branch_config(const char *var, const char *valu\n       \t\treturn -1;\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static const char *branch_get_color(enum color_branch ix)\n     @@ builtin/cat-file.c: static int batch_objects(struct batch_options *opt)\n       \n      -static int git_cat_file_config(const char *var, const char *value, void *cb)\n      +static int git_cat_file_config(const char *var, const char *value,\n     -+\t\t\t       struct key_value_info *kvi, void *cb)\n     ++\t\t\t       const struct config_context *ctx, void *cb)\n       {\n       \tif (userdiff_config(var, value) < 0)\n       \t\treturn -1;\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static int batch_option_callback(const struct option *opt,\n     @@ builtin/checkout.c: static int switch_branches(const struct checkout_opts *opts,\n       \n      -static int git_checkout_config(const char *var, const char *value, void *cb)\n      +static int git_checkout_config(const char *var, const char *value,\n     -+\t\t\t       struct key_value_info *kvi, void *cb)\n     ++\t\t\t       const struct config_context *ctx, void *cb)\n       {\n       \tstruct checkout_opts *opts = cb;\n       \n     @@ builtin/checkout.c: static int git_checkout_config(const char *var, const char *\n       \t\treturn git_default_submodule_config(var, value, NULL);\n       \n      -\treturn git_xmerge_config(var, value, NULL);\n     -+\treturn git_xmerge_config(var, value,kvi, NULL);\n     ++\treturn git_xmerge_config(var, value, ctx, NULL);\n       }\n       \n       static void setup_new_branch_info_and_source_tree(\n     +@@ builtin/checkout.c: static int checkout_main(int argc, const char **argv, const char *prefix,\n     + \t}\n     + \n     + \tif (opts->conflict_style) {\n     ++\t\tstruct key_value_info kvi = KVI_INIT;\n     ++\t\tstruct config_context ctx = {\n     ++\t\t\t.kvi = &kvi,\n     ++\t\t};\n     + \t\topts->merge = 1; /* implied */\n     +-\t\tgit_xmerge_config(\"merge.conflictstyle\", opts->conflict_style, NULL);\n     ++\t\tgit_xmerge_config(\"merge.conflictstyle\", opts->conflict_style,\n     ++\t\t\t\t  &ctx, NULL);\n     + \t}\n     + \tif (opts->force) {\n     + \t\topts->discard_changes = 1;\n      \n       ## builtin/clean.c ##\n      @@ builtin/clean.c: struct menu_stuff {\n     @@ builtin/clean.c: struct menu_stuff {\n       \n      -static int git_clean_config(const char *var, const char *value, void *cb)\n      +static int git_clean_config(const char *var, const char *value,\n     -+\t\t\t    struct key_value_info *kvi, void *cb)\n     ++\t\t\t    const struct config_context *ctx, void *cb)\n       {\n       \tconst char *slot_name;\n       \n     @@ builtin/clean.c: static int git_clean_config(const char *var, const char *value,\n       \t\treturn -1;\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static const char *clean_get_color(enum color_clean ix)\n     @@ builtin/clone.c: static int checkout(int submodule_progress, int filter_submodul\n       \n      -static int git_clone_config(const char *k, const char *v, void *cb)\n      +static int git_clone_config(const char *k, const char *v,\n     -+\t\t\t    struct key_value_info *kvi, void *cb)\n     ++\t\t\t    const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(k, \"clone.defaultremotename\")) {\n       \t\tfree(remote_name);\n     @@ builtin/clone.c: static int git_clone_config(const char *k, const char *v, void\n       \t\tconfig_filter_submodules = git_config_bool(k, v);\n       \n      -\treturn git_default_config(k, v, cb);\n     -+\treturn git_default_config(k, v,kvi, cb);\n     ++\treturn git_default_config(k, v, ctx, cb);\n       }\n       \n      -static int write_one_config(const char *key, const char *value, void *data)\n      +static int write_one_config(const char *key, const char *value,\n     -+\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n     ++\t\t\t    const struct config_context *ctx,\n     ++\t\t\t    void *data)\n       {\n       \t/*\n       \t * give git_clone_config a chance to write config values back to the\n     + \t * environment, since git_config_set_multivar_gently only deals with\n     + \t * config-file writes\n     + \t */\n     +-\tint apply_failed = git_clone_config(key, value, data);\n     ++\tint apply_failed = git_clone_config(key, value, ctx, data);\n     + \tif (apply_failed)\n     + \t\treturn apply_failed;\n     + \n      \n       ## builtin/column.c ##\n      @@ builtin/column.c: static const char * const builtin_column_usage[] = {\n     @@ builtin/column.c: static const char * const builtin_column_usage[] = {\n       \n      -static int column_config(const char *var, const char *value, void *cb)\n      +static int column_config(const char *var, const char *value,\n     -+\t\t\t struct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t const struct config_context *ctx UNUSED, void *cb)\n       {\n       \treturn git_column_config(var, value, cb, &colopts);\n       }\n     @@ builtin/commit-graph.c: static int write_option_max_new_filters(const struct opt\n       }\n       \n       static int git_commit_graph_write_config(const char *var, const char *value,\n     -+\t\t\t\t\t struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t\t const struct config_context *ctx UNUSED,\n       \t\t\t\t\t void *cb UNUSED)\n       {\n       \tif (!strcmp(var, \"commitgraph.maxnewfilters\"))\n     @@ builtin/commit.c: static int parse_status_slot(const char *slot)\n       \n      -static int git_status_config(const char *k, const char *v, void *cb)\n      +static int git_status_config(const char *k, const char *v,\n     -+\t\t\t     struct key_value_info *kvi, void *cb)\n     ++\t\t\t     const struct config_context *ctx, void *cb)\n       {\n       \tstruct wt_status *s = cb;\n       \tconst char *slot_name;\n     @@ builtin/commit.c: static int git_status_config(const char *k, const char *v, voi\n       \t\treturn 0;\n       \t}\n      -\treturn git_diff_ui_config(k, v, NULL);\n     -+\treturn git_diff_ui_config(k, v,kvi, NULL);\n     ++\treturn git_diff_ui_config(k, v, ctx, NULL);\n       }\n       \n       int cmd_status(int argc, const char **argv, const char *prefix)\n     @@ builtin/commit.c: int cmd_status(int argc, const char **argv, const char *prefix\n       \n      -static int git_commit_config(const char *k, const char *v, void *cb)\n      +static int git_commit_config(const char *k, const char *v,\n     -+\t\t\t     struct key_value_info *kvi, void *cb)\n     ++\t\t\t     const struct config_context *ctx, void *cb)\n       {\n       \tstruct wt_status *s = cb;\n       \n     @@ builtin/commit.c: static int git_commit_config(const char *k, const char *v, voi\n       \t}\n       \n      -\treturn git_status_config(k, v, s);\n     -+\treturn git_status_config(k, v,kvi, s);\n     ++\treturn git_status_config(k, v, ctx, s);\n       }\n       \n       int cmd_commit(int argc, const char **argv, const char *prefix)\n     @@ builtin/config.c: static void show_config_scope(struct strbuf *buf)\n       }\n       \n       static int show_all_config(const char *key_, const char *value_,\n     --\t\t\t   void *cb UNUSED)\n     -+\t\t\t   struct key_value_info *kvi UNUSED, void *cb UNUSED)\n     ++\t\t\t   const struct config_context *ctx UNUSED,\n     + \t\t\t   void *cb UNUSED)\n       {\n       \tif (show_origin || show_scope) {\n     - \t\tstruct strbuf buf = STRBUF_INIT;\n      @@ builtin/config.c: static int format_config(struct strbuf *buf, const char *key_, const char *value\n       \treturn 0;\n       }\n       \n      -static int collect_config(const char *key_, const char *value_, void *cb)\n      +static int collect_config(const char *key_, const char *value_,\n     -+\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n       {\n       \tstruct strbuf_list *values = cb;\n       \n     @@ builtin/config.c: static const char *get_colorbool_slot;\n       static char parsed_color[COLOR_MAXLEN];\n       \n       static int git_get_color_config(const char *var, const char *value,\n     -+\t\t\t\tstruct key_value_info *kvi UNUSED,\n     ++\t\t\t\tconst struct config_context *ctx UNUSED,\n       \t\t\t\tvoid *cb UNUSED)\n       {\n       \tif (!strcmp(var, get_color_slot)) {\n     @@ builtin/config.c: static int get_colorbool_found;\n       static int get_diff_color_found;\n       static int get_color_ui_found;\n       static int git_get_colorbool_config(const char *var, const char *value,\n     -+\t\t\t\t    struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t    const struct config_context *ctx UNUSED,\n       \t\t\t\t    void *data UNUSED)\n       {\n       \tif (!strcmp(var, get_colorbool_slot))\n     @@ builtin/config.c: struct urlmatch_current_candidate_value {\n       \n      -static int urlmatch_collect_fn(const char *var, const char *value, void *cb)\n      +static int urlmatch_collect_fn(const char *var, const char *value,\n     -+\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t       const struct config_context *ctx UNUSED,\n     ++\t\t\t       void *cb)\n       {\n       \tstruct string_list *values = cb;\n       \tstruct string_list_item *item = string_list_insert(values, var);\n     @@ builtin/difftool.c: static const char *const builtin_difftool_usage[] = {\n       \n      -static int difftool_config(const char *var, const char *value, void *cb)\n      +static int difftool_config(const char *var, const char *value,\n     -+\t\t\t   struct key_value_info *kvi, void *cb)\n     ++\t\t\t   const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"difftool.trustexitcode\")) {\n       \t\ttrust_exit_code = git_config_bool(var, value);\n     @@ builtin/difftool.c: static const char *const builtin_difftool_usage[] = {\n       \t}\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static int print_tool_help(void)\n      \n       ## builtin/fetch.c ##\n     -@@ builtin/fetch.c: static int fetch_write_commit_graph = -1;\n     - static int stdin_refspecs = 0;\n     - static int negotiate_only;\n     +@@ builtin/fetch.c: struct fetch_config {\n     + \tenum display_format display_format;\n     + };\n       \n      -static int git_fetch_config(const char *k, const char *v, void *cb)\n      +static int git_fetch_config(const char *k, const char *v,\n     -+\t\t\t    struct key_value_info *kvi, void *cb)\n     ++\t\t\t    const struct config_context *ctx, void *cb)\n       {\n     - \tif (!strcmp(k, \"fetch.prune\")) {\n     - \t\tfetch_prune_config = git_config_bool(k, v);\n     + \tstruct fetch_config *fetch_config = cb;\n     + \n      @@ builtin/fetch.c: static int git_fetch_config(const char *k, const char *v, void *cb)\n     - \t\treturn 0;\n     + \t\t\t    \"fetch.output\", v);\n       \t}\n       \n      -\treturn git_default_config(k, v, cb);\n     -+\treturn git_default_config(k, v,kvi, cb);\n     ++\treturn git_default_config(k, v, ctx, cb);\n       }\n       \n       static int parse_refmap_arg(const struct option *opt, const char *arg, int unset)\n     @@ builtin/fetch.c: struct remote_group_data {\n       \n      -static int get_remote_group(const char *key, const char *value, void *priv)\n      +static int get_remote_group(const char *key, const char *value,\n     -+\t\t\t    struct key_value_info *kvi UNUSED, void *priv)\n     ++\t\t\t    const struct config_context *ctx UNUSED,\n     ++\t\t\t    void *priv)\n       {\n       \tstruct remote_group_data *g = priv;\n       \n     @@ builtin/fsmonitor--daemon.c: static int fsmonitor__start_timeout_sec = 60;\n       \n      -static int fsmonitor_config(const char *var, const char *value, void *cb)\n      +static int fsmonitor_config(const char *var, const char *value,\n     -+\t\t\t    struct key_value_info *kvi, void *cb)\n     ++\t\t\t    const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, FSMONITOR__IPC_THREADS)) {\n       \t\tint i = git_config_int(var, value);\n     @@ builtin/fsmonitor--daemon.c: static int fsmonitor_config(const char *var, const\n       \t}\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       /*\n     @@ builtin/grep.c: static int wait_all(void)\n       \n      -static int grep_cmd_config(const char *var, const char *value, void *cb)\n      +static int grep_cmd_config(const char *var, const char *value,\n     -+\t\t\t   struct key_value_info *kvi, void *cb)\n     ++\t\t\t   const struct config_context *ctx, void *cb)\n       {\n     - \tint st = grep_config(var, value, cb);\n     +-\tint st = grep_config(var, value, cb);\n     ++\tint st = grep_config(var, value, ctx, cb);\n       \n       \tif (git_color_config(var, value, cb) < 0)\n       \t\tst = -1;\n      -\telse if (git_default_config(var, value, cb) < 0)\n     -+\telse if (git_default_config(var, value,kvi, cb) < 0)\n     ++\telse if (git_default_config(var, value, ctx, cb) < 0)\n       \t\tst = -1;\n       \n       \tif (!strcmp(var, \"grep.threads\")) {\n     @@ builtin/help.c: static int add_man_viewer_info(const char *var, const char *valu\n       \n      -static int git_help_config(const char *var, const char *value, void *cb)\n      +static int git_help_config(const char *var, const char *value,\n     -+\t\t\t   struct key_value_info *kvi, void *cb)\n     ++\t\t\t   const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"help.format\")) {\n       \t\tif (!value)\n     @@ builtin/help.c: static int git_help_config(const char *var, const char *value, v\n       \t\treturn add_man_viewer_info(var, value);\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static struct cmdnames main_cmds, other_cmds;\n     @@ builtin/index-pack.c: static void final(const char *final_pack_name, const char\n       \n      -static int git_index_pack_config(const char *k, const char *v, void *cb)\n      +static int git_index_pack_config(const char *k, const char *v,\n     -+\t\t\t\t struct key_value_info *kvi, void *cb)\n     ++\t\t\t\t const struct config_context *ctx, void *cb)\n       {\n       \tstruct pack_idx_option *opts = cb;\n       \n     @@ builtin/index-pack.c: static int git_index_pack_config(const char *k, const char\n       \t\t\topts->flags &= ~WRITE_REV;\n       \t}\n      -\treturn git_default_config(k, v, cb);\n     -+\treturn git_default_config(k, v,kvi, cb);\n     ++\treturn git_default_config(k, v, ctx, cb);\n       }\n       \n       static int cmp_uint32(const void *a_, const void *b_)\n     @@ builtin/log.c: static int cmd_log_walk(struct rev_info *rev)\n       \n      -static int git_log_config(const char *var, const char *value, void *cb)\n      +static int git_log_config(const char *var, const char *value,\n     -+\t\t\t  struct key_value_info *kvi, void *cb)\n     ++\t\t\t  const struct config_context *ctx, void *cb)\n       {\n       \tconst char *slot_name;\n       \n     @@ builtin/log.c: static int git_log_config(const char *var, const char *value, voi\n       \t}\n       \n      -\treturn git_diff_ui_config(var, value, cb);\n     -+\treturn git_diff_ui_config(var, value,kvi, cb);\n     ++\treturn git_diff_ui_config(var, value, ctx, cb);\n       }\n       \n       int cmd_whatchanged(int argc, const char **argv, const char *prefix)\n     @@ builtin/log.c: static enum cover_from_description parse_cover_from_description(c\n       \n      -static int git_format_config(const char *var, const char *value, void *cb)\n      +static int git_format_config(const char *var, const char *value,\n     -+\t\t\t     struct key_value_info *kvi, void *cb)\n     ++\t\t\t     const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"format.headers\")) {\n       \t\tif (!value)\n     @@ builtin/log.c: static int git_format_config(const char *var, const char *value,\n       \t\treturn 0;\n       \n      -\treturn git_log_config(var, value, cb);\n     -+\treturn git_log_config(var, value,kvi, cb);\n     ++\treturn git_log_config(var, value, ctx, cb);\n       }\n       \n       static const char *output_directory = NULL;\n     @@ builtin/merge.c: static void parse_branch_merge_options(char *bmo)\n       \n      -static int git_merge_config(const char *k, const char *v, void *cb)\n      +static int git_merge_config(const char *k, const char *v,\n     -+\t\t\t    struct key_value_info *kvi, void *cb)\n     ++\t\t\t    const struct config_context *ctx, void *cb)\n       {\n       \tint status;\n       \tconst char *str;\n     @@ builtin/merge.c: static int git_merge_config(const char *k, const char *v, void\n       \t}\n       \n      -\tstatus = fmt_merge_msg_config(k, v, cb);\n     -+\tstatus = fmt_merge_msg_config(k, v,kvi, cb);\n     ++\tstatus = fmt_merge_msg_config(k, v, ctx, cb);\n       \tif (status)\n       \t\treturn status;\n      -\treturn git_diff_ui_config(k, v, cb);\n     -+\treturn git_diff_ui_config(k, v,kvi, cb);\n     ++\treturn git_diff_ui_config(k, v, ctx, cb);\n       }\n       \n       static int read_tree_trivial(struct object_id *common, struct object_id *head,\n     @@ builtin/multi-pack-index.c: static struct option *add_common_options(struct opti\n       }\n       \n       static int git_multi_pack_index_write_config(const char *var, const char *value,\n     -+\t\t\t\t\t     struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t\t     const struct config_context *ctx UNUSED,\n       \t\t\t\t\t     void *cb UNUSED)\n       {\n       \tif (!strcmp(var, \"pack.writebitmaphashcache\")) {\n     @@ builtin/pack-objects.c: static void prepare_pack(int window, int depth)\n       \n      -static int git_pack_config(const char *k, const char *v, void *cb)\n      +static int git_pack_config(const char *k, const char *v,\n     -+\t\t\t   struct key_value_info *kvi, void *cb)\n     ++\t\t\t   const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(k, \"pack.window\")) {\n       \t\twindow = git_config_int(k, v);\n     @@ builtin/pack-objects.c: static int git_pack_config(const char *k, const char *v,\n       \t\toidmap_put(&configured_exclusions, ex);\n       \t}\n      -\treturn git_default_config(k, v, cb);\n     -+\treturn git_default_config(k, v,kvi, cb);\n     ++\treturn git_default_config(k, v, ctx, cb);\n       }\n       \n       /* Counters for trace2 output when in --stdin-packs mode. */\n     @@ builtin/patch-id.c: struct patch_id_opts {\n       \n      -static int git_patch_id_config(const char *var, const char *value, void *cb)\n      +static int git_patch_id_config(const char *var, const char *value,\n     -+\t\t\t       struct key_value_info *kvi, void *cb)\n     ++\t\t\t       const struct config_context *ctx, void *cb)\n       {\n       \tstruct patch_id_opts *opts = cb;\n       \n     @@ builtin/patch-id.c: static int git_patch_id_config(const char *var, const char *\n       \t}\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       int cmd_patch_id(int argc, const char **argv, const char *prefix)\n     @@ builtin/pull.c: static enum rebase_type config_get_rebase(int *rebase_unspecifie\n        */\n      -static int git_pull_config(const char *var, const char *value, void *cb)\n      +static int git_pull_config(const char *var, const char *value,\n     -+\t\t\t   struct key_value_info *kvi, void *cb)\n     ++\t\t\t   const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"rebase.autostash\")) {\n       \t\tconfig_autostash = git_config_bool(var, value);\n     @@ builtin/pull.c: static int git_pull_config(const char *var, const char *value, v\n       \t}\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       /**\n     @@ builtin/push.c: static void set_push_cert_flags(int *flags, int v)\n       \n      -static int git_push_config(const char *k, const char *v, void *cb)\n      +static int git_push_config(const char *k, const char *v,\n     -+\t\t\t   struct key_value_info *kvi, void *cb)\n     ++\t\t\t   const struct config_context *ctx, void *cb)\n       {\n       \tconst char *slot_name;\n       \tint *flags = cb;\n     @@ builtin/push.c: static int git_push_config(const char *k, const char *v, void *c\n       \t}\n       \n      -\treturn git_default_config(k, v, NULL);\n     -+\treturn git_default_config(k, v,kvi, NULL);\n     ++\treturn git_default_config(k, v, ctx, NULL);\n       }\n       \n       int cmd_push(int argc, const char **argv, const char *prefix)\n     @@ builtin/read-tree.c: static int debug_merge(const struct cache_entry * const *st\n       \n      -static int git_read_tree_config(const char *var, const char *value, void *cb)\n      +static int git_read_tree_config(const char *var, const char *value,\n     -+\t\t\t\tstruct key_value_info *kvi, void *cb)\n     ++\t\t\t\tconst struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"submodule.recurse\"))\n       \t\treturn git_default_submodule_config(var, value, cb);\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)\n     @@ builtin/rebase.c: static void parse_rebase_merges_value(struct rebase_options *o\n       \n      -static int rebase_config(const char *var, const char *value, void *data)\n      +static int rebase_config(const char *var, const char *value,\n     -+\t\t\t struct key_value_info *kvi, void *data)\n     ++\t\t\t const struct config_context *ctx, void *data)\n       {\n       \tstruct rebase_options *opts = data;\n       \n     @@ builtin/rebase.c: static int rebase_config(const char *var, const char *value, v\n       \t}\n       \n      -\treturn git_default_config(var, value, data);\n     -+\treturn git_default_config(var, value,kvi, data);\n     ++\treturn git_default_config(var, value, ctx, data);\n       }\n       \n       static int checkout_up_to_date(struct rebase_options *options)\n     @@ builtin/receive-pack.c: static enum deny_action parse_deny_action(const char *va\n       \n      -static int receive_pack_config(const char *var, const char *value, void *cb)\n      +static int receive_pack_config(const char *var, const char *value,\n     -+\t\t\t       struct key_value_info *kvi, void *cb)\n     ++\t\t\t       const struct config_context *ctx, void *cb)\n       {\n       \tint status = parse_hide_refs_config(var, value, \"receive\", &hidden_refs);\n       \n     @@ builtin/receive-pack.c: static int receive_pack_config(const char *var, const ch\n       \t}\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static void show_ref(const char *path, const struct object_id *oid)\n     @@ builtin/reflog.c: static struct reflog_expire_cfg *find_cfg_ent(const char *patt\n       \n      -static int reflog_expire_config(const char *var, const char *value, void *cb)\n      +static int reflog_expire_config(const char *var, const char *value,\n     -+\t\t\t\tstruct key_value_info *kvi, void *cb)\n     ++\t\t\t\tconst struct config_context *ctx, void *cb)\n       {\n       \tconst char *pattern, *key;\n       \tsize_t pattern_len;\n     @@ builtin/reflog.c: static int reflog_expire_config(const char *var, const char *v\n       \n       \tif (parse_config_key(var, \"gc\", &pattern, &pattern_len, &key) < 0)\n      -\t\treturn git_default_config(var, value, cb);\n     -+\t\treturn git_default_config(var, value,kvi, cb);\n     ++\t\treturn git_default_config(var, value, ctx, cb);\n       \n       \tif (!strcmp(key, \"reflogexpire\")) {\n       \t\tslot = EXPIRE_TOTAL;\n     @@ builtin/reflog.c: static int reflog_expire_config(const char *var, const char *v\n       \t\t\treturn -1;\n       \t} else\n      -\t\treturn git_default_config(var, value, cb);\n     -+\t\treturn git_default_config(var, value,kvi, cb);\n     ++\t\treturn git_default_config(var, value, ctx, cb);\n       \n       \tif (!pattern) {\n       \t\tswitch (slot) {\n     @@ builtin/remote.c: static const char *abbrev_ref(const char *name, const char *pr\n       #define abbrev_branch(name) abbrev_ref((name), \"refs/heads/\")\n       \n       static int config_read_branches(const char *key, const char *value,\n     -+\t\t\t\tstruct key_value_info *kvi UNUSED,\n     ++\t\t\t\tconst struct config_context *ctx UNUSED,\n       \t\t\t\tvoid *data UNUSED)\n       {\n       \tconst char *orig_key = key;\n     @@ builtin/remote.c: struct push_default_info\n       \n       static int config_read_push_default(const char *key, const char *value,\n      -\tvoid *cb)\n     -+\tstruct key_value_info *kvi UNUSED, void *cb)\n     ++\tconst struct config_context *ctx UNUSED, void *cb)\n       {\n       \tstruct push_default_info* info = cb;\n       \tif (strcmp(key, \"remote.pushdefault\") ||\n     @@ builtin/remote.c: static int prune(int argc, const char **argv, const char *pref\n       \n      -static int get_remote_default(const char *key, const char *value UNUSED, void *priv)\n      +static int get_remote_default(const char *key, const char *value UNUSED,\n     -+\t\t\t      struct key_value_info *kvi UNUSED, void *priv)\n     ++\t\t\t      const struct config_context *ctx UNUSED,\n     ++\t\t\t      void *priv)\n       {\n       \tif (strcmp(key, \"remotes.default\") == 0) {\n       \t\tint *found = priv;\n     @@ builtin/repack.c: struct pack_objects_args {\n       \n      -static int repack_config(const char *var, const char *value, void *cb)\n      +static int repack_config(const char *var, const char *value,\n     -+\t\t\t struct key_value_info *kvi, void *cb)\n     ++\t\t\t const struct config_context *ctx, void *cb)\n       {\n       \tstruct pack_objects_args *cruft_po_args = cb;\n       \tif (!strcmp(var, \"repack.usedeltabaseoffset\")) {\n     @@ builtin/repack.c: static int repack_config(const char *var, const char *value, v\n       \tif (!strcmp(var, \"repack.cruftthreads\"))\n       \t\treturn git_config_string(&cruft_po_args->threads, var, value);\n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       /*\n     @@ builtin/reset.c: static int reset_refs(const char *rev, const struct object_id *\n       \n      -static int git_reset_config(const char *var, const char *value, void *cb)\n      +static int git_reset_config(const char *var, const char *value,\n     -+\t\t\t    struct key_value_info *kvi, void *cb)\n     ++\t\t\t    const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"submodule.recurse\"))\n       \t\treturn git_default_submodule_config(var, value, cb);\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       int cmd_reset(int argc, const char **argv, const char *prefix)\n     @@ builtin/send-pack.c: static void print_helper_status(struct ref *ref)\n       \n      -static int send_pack_config(const char *k, const char *v, void *cb)\n      +static int send_pack_config(const char *k, const char *v,\n     -+\t\t\t    struct key_value_info *kvi, void *cb)\n     ++\t\t\t    const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(k, \"push.gpgsign\")) {\n       \t\tconst char *value;\n     @@ builtin/send-pack.c: static int send_pack_config(const char *k, const char *v, v\n       \t\t}\n       \t}\n      -\treturn git_default_config(k, v, cb);\n     -+\treturn git_default_config(k, v,kvi, cb);\n     ++\treturn git_default_config(k, v, ctx, cb);\n       }\n       \n       int cmd_send_pack(int argc, const char **argv, const char *prefix)\n     @@ builtin/show-branch.c: static void append_one_rev(const char *av)\n       \n      -static int git_show_branch_config(const char *var, const char *value, void *cb)\n      +static int git_show_branch_config(const char *var, const char *value,\n     -+\t\t\t\t  struct key_value_info *kvi, void *cb)\n     ++\t\t\t\t  const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"showbranch.default\")) {\n       \t\tif (!value)\n     @@ builtin/show-branch.c: static int git_show_branch_config(const char *var, const\n       \t\treturn -1;\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static int omit_in_dense(struct commit *commit, struct commit **rev, int n)\n     @@ builtin/stash.c: static int show_stat = 1;\n       \n      -static int git_stash_config(const char *var, const char *value, void *cb)\n      +static int git_stash_config(const char *var, const char *value,\n     -+\t\t\t    struct key_value_info *kvi, void *cb)\n     ++\t\t\t    const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"stash.showstat\")) {\n       \t\tshow_stat = git_config_bool(var, value);\n     @@ builtin/stash.c: static int git_stash_config(const char *var, const char *value,\n       \t\treturn 0;\n       \t}\n      -\treturn git_diff_basic_config(var, value, cb);\n     -+\treturn git_diff_basic_config(var, value,kvi, cb);\n     ++\treturn git_diff_basic_config(var, value, ctx, cb);\n       }\n       \n       static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)\n     @@ builtin/submodule--helper.c: static int update_clone_task_finished(int result,\n       }\n       \n       static int git_update_clone_config(const char *var, const char *value,\n     -+\t\t\t\t   struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t   const struct config_context *ctx UNUSED,\n       \t\t\t\t   void *cb)\n       {\n       \tint *max_jobs = cb;\n     @@ builtin/tag.c: static const char tag_template_nocleanup[] =\n       \n      -static int git_tag_config(const char *var, const char *value, void *cb)\n      +static int git_tag_config(const char *var, const char *value,\n     -+\t\t\t  struct key_value_info *kvi, void *cb)\n     ++\t\t\t  const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"tag.gpgsign\")) {\n       \t\tconfig_sign_tag = git_config_bool(var, value);\n     @@ builtin/tag.c: static int git_tag_config(const char *var, const char *value, voi\n       \t\treturn -1;\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static void write_tag_body(int fd, const struct object_id *oid)\n     @@ builtin/var.c: static const struct git_var *get_git_var(const char *var)\n       \n      -static int show_config(const char *var, const char *value, void *cb)\n      +static int show_config(const char *var, const char *value,\n     -+\t\t       struct key_value_info *kvi, void *cb)\n     ++\t\t       const struct config_context *ctx, void *cb)\n       {\n       \tif (value)\n       \t\tprintf(\"%s=%s\\n\", var, value);\n       \telse\n       \t\tprintf(\"%s\\n\", var);\n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       int cmd_var(int argc, const char **argv, const char *prefix UNUSED)\n     @@ builtin/worktree.c: static int verbose;\n       \n      -static int git_worktree_config(const char *var, const char *value, void *cb)\n      +static int git_worktree_config(const char *var, const char *value,\n     -+\t\t\t       struct key_value_info *kvi, void *cb)\n     ++\t\t\t       const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"worktree.guessremote\")) {\n       \t\tguess_remote = git_config_bool(var, value);\n     @@ builtin/worktree.c: static int verbose;\n       \t}\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static int delete_git_dir(const char *id)\n     @@ bundle-uri.c: static int bundle_list_update(const char *key, const char *value,\n       \n      -static int config_to_bundle_list(const char *key, const char *value, void *data)\n      +static int config_to_bundle_list(const char *key, const char *value,\n     -+\t\t\t\t struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t const struct config_context *ctx UNUSED,\n      +\t\t\t\t void *data)\n       {\n       \tstruct bundle_list *list = data;\n     @@ bundle-uri.c: cached:\n       \n      -static int config_to_packet_line(const char *key, const char *value, void *data)\n      +static int config_to_packet_line(const char *key, const char *value,\n     -+\t\t\t\t struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t const struct config_context *ctx UNUSED,\n      +\t\t\t\t void *data)\n       {\n       \tstruct packet_reader *writer = data;\n       \n      \n     + ## compat/mingw.c ##\n     +@@ compat/mingw.c: static int core_restrict_inherited_handles = -1;\n     + static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;\n     + static char *unset_environment_variables;\n     + \n     +-int mingw_core_config(const char *var, const char *value, void *cb)\n     ++int mingw_core_config(const char *var, const char *value,\n     ++\t\t      const struct config_context *ctx, void *cb)\n     + {\n     + \tif (!strcmp(var, \"core.hidedotfiles\")) {\n     + \t\tif (value && !strcasecmp(value, \"dotgitonly\"))\n     +\n     + ## compat/mingw.h ##\n     +@@ compat/mingw.h: typedef _sigset_t sigset_t;\n     + #undef _POSIX_THREAD_SAFE_FUNCTIONS\n     + #endif\n     + \n     +-int mingw_core_config(const char *var, const char *value, void *cb);\n     ++struct config_context;\n     ++int mingw_core_config(const char *var, const char *value,\n     ++\t\t      const struct config_context *ctx, void *cb);\n     + #define platform_core_config mingw_core_config\n     + \n     + /*\n     +\n       ## config.c ##\n      @@ config.c: struct config_include_data {\n       };\n     @@ config.c: struct config_include_data {\n       \n      -static int git_config_include(const char *var, const char *value, void *data);\n      +static int git_config_include(const char *var, const char *value,\n     -+\t\t\t      struct key_value_info *kvi, void *data);\n     ++\t\t\t      const struct config_context *ctx, void *data);\n       \n       #define MAX_INCLUDE_DEPTH 10\n       static const char include_depth_advice[] = N_(\n     @@ config.c: static int include_by_branch(const char *cond, size_t cond_len)\n       \n      -static int add_remote_url(const char *var, const char *value, void *data)\n      +static int add_remote_url(const char *var, const char *value,\n     -+\t\t\t  struct key_value_info *kvi UNUSED, void *data)\n     ++\t\t\t  const struct config_context *ctx UNUSED, void *data)\n       {\n       \tstruct string_list *remote_urls = data;\n       \tconst char *remote_name;\n     @@ config.c: static void populate_remote_urls(struct config_include_data *inc)\n       }\n       \n       static int forbid_remote_url(const char *var, const char *value UNUSED,\n     -+\t\t\t     struct key_value_info *kvi UNUSED,\n     ++\t\t\t     const struct config_context *ctx UNUSED,\n       \t\t\t     void *data UNUSED)\n       {\n       \tconst char *remote_name;\n     @@ config.c: static int include_condition_is_true(struct config_source *cs,\n       \n      -static int git_config_include(const char *var, const char *value, void *data)\n      +static int git_config_include(const char *var, const char *value,\n     -+\t\t\t      struct key_value_info *kvi UNUSED, void *data)\n     ++\t\t\t      const struct config_context *ctx,\n     ++\t\t\t      void *data)\n       {\n       \tstruct config_include_data *inc = data;\n       \tstruct config_source *cs = inc->config_reader->source;\n     +@@ config.c: static int git_config_include(const char *var, const char *value, void *data)\n     + \t * Pass along all values, including \"include\" directives; this makes it\n     + \t * possible to query information on the includes themselves.\n     + \t */\n     +-\tret = inc->fn(var, value, inc->data);\n     ++\tret = inc->fn(var, value, NULL, inc->data);\n     + \tif (ret < 0)\n     + \t\treturn ret;\n     + \n     +@@ config.c: static int config_parse_pair(const char *key, const char *value,\n     + \tif (git_config_parse_key(key, &canonical_name, NULL))\n     + \t\treturn -1;\n     + \n     +-\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n     ++\tret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;\n     + \tfree(canonical_name);\n     + \treturn ret;\n     + }\n     +@@ config.c: static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n     + \t * accurate line number in error messages.\n     + \t */\n     + \tcs->linenr--;\n     +-\tret = fn(name->buf, value, data);\n     ++\tret = fn(name->buf, value, NULL, data);\n     + \tif (ret >= 0)\n     + \t\tcs->linenr++;\n     + \treturn ret;\n      @@ config.c: int git_config_color(char *dest, const char *var, const char *value)\n       \treturn 0;\n       }\n       \n      -static int git_default_core_config(const char *var, const char *value, void *cb)\n      +static int git_default_core_config(const char *var, const char *value,\n     -+\t\t\t\t   struct key_value_info *kvi, void *cb)\n     ++\t\t\t\t   const struct config_context *ctx, void *cb)\n       {\n       \t/* This needs a better name */\n       \tif (!strcmp(var, \"core.filemode\")) {\n     @@ config.c: static int git_default_core_config(const char *var, const char *value,\n       \n       \t/* Add other config variables here and to Documentation/config.txt. */\n      -\treturn platform_core_config(var, value, cb);\n     -+\treturn platform_core_config(var, value,kvi, cb);\n     ++\treturn platform_core_config(var, value, ctx, cb);\n       }\n       \n       static int git_default_sparse_config(const char *var, const char *value)\n     @@ config.c: static int git_default_mailmap_config(const char *var, const char *val\n       \n      -int git_default_config(const char *var, const char *value, void *cb)\n      +int git_default_config(const char *var, const char *value,\n     -+\t\t       struct key_value_info *kvi, void *cb)\n     ++\t\t       const struct config_context *ctx, void *cb)\n       {\n       \tif (starts_with(var, \"core.\"))\n      -\t\treturn git_default_core_config(var, value, cb);\n     -+\t\treturn git_default_core_config(var, value,kvi, cb);\n     ++\t\treturn git_default_core_config(var, value, ctx, cb);\n       \n       \tif (starts_with(var, \"user.\") ||\n       \t    starts_with(var, \"author.\") ||\n       \t    starts_with(var, \"committer.\"))\n      -\t\treturn git_ident_config(var, value, cb);\n     -+\t\treturn git_ident_config(var, value,kvi, cb);\n     ++\t\treturn git_ident_config(var, value, ctx, cb);\n       \n       \tif (starts_with(var, \"i18n.\"))\n       \t\treturn git_default_i18n_config(var, value);\n     +@@ config.c: static void configset_iter(struct config_reader *reader, struct config_set *set,\n     + \n     + \t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n     + \n     +-\t\tif (fn(entry->key, values->items[value_index].string, data) < 0)\n     ++\t\tif (fn(entry->key, values->items[value_index].string, NULL, data) < 0)\n     + \t\t\tgit_die_config_linenr(entry->key,\n     + \t\t\t\t\t      reader->config_kvi->filename,\n     + \t\t\t\t\t      reader->config_kvi->linenr);\n      @@ config.c: struct configset_add_data {\n       };\n       #define CONFIGSET_ADD_INIT { 0 }\n       \n      -static int config_set_callback(const char *key, const char *value, void *cb)\n      +static int config_set_callback(const char *key, const char *value,\n     -+\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t       const struct config_context *ctx UNUSED,\n     ++\t\t\t       void *cb)\n       {\n       \tstruct configset_add_data *data = cb;\n       \tconfigset_add_value(data->config_reader, data->config_set, key, value);\n     @@ config.c: static int store_aux_event(enum config_event_t type,\n       \n      -static int store_aux(const char *key, const char *value, void *cb)\n      +static int store_aux(const char *key, const char *value,\n     -+\t\t     struct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t     const struct config_context *ctx UNUSED, void *cb)\n       {\n       \tstruct config_store_data *store = cb;\n       \n      \n       ## config.h ##\n     -@@ config.h: struct key_value_info {\n     +@@ config.h: struct config_options {\n     + \t} error_action;\n     + };\n     + \n     ++/* Config source metadata for a given config key-value pair */\n     ++struct key_value_info {\n     ++\tconst char *filename;\n     ++\tint linenr;\n     ++\tenum config_origin_type origin_type;\n     ++\tenum config_scope scope;\n     ++};\n     ++#define KVI_INIT { \\\n     ++\t.filename = NULL, \\\n     ++\t.linenr = -1, \\\n     ++\t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n     ++\t.scope = CONFIG_SCOPE_UNKNOWN, \\\n     ++}\n     ++\n     ++/* Captures additional information that a config callback can use. */\n     ++struct config_context {\n     ++\t/* Config source metadata for key and value. */\n     ++\tconst struct key_value_info *kvi;\n     ++};\n     ++#define CONFIG_CONTEXT_INIT { 0 }\n     ++\n     + /**\n     +- * A config callback function takes three parameters:\n     ++ * A config callback function takes four parameters:\n     +  *\n     +  * - the name of the parsed variable. This is in canonical \"flat\" form: the\n     +  *   section, subsection, and variable segments will be separated by dots,\n     +@@ config.h: struct config_options {\n     +  *   value specified, the value will be NULL (typically this means it\n     +  *   should be interpreted as boolean true).\n     +  *\n     ++ * - the 'config context', that is, additional information about the config\n     ++ *   iteration operation provided by the config machinery. For example, this\n     ++ *   includes information about the config source being parsed (e.g. the\n     ++ *   filename).\n     ++ *\n     +  * - a void pointer passed in by the caller of the config API; this can\n     +  *   contain callback-specific data\n     +  *\n     +  * A config callback should return 0 for success, or -1 if the variable\n     +  * could not be parsed properly.\n        */\n     - typedef int (*config_fn_t)(const char *, const char *, struct key_value_info *, void *);\n     +-typedef int (*config_fn_t)(const char *, const char *, void *);\n     ++typedef int (*config_fn_t)(const char *, const char *,\n     ++\t\t\t   const struct config_context *, void *);\n       \n      -int git_default_config(const char *, const char *, void *);\n     -+int git_default_config(const char *, const char *, struct key_value_info *,\n     -+\t\t       void *);\n     ++int git_default_config(const char *, const char *,\n     ++\t\t       const struct config_context *, void *);\n       \n       /**\n        * Read a specific file in git-config format.\n     +@@ config.h: int git_config_get_expiry(const char *key, const char **output);\n     + /* parse either \"this many days\" integer, or \"5.days.ago\" approxidate */\n     + int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t now);\n     + \n     +-struct key_value_info {\n     +-\tconst char *filename;\n     +-\tint linenr;\n     +-\tenum config_origin_type origin_type;\n     +-\tenum config_scope scope;\n     +-};\n     +-\n     + /**\n     +  * First prints the error message specified by the caller in `err` and then\n     +  * dies printing the line number and the file name of the highest priority\n      \n       ## connect.c ##\n      @@ connect.c: static struct child_process *git_tcp_connect(int fd[2], char *host, int flags)\n     @@ connect.c: static struct child_process *git_tcp_connect(int fd[2], char *host, i\n       \n       static int git_proxy_command_options(const char *var, const char *value,\n      -\t\tvoid *cb)\n     -+\t\tstruct key_value_info *kvi, void *cb)\n     ++\t\tconst struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"core.gitproxy\")) {\n       \t\tconst char *for_pos;\n     @@ connect.c: static int git_proxy_command_options(const char *var, const char *val\n       \t}\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static int git_use_proxy(const char *host)\n      \n     + ## contrib/coccinelle/config_fn_ctx.pending.cocci (new) ##\n     +@@\n     ++@ get_fn @\n     ++identifier fn, R;\n     ++@@\n     ++(\n     ++(\n     ++git_config_from_file\n     ++|\n     ++git_config_from_file_with_options\n     ++|\n     ++git_config_from_mem\n     ++|\n     ++git_config_from_blob_oid\n     ++|\n     ++read_early_config\n     ++|\n     ++read_very_early_config\n     ++|\n     ++config_with_options\n     ++|\n     ++git_config\n     ++|\n     ++git_protected_config\n     ++|\n     ++config_from_gitmodules\n     ++)\n     ++  (fn, ...)\n     ++|\n     ++repo_config(R, fn, ...)\n     ++)\n     ++\n     ++@ extends get_fn @\n     ++identifier C1, C2, D;\n     ++@@\n     ++int fn(const char *C1, const char *C2,\n     +++ const struct config_context *ctx,\n     ++  void *D);\n     ++\n     ++@ extends get_fn @\n     ++@@\n     ++int fn(const char *, const char *,\n     +++ const struct config_context *,\n     ++  void *);\n     ++\n     ++@ extends get_fn @\n     ++// Don't change fns that look like callback fns but aren't\n     ++identifier fn2 != tar_filter_config && != git_diff_heuristic_config &&\n     ++  != git_default_submodule_config && != git_color_config &&\n     ++  != bundle_list_update && != parse_object_filter_config;\n     ++identifier C1, C2, D1, D2, S;\n     ++attribute name UNUSED;\n     ++@@\n     ++int fn(const char *C1, const char *C2,\n     +++ const struct config_context *ctx,\n     ++  void *D1) {\n     ++<+...\n     ++(\n     ++fn2(C1, C2\n     +++ , ctx\n     ++, D2);\n     ++|\n     ++if(fn2(C1, C2\n     +++ , ctx\n     ++, D2) < 0) { ... }\n     ++|\n     ++return fn2(C1, C2\n     +++ , ctx\n     ++, D2);\n     ++|\n     ++S = fn2(C1, C2\n     +++ , ctx\n     ++, D2);\n     ++)\n     ++...+>\n     ++  }\n     ++\n     ++@ extends get_fn@\n     ++identifier C1, C2, D;\n     ++attribute name UNUSED;\n     ++@@\n     ++int fn(const char *C1, const char *C2,\n     +++ const struct config_context *ctx UNUSED,\n     ++  void *D) {...}\n     ++\n     ++\n     ++// The previous rules don't catch all callbacks, especially if they're defined\n     ++// in a separate file from the git_config() call. Fix these manually.\n     ++@@\n     ++identifier C1, C2, D;\n     ++attribute name UNUSED;\n     ++@@\n     ++int\n     ++(\n     ++git_ident_config\n     ++|\n     ++urlmatch_collect_fn\n     ++|\n     ++write_one_config\n     ++|\n     ++forbid_remote_url\n     ++|\n     ++credential_config_callback\n     ++)\n     ++  (const char *C1, const char *C2,\n     +++ const struct config_context *ctx UNUSED,\n     ++  void *D) {...}\n     ++\n     ++@@\n     ++identifier C1, C2, D, D2, S, fn2;\n     ++@@\n     ++int\n     ++(\n     ++http_options\n     ++|\n     ++git_status_config\n     ++|\n     ++git_commit_config\n     ++|\n     ++git_default_core_config\n     ++|\n     ++grep_config\n     ++)\n     ++  (const char *C1, const char *C2,\n     +++ const struct config_context *ctx,\n     ++  void *D) {\n     ++<+...\n     ++(\n     ++fn2(C1, C2\n     +++ , ctx\n     ++, D2);\n     ++|\n     ++if(fn2(C1, C2\n     +++ , ctx\n     ++, D2) < 0) { ... }\n     ++|\n     ++return fn2(C1, C2\n     +++ , ctx\n     ++, D2);\n     ++|\n     ++S = fn2(C1, C2\n     +++ , ctx\n     ++, D2);\n     ++)\n     ++...+>\n     ++  }\n     +\n       ## convert.c ##\n      @@ convert.c: static int apply_filter(const char *path, const char *src, size_t len,\n       \treturn 0;\n     @@ convert.c: static int apply_filter(const char *path, const char *src, size_t len\n       \n      -static int read_convert_config(const char *var, const char *value, void *cb UNUSED)\n      +static int read_convert_config(const char *var, const char *value,\n     -+\t\t\t       struct key_value_info *kvi UNUSED,\n     ++\t\t\t       const struct config_context *ctx UNUSED,\n      +\t\t\t       void *cb UNUSED)\n       {\n       \tconst char *key, *name;\n     @@ credential.c: static int credential_from_potentially_partial_url(struct credenti\n       \t\t\t\t\t\t   const char *url);\n       \n       static int credential_config_callback(const char *var, const char *value,\n     -+\t\t\t\t      struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t      const struct config_context *ctx UNUSED,\n       \t\t\t\t      void *data)\n       {\n       \tstruct credential *c = data;\n     @@ delta-islands.c: static void free_remote_islands(kh_str_t *remote_islands)\n       \n      -static int island_config_callback(const char *k, const char *v, void *cb)\n      +static int island_config_callback(const char *k, const char *v,\n     -+\t\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t\t  const struct config_context *ctx UNUSED,\n     ++\t\t\t\t  void *cb)\n       {\n       \tstruct island_load_data *ild = cb;\n       \n     @@ diff.c: static unsigned parse_color_moved_ws(const char *arg)\n       \n      -int git_diff_ui_config(const char *var, const char *value, void *cb)\n      +int git_diff_ui_config(const char *var, const char *value,\n     -+\t\t       struct key_value_info *kvi, void *cb)\n     ++\t\t       const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"diff.color\") || !strcmp(var, \"color.diff\")) {\n       \t\tdiff_use_color_default = git_config_colorbool(var, value);\n     @@ diff.c: int git_diff_ui_config(const char *var, const char *value, void *cb)\n       \t\treturn -1;\n       \n      -\treturn git_diff_basic_config(var, value, cb);\n     -+\treturn git_diff_basic_config(var, value,kvi, cb);\n     ++\treturn git_diff_basic_config(var, value, ctx, cb);\n       }\n       \n      -int git_diff_basic_config(const char *var, const char *value, void *cb)\n      +int git_diff_basic_config(const char *var, const char *value,\n     -+\t\t\t  struct key_value_info *kvi, void *cb)\n     ++\t\t\t  const struct config_context *ctx, void *cb)\n       {\n       \tconst char *name;\n       \n     @@ diff.c: int git_diff_basic_config(const char *var, const char *value, void *cb)\n       \t\treturn -1;\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static char *quote_two(const char *one, const char *two)\n     @@ diff.h: void free_diffstat_info(struct diffstat_t *diffstat);\n       \t\t   const char **optarg);\n       \n      -int git_diff_basic_config(const char *var, const char *value, void *cb);\n     ++struct config_context;\n      +int git_diff_basic_config(const char *var, const char *value,\n     -+\t\t\t  struct key_value_info *kvi, void *cb);\n     ++\t\t\t  const struct config_context *ctx, void *cb);\n       int git_diff_heuristic_config(const char *var, const char *value, void *cb);\n       void init_diff_ui_defaults(void);\n      -int git_diff_ui_config(const char *var, const char *value, void *cb);\n      +int git_diff_ui_config(const char *var, const char *value,\n     -+\t\t       struct key_value_info *kvi, void *cb);\n     ++\t\t       const struct config_context *ctx, void *cb);\n       void repo_diff_setup(struct repository *, struct diff_options *);\n       struct option *add_diff_options(const struct option *, struct diff_options *);\n       int diff_opt_parse(struct diff_options *, const char **, int, const char *);\n     @@ fetch-pack.c: static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n       \n      -static int fetch_pack_config_cb(const char *var, const char *value, void *cb)\n      +static int fetch_pack_config_cb(const char *var, const char *value,\n     -+\t\t\t\tstruct key_value_info *kvi, void *cb)\n     ++\t\t\t\tconst struct config_context *ctx, void *cb)\n       {\n       \tif (strcmp(var, \"fetch.fsck.skiplist\") == 0) {\n       \t\tconst char *path;\n     @@ fetch-pack.c: static int fetch_pack_config_cb(const char *var, const char *value\n       \t}\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       static void fetch_pack_config(void)\n     @@ fmt-merge-msg.c: static int use_branch_desc;\n       \n      -int fmt_merge_msg_config(const char *key, const char *value, void *cb)\n      +int fmt_merge_msg_config(const char *key, const char *value,\n     -+\t\t\t struct key_value_info *kvi, void *cb)\n     ++\t\t\t const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(key, \"merge.log\") || !strcmp(key, \"merge.summary\")) {\n       \t\tint is_bool;\n     @@ fmt-merge-msg.c: int fmt_merge_msg_config(const char *key, const char *value, vo\n       \t\tsuppress_dest_pattern_seen = 1;\n       \t} else {\n      -\t\treturn git_default_config(key, value, cb);\n     -+\t\treturn git_default_config(key, value,kvi, cb);\n     ++\t\treturn git_default_config(key, value, ctx, cb);\n       \t}\n       \treturn 0;\n       }\n     @@ fmt-merge-msg.h: struct fmt_merge_msg_opts {\n       extern int merge_log_config;\n      -int fmt_merge_msg_config(const char *key, const char *value, void *cb);\n      +int fmt_merge_msg_config(const char *key, const char *value,\n     -+\t\t\t struct key_value_info *kvi, void *cb);\n     ++\t\t\t const struct config_context *ctx, void *cb);\n       int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n       \t\t  struct fmt_merge_msg_opts *);\n       \n     @@ fsck.c: struct fsck_gitmodules_data {\n       \n      -static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)\n      +static int fsck_gitmodules_fn(const char *var, const char *value,\n     -+\t\t\t      struct key_value_info *kvi UNUSED, void *vdata)\n     ++\t\t\t      const struct config_context *ctx UNUSED,\n     ++\t\t\t      void *vdata)\n       {\n       \tstruct fsck_gitmodules_data *data = vdata;\n       \tconst char *subsection, *key;\n     @@ fsck.c: int fsck_finish(struct fsck_options *options)\n       \n      -int git_fsck_config(const char *var, const char *value, void *cb)\n      +int git_fsck_config(const char *var, const char *value,\n     -+\t\t    struct key_value_info *kvi, void *cb)\n     ++\t\t    const struct config_context *ctx, void *cb)\n       {\n       \tstruct fsck_options *options = cb;\n       \tif (strcmp(var, \"fsck.skiplist\") == 0) {\n     @@ fsck.c: int git_fsck_config(const char *var, const char *value, void *cb)\n       \t}\n       \n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n       \n       /*\n      \n       ## fsck.h ##\n     -@@ fsck.h: const char *fsck_describe_object(struct fsck_options *options,\n     +@@ fsck.h: void fsck_put_object_name(struct fsck_options *options,\n     + const char *fsck_describe_object(struct fsck_options *options,\n     + \t\t\t\t const struct object_id *oid);\n     + \n     ++struct key_value_info;\n     + /*\n        * git_config() callback for use by fsck-y tools that want to support\n        * fsck.<msg> fsck.skipList etc.\n        */\n      -int git_fsck_config(const char *var, const char *value, void *cb);\n      +int git_fsck_config(const char *var, const char *value,\n     -+\t\t    struct key_value_info *kvi, void *cb);\n     ++\t\t    const struct config_context *ctx, void *cb);\n       \n       #endif\n      \n     + ## git-compat-util.h ##\n     +@@ git-compat-util.h: typedef uintmax_t timestamp_t;\n     + #endif\n     + \n     + #ifndef platform_core_config\n     ++struct config_context;\n     + static inline int noop_core_config(const char *var UNUSED,\n     + \t\t\t\t   const char *value UNUSED,\n     ++\t\t\t\t   const struct config_context *ctx UNUSED,\n     + \t\t\t\t   void *cb UNUSED)\n     + {\n     + \treturn 0;\n     +\n       ## gpg-interface.c ##\n      @@\n       #include \"alias.h\"\n       #include \"wrapper.h\"\n       \n      -static int git_gpg_config(const char *, const char *, void *);\n     -+static int git_gpg_config(const char *, const char *, struct key_value_info *,\n     -+\t\t\t  void *);\n     ++static int git_gpg_config(const char *, const char *,\n     ++\t\t\t  const struct config_context *, void *);\n       \n       static void gpg_interface_lazy_init(void)\n       {\n     @@ gpg-interface.c: void set_signing_key(const char *key)\n       \n      -static int git_gpg_config(const char *var, const char *value, void *cb UNUSED)\n      +static int git_gpg_config(const char *var, const char *value,\n     -+\t\t\t  struct key_value_info *kvi UNUSED, void *cb UNUSED)\n     ++\t\t\t  const struct config_context *ctx UNUSED,\n     ++\t\t\t  void *cb UNUSED)\n       {\n       \tstruct gpg_format *fmt = NULL;\n       \tchar *fmtname = NULL;\n     @@ grep.c: define_list_config_array_extra(color_grep_slots, {\"match\"});\n        */\n      -int grep_config(const char *var, const char *value, void *cb)\n      +int grep_config(const char *var, const char *value,\n     -+\t\tstruct key_value_info *kvi UNUSED, void *cb)\n     ++\t\tconst struct config_context *ctx, void *cb)\n       {\n       \tstruct grep_opt *opt = cb;\n       \tconst char *slot;\n     +@@ grep.c: int grep_config(const char *var, const char *value, void *cb)\n     + \tif (!strcmp(var, \"color.grep\"))\n     + \t\topt->color = git_config_colorbool(var, value);\n     + \tif (!strcmp(var, \"color.grep.match\")) {\n     +-\t\tif (grep_config(\"color.grep.matchcontext\", value, cb) < 0)\n     ++\t\tif (grep_config(\"color.grep.matchcontext\", value, ctx, cb) < 0)\n     + \t\t\treturn -1;\n     +-\t\tif (grep_config(\"color.grep.matchselected\", value, cb) < 0)\n     ++\t\tif (grep_config(\"color.grep.matchselected\", value, ctx, cb) < 0)\n     + \t\t\treturn -1;\n     + \t} else if (skip_prefix(var, \"color.grep.\", &slot)) {\n     + \t\tint i = LOOKUP_CONFIG(color_grep_slots, slot);\n     +\n     + ## grep.h ##\n     +@@ grep.h: struct grep_opt {\n     + \t.output = std_output, \\\n     + }\n     + \n     +-int grep_config(const char *var, const char *value, void *);\n     ++struct config_context;\n     ++int grep_config(const char *var, const char *value,\n     ++\t\tconst struct config_context *ctx, void *data);\n     + void grep_init(struct grep_opt *, struct repository *repo);\n     + \n     + void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);\n      \n       ## help.c ##\n      @@ help.c: void load_command_list(const char *prefix,\n     @@ help.c: void load_command_list(const char *prefix,\n       \n      -static int get_colopts(const char *var, const char *value, void *data)\n      +static int get_colopts(const char *var, const char *value,\n     -+\t\t       struct key_value_info *kvi UNUSED, void *data)\n     ++\t\t       const struct config_context *ctx UNUSED, void *data)\n       {\n       \tunsigned int *colopts = data;\n       \n     @@ help.c: void list_developer_interfaces_help(void)\n       \n      -static int get_alias(const char *var, const char *value, void *data)\n      +static int get_alias(const char *var, const char *value,\n     -+\t\t     struct key_value_info *kvi UNUSED, void *data)\n     ++\t\t     const struct config_context *ctx UNUSED, void *data)\n       {\n       \tstruct string_list *list = data;\n       \n     @@ help.c: static struct cmdnames aliases;\n       #define AUTOCORRECT_IMMEDIATELY (-1)\n       \n       static int git_unknown_cmd_config(const char *var, const char *value,\n     -+\t\t\t\t  struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t  const struct config_context *ctx UNUSED,\n       \t\t\t\t  void *cb UNUSED)\n       {\n       \tconst char *p;\n      \n     + ## http.c ##\n     +@@ http.c: static void process_curl_messages(void)\n     + \t}\n     + }\n     + \n     +-static int http_options(const char *var, const char *value, void *cb)\n     ++static int http_options(const char *var, const char *value,\n     ++\t\t\tconst struct config_context *ctx, void *data)\n     + {\n     + \tif (!strcmp(\"http.version\", var)) {\n     + \t\treturn git_config_string(&curl_http_version, var, value);\n     +@@ http.c: static int http_options(const char *var, const char *value, void *cb)\n     + \t}\n     + \n     + \t/* Fall back on the default ones */\n     +-\treturn git_default_config(var, value, cb);\n     ++\treturn git_default_config(var, value, ctx, data);\n     + }\n     + \n     + static int curl_empty_auth_enabled(void)\n     +\n       ## ident.c ##\n      @@ ident.c: static int set_ident(const char *var, const char *value)\n       \treturn 0;\n     @@ ident.c: static int set_ident(const char *var, const char *value)\n       \n      -int git_ident_config(const char *var, const char *value, void *data UNUSED)\n      +int git_ident_config(const char *var, const char *value,\n     -+\t\t     struct key_value_info *kvi UNUSED, void *data UNUSED)\n     ++\t\t     const struct config_context *ctx UNUSED,\n     ++\t\t     void *data UNUSED)\n       {\n       \tif (!strcmp(var, \"user.useconfigonly\")) {\n       \t\tident_use_config_only = git_config_bool(var, value);\n     @@ ident.h: const char *fmt_name(enum want_ident);\n       int author_ident_sufficiently_given(void);\n       \n      -int git_ident_config(const char *, const char *, void *);\n     -+int git_ident_config(const char *, const char *,\n     -+\t\t     struct key_value_info *UNUSED, void *);\n     ++struct config_context;\n     ++int git_ident_config(const char *, const char *, const struct config_context *,\n     ++\t\t     void *);\n       \n       #endif\n      \n     @@ imap-send.c: static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, i\n       \n      -static int git_imap_config(const char *var, const char *val, void *cb)\n      +static int git_imap_config(const char *var, const char *val,\n     -+\t\t\t   struct key_value_info *kvi, void *cb)\n     ++\t\t\t   const struct config_context *ctx, void *cb)\n       {\n       \n       \tif (!strcmp(\"imap.sslverify\", var))\n     @@ imap-send.c: static int git_imap_config(const char *var, const char *val, void *\n       \t\t}\n       \t} else\n      -\t\treturn git_default_config(var, val, cb);\n     -+\t\treturn git_default_config(var, val,kvi, cb);\n     ++\t\treturn git_default_config(var, val, ctx, cb);\n       \n       \treturn 0;\n       }\n     @@ ll-merge.c: static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;\n       static const char *default_ll_merge;\n       \n       static int read_merge_config(const char *var, const char *value,\n     -+\t\t\t     struct key_value_info *kvi UNUSED,\n     ++\t\t\t     const struct config_context *ctx UNUSED,\n       \t\t\t     void *cb UNUSED)\n       {\n       \tstruct ll_merge_driver *fn;\n     @@ ls-refs.c: static void send_possibly_unborn_head(struct ls_refs_data *data)\n       }\n       \n       static int ls_refs_config(const char *var, const char *value,\n     --\t\t\t  void *cb_data)\n     -+\t\t\t  struct key_value_info *kvi UNUSED, void *cb_data)\n     ++\t\t\t  const struct config_context *ctx UNUSED,\n     + \t\t\t  void *cb_data)\n       {\n       \tstruct ls_refs_data *data = cb_data;\n     - \t/*\n      \n       ## mailinfo.c ##\n      @@ mailinfo.c: int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)\n     @@ mailinfo.c: int mailinfo_parse_quoted_cr_action(const char *actionstr, int *acti\n       \n      -static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n      +static int git_mailinfo_config(const char *var, const char *value,\n     -+\t\t\t       struct key_value_info *kvi, void *mi_)\n     ++\t\t\t       const struct config_context *ctx, void *mi_)\n       {\n       \tstruct mailinfo *mi = mi_;\n       \n       \tif (!starts_with(var, \"mailinfo.\"))\n      -\t\treturn git_default_config(var, value, NULL);\n     -+\t\treturn git_default_config(var, value,kvi, NULL);\n     ++\t\treturn git_default_config(var, value, ctx, NULL);\n       \tif (!strcmp(var, \"mailinfo.scissors\")) {\n       \t\tmi->use_scissors = git_config_bool(var, value);\n       \t\treturn 0;\n     @@ notes-utils.c: static combine_notes_fn parse_combine_notes_fn(const char *v)\n       \n      -static int notes_rewrite_config(const char *k, const char *v, void *cb)\n      +static int notes_rewrite_config(const char *k, const char *v,\n     -+\t\t\t\tstruct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t\tconst struct config_context *ctx UNUSED,\n     ++\t\t\t\tvoid *cb)\n       {\n       \tstruct notes_rewrite_cfg *c = cb;\n       \tif (starts_with(k, \"notes.rewrite.\") && !strcmp(k+14, c->cmd)) {\n     @@ notes.c: void string_list_add_refs_from_colon_sep(struct string_list *list,\n       \n      -static int notes_display_config(const char *k, const char *v, void *cb)\n      +static int notes_display_config(const char *k, const char *v,\n     -+\t\t\t\tstruct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t\tconst struct config_context *ctx UNUSED,\n     ++\t\t\t\tvoid *cb)\n       {\n       \tint *load_refs = cb;\n       \n     @@ pager.c: static void wait_for_pager_signal(int signo)\n       }\n       \n       static int core_pager_config(const char *var, const char *value,\n     -+\t\t\t     struct key_value_info *kvi UNUSED,\n     ++\t\t\t     const struct config_context *ctx UNUSED,\n       \t\t\t     void *data UNUSED)\n       {\n       \tif (!strcmp(var, \"core.pager\"))\n     @@ pager.c: struct pager_command_config_data {\n       \n      -static int pager_command_config(const char *var, const char *value, void *vdata)\n      +static int pager_command_config(const char *var, const char *value,\n     -+\t\t\t\tstruct key_value_info *kvi UNUSED,\n     ++\t\t\t\tconst struct config_context *ctx UNUSED,\n      +\t\t\t\tvoid *vdata)\n       {\n       \tstruct pager_command_config_data *data = vdata;\n     @@ pretty.c: static void save_user_format(struct rev_info *rev, const char *cp, int\n       }\n       \n       static int git_pretty_formats_config(const char *var, const char *value,\n     -+\t\t\t\t     struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t     const struct config_context *ctx UNUSED,\n       \t\t\t\t     void *cb UNUSED)\n       {\n       \tstruct cmt_fmt_map *commit_format = NULL;\n     @@ promisor-remote.c: static void promisor_remote_move_to_tail(struct promisor_remo\n       \n      -static int promisor_remote_config(const char *var, const char *value, void *data)\n      +static int promisor_remote_config(const char *var, const char *value,\n     -+\t\t\t\t  struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t  const struct config_context *ctx UNUSED,\n      +\t\t\t\t  void *data)\n       {\n       \tstruct promisor_remote_config *config = data;\n     @@ remote.c: static void read_branches_file(struct remote_state *remote_state,\n       \n      -static int handle_config(const char *key, const char *value, void *cb)\n      +static int handle_config(const char *key, const char *value,\n     -+\t\t\t struct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t const struct config_context *ctx UNUSED, void *cb)\n       {\n       \tconst char *name;\n       \tsize_t namelen;\n     @@ revision.c: struct exclude_hidden_refs_cb {\n       \n      -static int hide_refs_config(const char *var, const char *value, void *cb_data)\n      +static int hide_refs_config(const char *var, const char *value,\n     -+\t\t\t    struct key_value_info *kvi UNUSED, void *cb_data)\n     ++\t\t\t    const struct config_context *ctx UNUSED,\n     ++\t\t\t    void *cb_data)\n       {\n       \tstruct exclude_hidden_refs_cb *cb = cb_data;\n       \tcb->exclusions->hidden_refs_configured = 1;\n     @@ scalar.c: static int cmd_register(int argc, const char **argv)\n       \n      -static int get_scalar_repos(const char *key, const char *value, void *data)\n      +static int get_scalar_repos(const char *key, const char *value,\n     -+\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n     ++\t\t\t    const struct config_context *ctx UNUSED,\n     ++\t\t\t    void *data)\n       {\n       \tstruct string_list *list = data;\n       \n     @@ sequencer.c: static struct update_ref_record *init_update_ref_record(const char\n       \n      -static int git_sequencer_config(const char *k, const char *v, void *cb)\n      +static int git_sequencer_config(const char *k, const char *v,\n     -+\t\t\t\tstruct key_value_info *kvi, void *cb)\n     ++\t\t\t\tconst struct config_context *ctx, void *cb)\n       {\n       \tstruct replay_opts *opts = cb;\n       \tint status;\n     @@ sequencer.c: static int git_sequencer_config(const char *k, const char *v, void\n       \t\topts->commit_use_reference = git_config_bool(k, v);\n       \n      -\treturn git_diff_basic_config(k, v, NULL);\n     -+\treturn git_diff_basic_config(k, v,kvi, NULL);\n     ++\treturn git_diff_basic_config(k, v, ctx, NULL);\n       }\n       \n       void sequencer_init_config(struct replay_opts *opts)\n     @@ sequencer.c: static int git_config_string_dup(char **dest,\n       \n      -static int populate_opts_cb(const char *key, const char *value, void *data)\n      +static int populate_opts_cb(const char *key, const char *value,\n     -+\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n     ++\t\t\t    const struct config_context *ctx UNUSED,\n     ++\t\t\t    void *data)\n       {\n       \tstruct replay_opts *opts = data;\n       \tint error_flag = 1;\n     @@ setup.c: no_prevention_needed:\n       \n      -static int read_worktree_config(const char *var, const char *value, void *vdata)\n      +static int read_worktree_config(const char *var, const char *value,\n     -+\t\t\t\tstruct key_value_info *kvi UNUSED,\n     ++\t\t\t\tconst struct config_context *ctx UNUSED,\n      +\t\t\t\tvoid *vdata)\n       {\n       \tstruct repository_format *data = vdata;\n     @@ setup.c: static enum extension_result handle_extension(const char *var,\n       \n      -static int check_repo_format(const char *var, const char *value, void *vdata)\n      +static int check_repo_format(const char *var, const char *value,\n     -+\t\t\t     struct key_value_info *kvi, void *vdata)\n     ++\t\t\t     const struct config_context *ctx, void *vdata)\n       {\n       \tstruct repository_format *data = vdata;\n       \tconst char *ext;\n     @@ setup.c: static int check_repo_format(const char *var, const char *value, void *\n       \t}\n       \n      -\treturn read_worktree_config(var, value, vdata);\n     -+\treturn read_worktree_config(var, value,kvi, vdata);\n     ++\treturn read_worktree_config(var, value, ctx, vdata);\n       }\n       \n       static int check_repository_format_gently(const char *gitdir, struct repository_format *candidate, int *nongit_ok)\n     @@ setup.c: struct safe_directory_data {\n       \n      -static int safe_directory_cb(const char *key, const char *value, void *d)\n      +static int safe_directory_cb(const char *key, const char *value,\n     -+\t\t\t     struct key_value_info *kvi UNUSED, void *d)\n     ++\t\t\t     const struct config_context *ctx UNUSED, void *d)\n       {\n       \tstruct safe_directory_data *data = d;\n       \n     @@ setup.c: static int ensure_valid_ownership(const char *gitfile,\n       \n      -static int allowed_bare_repo_cb(const char *key, const char *value, void *d)\n      +static int allowed_bare_repo_cb(const char *key, const char *value,\n     -+\t\t\t\tstruct key_value_info *kvi UNUSED, void *d)\n     ++\t\t\t\tconst struct config_context *ctx UNUSED,\n     ++\t\t\t\tvoid *d)\n       {\n       \tenum allowed_bare_repo *allowed_bare_repo = d;\n       \n     @@ submodule-config.c: struct parse_config_parameter {\n        */\n      -static int parse_config(const char *var, const char *value, void *data)\n      +static int parse_config(const char *var, const char *value,\n     -+\t\t\tstruct key_value_info *kvi UNUSED, void *data)\n     ++\t\t\tconst struct config_context *ctx UNUSED, void *data)\n       {\n       \tstruct parse_config_parameter *me = data;\n       \tstruct submodule *submodule;\n     @@ submodule-config.c: out:\n       \n      -static int gitmodules_cb(const char *var, const char *value, void *data)\n      +static int gitmodules_cb(const char *var, const char *value,\n     -+\t\t\t struct key_value_info *kvi UNUSED, void *data)\n     ++\t\t\t const struct config_context *ctx, void *data)\n       {\n       \tstruct repository *repo = data;\n       \tstruct parse_config_parameter parameter;\n     +@@ submodule-config.c: static int gitmodules_cb(const char *var, const char *value, void *data)\n     + \tparameter.gitmodules_oid = null_oid();\n     + \tparameter.overwrite = 1;\n     + \n     +-\treturn parse_config(var, value, &parameter);\n     ++\treturn parse_config(var, value, ctx, &parameter);\n     + }\n     + \n     + void repo_read_gitmodules(struct repository *repo, int skip_if_read)\n      @@ submodule-config.c: void submodule_free(struct repository *r)\n       \t\tsubmodule_cache_clear(r->submodule_cache);\n       }\n       \n      -static int config_print_callback(const char *var, const char *value, void *cb_data)\n      +static int config_print_callback(const char *var, const char *value,\n     -+\t\t\t\t struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t const struct config_context *ctx UNUSED,\n      +\t\t\t\t void *cb_data)\n       {\n       \tchar *wanted_key = cb_data;\n     @@ submodule-config.c: struct fetch_config {\n       \n      -static int gitmodules_fetch_config(const char *var, const char *value, void *cb)\n      +static int gitmodules_fetch_config(const char *var, const char *value,\n     -+\t\t\t\t   struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t   const struct config_context *ctx UNUSED,\n      +\t\t\t\t   void *cb)\n       {\n       \tstruct fetch_config *config = cb;\n     @@ submodule-config.c: void fetch_config_from_gitmodules(int *max_children, int *re\n       }\n       \n       static int gitmodules_update_clone_config(const char *var, const char *value,\n     -+\t\t\t\t\t  struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t\t  const struct config_context *ctx UNUSED,\n       \t\t\t\t\t  void *cb)\n       {\n       \tint *max_jobs = cb;\n     @@ t/helper/test-config.c\n       \n      -static int iterate_cb(const char *var, const char *value, void *data UNUSED)\n      +static int iterate_cb(const char *var, const char *value,\n     -+\t\t      struct key_value_info *kvi UNUSED, void *data UNUSED)\n     ++\t\t      const struct config_context *ctx UNUSED,\n     ++\t\t      void *data UNUSED)\n       {\n       \tstatic int nr;\n       \n     @@ t/helper/test-config.c: static int iterate_cb(const char *var, const char *value\n       \n      -static int parse_int_cb(const char *var, const char *value, void *data)\n      +static int parse_int_cb(const char *var, const char *value,\n     -+\t\t\tstruct key_value_info *kvi UNUSED, void *data)\n     ++\t\t\tconst struct config_context *ctx UNUSED, void *data)\n       {\n       \tconst char *key_to_match = data;\n       \n     @@ t/helper/test-config.c: static int parse_int_cb(const char *var, const char *val\n       \n      -static int early_config_cb(const char *var, const char *value, void *vdata)\n      +static int early_config_cb(const char *var, const char *value,\n     -+\t\t\t   struct key_value_info *kvi UNUSED, void *vdata)\n     ++\t\t\t   const struct config_context *ctx UNUSED,\n     ++\t\t\t   void *vdata)\n       {\n       \tconst char *key = vdata;\n       \n     @@ t/helper/test-userdiff.c: static int driver_cb(struct userdiff_driver *driver,\n       \n      -static int cmd__userdiff_config(const char *var, const char *value, void *cb UNUSED)\n      +static int cmd__userdiff_config(const char *var, const char *value,\n     -+\t\t\t\tstruct key_value_info *kvi UNUSED,\n     ++\t\t\t\tconst struct config_context *ctx UNUSED,\n      +\t\t\t\tvoid *cb UNUSED)\n       {\n       \tif (userdiff_config(var, value) < 0)\n     @@ trace2/tr2_cfg.c: struct tr2_cfg_data {\n        */\n      -static int tr2_cfg_cb(const char *key, const char *value, void *d)\n      +static int tr2_cfg_cb(const char *key, const char *value,\n     -+\t\t      struct key_value_info *kvi UNUSED, void *d)\n     ++\t\t      const struct config_context *ctx UNUSED, void *d)\n       {\n       \tstruct strbuf **s;\n       \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n     +@@ trace2/tr2_cfg.c: void tr2_list_env_vars_fl(const char *file, int line)\n     + void tr2_cfg_set_fl(const char *file, int line, const char *key,\n     + \t\t    const char *value)\n     + {\n     ++\tstruct key_value_info kvi = KVI_INIT;\n     ++\tstruct config_context ctx = {\n     ++\t\t.kvi = &kvi,\n     ++\t};\n     + \tstruct tr2_cfg_data data = { file, line };\n     + \n     + \tif (tr2_cfg_load_patterns() > 0)\n     +-\t\ttr2_cfg_cb(key, value, &data);\n     ++\t\ttr2_cfg_cb(key, value, &ctx, &data);\n     + }\n      \n       ## trace2/tr2_sysenv.c ##\n      @@ trace2/tr2_sysenv.c: static struct tr2_sysenv_entry tr2_sysenv_settings[] = {\n     @@ trace2/tr2_sysenv.c: static struct tr2_sysenv_entry tr2_sysenv_settings[] = {\n       \n      -static int tr2_sysenv_cb(const char *key, const char *value, void *d)\n      +static int tr2_sysenv_cb(const char *key, const char *value,\n     -+\t\t\t struct key_value_info *kvi UNUSED, void *d)\n     ++\t\t\t const struct config_context *ctx UNUSED, void *d)\n       {\n       \tint k;\n       \n     @@ trailer.c: static struct {\n       };\n       \n       static int git_trailer_default_config(const char *conf_key, const char *value,\n     -+\t\t\t\t      struct key_value_info *kvi UNUSED,\n     ++\t\t\t\t      const struct config_context *ctx UNUSED,\n       \t\t\t\t      void *cb UNUSED)\n       {\n       \tconst char *trailer_item, *variable_name;\n     @@ trailer.c: static int git_trailer_default_config(const char *conf_key, const cha\n       }\n       \n       static int git_trailer_config(const char *conf_key, const char *value,\n     -+\t\t\t      struct key_value_info *kvi UNUSED,\n     ++\t\t\t      const struct config_context *ctx UNUSED,\n       \t\t\t      void *cb UNUSED)\n       {\n       \tconst char *trailer_item, *variable_name;\n     @@ upload-pack.c: static int parse_object_filter_config(const char *var, const char\n       \n      -static int upload_pack_config(const char *var, const char *value, void *cb_data)\n      +static int upload_pack_config(const char *var, const char *value,\n     -+\t\t\t      struct key_value_info *kvi UNUSED,\n     ++\t\t\t      const struct config_context *ctx UNUSED,\n      +\t\t\t      void *cb_data)\n       {\n       \tstruct upload_pack_data *data = cb_data;\n     @@ upload-pack.c: static int upload_pack_config(const char *var, const char *value,\n       \n      -static int upload_pack_protected_config(const char *var, const char *value, void *cb_data)\n      +static int upload_pack_protected_config(const char *var, const char *value,\n     -+\t\t\t\t\tstruct key_value_info *kvi UNUSED,\n     ++\t\t\t\t\tconst struct config_context *ctx UNUSED,\n      +\t\t\t\t\tvoid *cb_data)\n       {\n       \tstruct upload_pack_data *data = cb_data;\n     @@ urlmatch.c: static int cmp_matches(const struct urlmatch_item *a,\n       \n      -int urlmatch_config_entry(const char *var, const char *value, void *cb)\n      +int urlmatch_config_entry(const char *var, const char *value,\n     -+\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n     ++\t\t\t  const struct config_context *ctx, void *cb)\n       {\n       \tstruct string_list_item *item;\n       \tstruct urlmatch_config *collect = cb;\n     +@@ urlmatch.c: int urlmatch_config_entry(const char *var, const char *value, void *cb)\n     + \n     + \tif (!skip_prefix(var, collect->section, &key) || *(key++) != '.') {\n     + \t\tif (collect->cascade_fn)\n     +-\t\t\treturn collect->cascade_fn(var, value, cb);\n     ++\t\t\treturn collect->cascade_fn(var, value, ctx, cb);\n     + \t\treturn 0; /* not interested */\n     + \t}\n     + \tdot = strrchr(key, '.');\n     +@@ urlmatch.c: int urlmatch_config_entry(const char *var, const char *value, void *cb)\n     + \tstrbuf_addstr(&synthkey, collect->section);\n     + \tstrbuf_addch(&synthkey, '.');\n     + \tstrbuf_addstr(&synthkey, key);\n     +-\tretval = collect->collect_fn(synthkey.buf, value, collect->cb);\n     ++\tretval = collect->collect_fn(synthkey.buf, value, ctx, collect->cb);\n     + \n     + \tstrbuf_release(&synthkey);\n     + \treturn retval;\n      \n       ## urlmatch.h ##\n      @@ urlmatch.h: struct urlmatch_config {\n     @@ urlmatch.h: struct urlmatch_config {\n       \n      -int urlmatch_config_entry(const char *var, const char *value, void *cb);\n      +int urlmatch_config_entry(const char *var, const char *value,\n     -+\t\t\t  struct key_value_info *kvi, void *cb);\n     ++\t\t\t  const struct config_context *ctx, void *cb);\n       void urlmatch_config_release(struct urlmatch_config *config);\n       \n       #endif /* URL_MATCH_H */\n     @@ xdiff-interface.c: int xdiff_compare_lines(const char *l1, long s1,\n       \n      -int git_xmerge_config(const char *var, const char *value, void *cb)\n      +int git_xmerge_config(const char *var, const char *value,\n     -+\t\t      struct key_value_info *kvi, void *cb)\n     ++\t\t      const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"merge.conflictstyle\")) {\n       \t\tif (!value)\n     @@ xdiff-interface.c: int git_xmerge_config(const char *var, const char *value, voi\n       \t\treturn 0;\n       \t}\n      -\treturn git_default_config(var, value, cb);\n     -+\treturn git_default_config(var, value,kvi, cb);\n     ++\treturn git_default_config(var, value, ctx, cb);\n       }\n      \n       ## xdiff-interface.h ##\n     @@ xdiff-interface.h: int buffer_is_binary(const char *ptr, unsigned long size);\n       void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n       void xdiff_clear_find_func(xdemitconf_t *xecfg);\n      -int git_xmerge_config(const char *var, const char *value, void *cb);\n     ++struct config_context;\n      +int git_xmerge_config(const char *var, const char *value,\n     -+\t\t      struct key_value_info *kvi, void *cb);\n     ++\t\t      const struct config_context *ctx, void *cb);\n       extern int git_xmerge_style;\n       \n       /*\n  5:  f363b160259 <  -:  ----------- (RFC-only) config: finish config_fn_t refactor\n  6:  f57c1007cad !  4:  c5051ddc10d config.c: pass kvi in configsets\n     @@ Metadata\n      Author: Glen Choo <chooglen@google.com>\n      \n       ## Commit message ##\n     -    config.c: pass kvi in configsets\n     +    config.c: pass ctx in configsets\n      \n     -    Trivially pass \"struct key_value_info\" to config callbacks in\n     -    configset_iter(). Then, in config callbacks that are only used with\n     -    configsets, use the \"kvi\" arg to replace calls to current_config_*(),\n     -    and delete current_config_line() because it has no remaining callers.\n     +    Pass config_context to config callbacks in configset_iter(), trivially\n     +    setting the .kvi member to the cached key_value_info. Then, in config\n     +    callbacks that are only used with configsets, use the .kvi member to\n     +    replace calls to current_config_*(), and delete current_config_line()\n     +    because it has no remaining callers.\n      \n          This leaves builtin/config.c and config.c as the only remaining users of\n          current_config_*().\n     @@ builtin/remote.c: struct push_default_info\n       };\n       \n       static int config_read_push_default(const char *key, const char *value,\n     --\tstruct key_value_info *kvi UNUSED, void *cb)\n     -+\tstruct key_value_info *kvi, void *cb)\n     +-\tconst struct config_context *ctx UNUSED, void *cb)\n     ++\tconst struct config_context *ctx, void *cb)\n       {\n     ++\tconst struct key_value_info *kvi = ctx->kvi;\n     ++\n       \tstruct push_default_info* info = cb;\n       \tif (strcmp(key, \"remote.pushdefault\") ||\n       \t    !value || strcmp(value, info->old_name))\n     @@ config.c: static void configset_iter(struct config_reader *reader, struct config\n       \tstruct string_list *values;\n       \tstruct config_set_element *entry;\n       \tstruct configset_list *list = &set->list;\n     -+\tstruct key_value_info *kvi;\n     ++\tstruct config_context ctx = CONFIG_CONTEXT_INIT;\n       \n       \tfor (i = 0; i < list->nr; i++) {\n       \t\tentry = list->items[i].e;\n     - \t\tvalue_index = list->items[i].value_index;\n     +@@ config.c: static void configset_iter(struct config_reader *reader, struct config_set *set,\n       \t\tvalues = &entry->value_list;\n     -+\t\tkvi = values->items[value_index].util;\n       \n       \t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n     - \n     +-\n      -\t\tif (fn(entry->key, values->items[value_index].string, NULL, data) < 0)\n     --\t\t\tgit_die_config_linenr(entry->key,\n     ++\t\tctx.kvi = values->items[value_index].util;\n     ++\t\tif (fn(entry->key, values->items[value_index].string, &ctx, data) < 0)\n     + \t\t\tgit_die_config_linenr(entry->key,\n      -\t\t\t\t\t      reader->config_kvi->filename,\n      -\t\t\t\t\t      reader->config_kvi->linenr);\n      -\n     -+\t\tif (fn(entry->key, values->items[value_index].string, kvi, data) < 0)\n     -+\t\t\tgit_die_config_linenr(entry->key, kvi->filename, kvi->linenr);\n     ++\t\t\t\t\t      ctx.kvi->filename,\n     ++\t\t\t\t\t      ctx.kvi->linenr);\n       \t\tconfig_reader_set_kvi(reader, NULL);\n       \t}\n       }\n     @@ remote.c: static void read_branches_file(struct remote_state *remote_state,\n       }\n       \n       static int handle_config(const char *key, const char *value,\n     --\t\t\t struct key_value_info *kvi UNUSED, void *cb)\n     -+\t\t\t struct key_value_info *kvi, void *cb)\n     +-\t\t\t const struct config_context *ctx UNUSED, void *cb)\n     ++\t\t\t const struct config_context *ctx, void *cb)\n       {\n       \tconst char *name;\n       \tsize_t namelen;\n     +@@ remote.c: static int handle_config(const char *key, const char *value,\n     + \tstruct remote *remote;\n     + \tstruct branch *branch;\n     + \tstruct remote_state *remote_state = cb;\n     ++\tconst struct key_value_info *kvi = ctx->kvi;\n     + \n     + \tif (parse_config_key(key, \"branch\", &name, &namelen, &subkey) >= 0) {\n     + \t\t/* There is no subsection. */\n      @@ remote.c: static int handle_config(const char *key, const char *value,\n       \t}\n       \tremote = make_remote(remote_state, name, namelen);\n     @@ t/helper/test-config.c\n        */\n       \n       static int iterate_cb(const char *var, const char *value,\n     --\t\t      struct key_value_info *kvi UNUSED, void *data UNUSED)\n     -+\t\t      struct key_value_info *kvi, void *data UNUSED)\n     +-\t\t      const struct config_context *ctx UNUSED,\n     ++\t\t      const struct config_context *ctx,\n     + \t\t      void *data UNUSED)\n       {\n     ++\tconst struct key_value_info *kvi = ctx->kvi;\n       \tstatic int nr;\n       \n     + \tif (nr++)\n      @@ t/helper/test-config.c: static int iterate_cb(const char *var, const char *value,\n       \n       \tprintf(\"key=%s\\n\", var);\n  7:  641a56f0b40 !  5:  595e7d2e163 config: provide kvi with config files\n     @@ Metadata\n      Author: Glen Choo <chooglen@google.com>\n      \n       ## Commit message ##\n     -    config: provide kvi with config files\n     +    config: pass ctx with config files\n      \n     -    Refactor out the configset logic that caches \"struct config_source\" and\n     -    \"enum config_scope\" as a \"struct key_value_info\", and use it to pass the\n     -    \"kvi\" arg to config callbacks when parsing config files. Get the \"enum\n     -    config_scope\" value by plumbing an additional arg through\n     -    git_config_from_file_with_options() and the underlying machinery.\n     +    Pass config_context to config_callbacks when parsing config files. To\n     +    provide the .kvi member, refactor out the configset logic that caches\n     +    \"struct config_source\" and \"enum config_scope\" as a \"struct\n     +    key_value_info\". Make the \"enum config_scope\" available to the config\n     +    file machinery by plumbing an additional arg through\n     +    git_config_from_file_with_options().\n      \n     -    We do not exercise the \"kvi\" arg yet because the remaining\n     -    current_config_*() callers may be used with config_with_options(), which\n     -    may read config from parameters, but parameters don't pass \"kvi\" yet.\n     +    We do not exercise ctx yet because the remaining current_config_*()\n     +    callers may be used with config_with_options(), which may read config\n     +    from parameters, but parameters don't pass ctx yet.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n     @@ config.c: static int handle_path_include(struct config_source *cs, const char *p\n       \t\tinc->depth--;\n       \t}\n       cleanup:\n     -@@ config.c: static int include_condition_is_true(struct config_source *cs,\n     - }\n     - \n     - static int git_config_include(const char *var, const char *value,\n     --\t\t\t      struct key_value_info *kvi UNUSED, void *data)\n     -+\t\t\t      struct key_value_info *kvi, void *data)\n     - {\n     - \tstruct config_include_data *inc = data;\n     - \tstruct config_source *cs = inc->config_reader->source;\n      @@ config.c: static int git_config_include(const char *var, const char *value,\n       \t * Pass along all values, including \"include\" directives; this makes it\n       \t * possible to query information on the includes themselves.\n       \t */\n      -\tret = inc->fn(var, value, NULL, inc->data);\n     -+\tret = inc->fn(var, value, kvi, inc->data);\n     ++\tret = inc->fn(var, value, ctx, inc->data);\n       \tif (ret < 0)\n       \t\treturn ret;\n       \n     @@ config.c: static char *parse_value(struct config_source *cs)\n       {\n       \tint c;\n       \tchar *value;\n     + \tint ret;\n     ++\tstruct config_context ctx = {\n     ++\t\t.kvi = kvi,\n     ++\t};\n     + \n     + \t/* Get the full name */\n     + \tfor (;;) {\n      @@ config.c: static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n       \t * accurate line number in error messages.\n       \t */\n       \tcs->linenr--;\n      -\tret = fn(name->buf, value, NULL, data);\n      +\tkvi->linenr = cs->linenr;\n     -+\tret = fn(name->buf, value, kvi, data);\n     ++\tret = fn(name->buf, value, &ctx, data);\n       \tif (ret >= 0)\n       \t\tcs->linenr++;\n       \treturn ret;\n     @@ config.c: int git_default_config(const char *var, const char *value,\n      +\t\t\t  void *data, enum config_scope scope,\n       \t\t\t  const struct config_options *opts)\n       {\n     -+\tstruct key_value_info kvi = { 0 };\n     ++\tstruct key_value_info kvi = KVI_INIT;\n       \tint ret;\n       \n       \t/* push config-file parsing state stack */\n     @@ config.c: int git_config_set_multivar_in_file_gently(const char *config_filename\n       \t\t\tgoto out_free;\n      \n       ## config.h ##\n     -@@ config.h: int git_default_config(const char *, const char *, struct key_value_info *,\n     +@@ config.h: int git_default_config(const char *, const char *,\n       int git_config_from_file(config_fn_t fn, const char *, void *);\n       \n       int git_config_from_file_with_options(config_fn_t fn, const char *,\n  8:  74f43fc727e =  6:  a2a891a069f builtin/config.c: test misuse of format_config()\n  9:  3760015d2c0 !  7:  1fb1708bbd9 config.c: provide kvi with CLI config\n     @@ Metadata\n      Author: Glen Choo <chooglen@google.com>\n      \n       ## Commit message ##\n     -    config.c: provide kvi with CLI config\n     +    config.c: pass ctx with CLI config\n      \n     -    Refactor out kvi_from_param() from the logic that caches CLI config in\n     -    configsets, and use it to pass the \"kvi\" arg to config callbacks when\n     -    parsing CLI config. Now that \"kvi\" is always present when config\n     -    machinery calls config callbacks, plumb \"kvi\" so that we can replace\n     -    nearly all calls to current_config_*(). (The exception is an edge case\n     -    where trace2/*.c calls current_config_scope(). That will be handled in a\n     -    later commit.) Note that this results in \"kvi\" containing a different,\n     -    more complete set of information than the mocked up \"struct\n     -    config_source\" in git_config_from_parameters().\n     +    Pass config_context when parsing CLI config. To provide the .kvi member,\n     +    refactor out kvi_from_param() from the logic that caches CLI config in\n     +    configsets. Now that config_context and config_context.kvi is always\n     +    present when config machinery calls config callbacks, plumb \"kvi\" so\n     +    that we can remove all calls of current_config_scope() except for\n     +    trace2/*.c (which will be handled in a later commit), and remove all\n     +    other current_config_*() (the functions themselves and their calls).\n     +    Note that this results in .kvi containing a different, more complete\n     +    set of information than the mocked up \"struct config_source\" in\n     +    git_config_from_parameters().\n      \n          Plumbing \"kvi\" reveals a few places where we've been doing the wrong\n          thing:\n     @@ Commit message\n      \n          * \"git config --get-urlmatch --show-scope\" iterates config to collect\n            values, but then attempts to display the scope after config iteration.\n     -      Fix this by copying the \"kvi\" arg in the collection phase so that it\n     +      Fix this by copying the \"kvi\" value in the collection phase so that it\n            can be read back later. This means that we can now support \"git config\n            --get-urlmatch --show-origin\" (we don't allow this combination of args\n            because of this bug), but that is left unchanged for now.\n     @@ builtin/config.c: static void check_argc(int argc, int min, int max)\n       }\n       \n      -static void show_config_origin(struct strbuf *buf)\n     -+static void show_config_origin(struct key_value_info *kvi, struct strbuf *buf)\n     ++static void show_config_origin(const struct key_value_info *kvi,\n     ++\t\t\t       struct strbuf *buf)\n       {\n       \tconst char term = end_nul ? '\\0' : '\\t';\n       \n     @@ builtin/config.c: static void check_argc(int argc, int min, int max)\n       }\n       \n      -static void show_config_scope(struct strbuf *buf)\n     -+static void show_config_scope(struct key_value_info *kvi, struct strbuf *buf)\n     ++static void show_config_scope(const struct key_value_info *kvi,\n     ++\t\t\t      struct strbuf *buf)\n       {\n       \tconst char term = end_nul ? '\\0' : '\\t';\n      -\tconst char *scope = config_scope_name(current_config_scope());\n     @@ builtin/config.c: static void check_argc(int argc, int min, int max)\n       }\n       \n       static int show_all_config(const char *key_, const char *value_,\n     --\t\t\t   struct key_value_info *kvi UNUSED, void *cb UNUSED)\n     -+\t\t\t   struct key_value_info *kvi, void *cb UNUSED)\n     +-\t\t\t   const struct config_context *ctx UNUSED,\n     ++\t\t\t   const struct config_context *ctx,\n     + \t\t\t   void *cb UNUSED)\n       {\n     ++\tconst struct key_value_info *kvi = ctx->kvi;\n     ++\n       \tif (show_origin || show_scope) {\n       \t\tstruct strbuf buf = STRBUF_INIT;\n       \t\tif (show_scope)\n     @@ builtin/config.c: struct strbuf_list {\n       \n      -static int format_config(struct strbuf *buf, const char *key_, const char *value_)\n      +static int format_config(struct strbuf *buf, const char *key_,\n     -+\t\t\t const char *value_, struct key_value_info *kvi)\n     ++\t\t\t const char *value_, const struct key_value_info *kvi)\n       {\n       \tif (show_scope)\n      -\t\tshow_config_scope(buf);\n     @@ builtin/config.c: static int format_config(struct strbuf *buf, const char *key_,\n       }\n       \n       static int collect_config(const char *key_, const char *value_,\n     --\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n     -+\t\t\t  struct key_value_info *kvi, void *cb)\n     +-\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n     ++\t\t\t  const struct config_context *ctx, void *cb)\n       {\n       \tstruct strbuf_list *values = cb;\n     ++\tconst struct key_value_info *kvi = ctx->kvi;\n       \n     + \tif (!use_key_regexp && strcmp(key_, key))\n     + \t\treturn 0;\n      @@ builtin/config.c: static int collect_config(const char *key_, const char *value_,\n       \tALLOC_GROW(values->items, values->nr + 1, values->alloc);\n       \tstrbuf_init(&values->items[values->nr], 0);\n     @@ builtin/config.c: static int get_value(const char *key_, const char *regex_, uns\n       \t\t\t    &given_config_source, &config_options);\n       \n       \tif (!values.nr && default_value) {\n     -+\t\tstruct key_value_info kvi = { 0 };\n     ++\t\tstruct key_value_info kvi = KVI_INIT;\n       \t\tstruct strbuf *item;\n      +\n      +\t\tkvi_from_param(&kvi);\n     @@ builtin/config.c: static void check_write(void)\n       };\n       \n       static int urlmatch_collect_fn(const char *var, const char *value,\n     --\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n     -+\t\t\t       struct key_value_info *kvi, void *cb)\n     +-\t\t\t       const struct config_context *ctx UNUSED,\n     ++\t\t\t       const struct config_context *ctx,\n     + \t\t\t       void *cb)\n       {\n       \tstruct string_list *values = cb;\n       \tstruct string_list_item *item = string_list_insert(values, var);\n     + \tstruct urlmatch_current_candidate_value *matched = item->util;\n     ++\tconst struct key_value_info *kvi = ctx->kvi;\n     + \n     + \tif (!matched) {\n     + \t\tmatched = xmalloc(sizeof(*matched));\n      @@ builtin/config.c: static int urlmatch_collect_fn(const char *var, const char *value,\n       \t} else {\n       \t\tstrbuf_reset(&matched->value);\n       \t}\n     -+\tmemcpy(&matched->kvi, kvi, sizeof(struct key_value_info));\n     ++\tmatched->kvi = *kvi;\n       \n       \tif (value) {\n       \t\tstrbuf_addstr(&matched->value, value);\n     @@ config.c: static const char include_depth_advice[] = N_(\n       \"This might be due to circular includes.\");\n      -static int handle_path_include(struct config_source *cs, const char *path,\n      +static int handle_path_include(struct config_source *cs,\n     -+\t\t\t       struct key_value_info *kvi,\n     ++\t\t\t       const struct key_value_info *kvi,\n      +\t\t\t       const char *path,\n       \t\t\t       struct config_include_data *inc)\n       {\n     @@ config.c: static int git_config_include(const char *var, const char *value,\n       \n       \tif (!strcmp(var, \"include.path\"))\n      -\t\tret = handle_path_include(cs, value, inc);\n     -+\t\tret = handle_path_include(cs, kvi, value, inc);\n     ++\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n       \n       \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n       \t    cond && include_condition_is_true(cs, inc, cond, cond_len) &&\n     @@ config.c: static int git_config_include(const char *var, const char *value,\n       \t\tif (inc->opts->unconditional_remote_url)\n       \t\t\tinc->fn = forbid_remote_url;\n      -\t\tret = handle_path_include(cs, value, inc);\n     -+\t\tret = handle_path_include(cs, kvi, value, inc);\n     ++\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n       \t\tinc->fn = old_fn;\n       \t}\n       \n     @@ config.c: out_free_ret_1:\n       {\n       \tchar *canonical_name;\n       \tint ret;\n     -@@ config.c: static int config_parse_pair(const char *key, const char *value,\n     ++\tstruct config_context ctx = {\n     ++\t\t.kvi = kvi,\n     ++\t};\n     + \n     + \tif (!strlen(key))\n     + \t\treturn error(_(\"empty config key\"));\n       \tif (git_config_parse_key(key, &canonical_name, NULL))\n       \t\treturn -1;\n       \n      -\tret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;\n     -+\tret = (fn(canonical_name, value, kvi, data) < 0) ? -1 : 0;\n     ++\tret = (fn(canonical_name, value, &ctx, data) < 0) ? -1 : 0;\n       \tfree(canonical_name);\n       \treturn ret;\n       }\n     @@ config.c: static int config_parse_pair(const char *key, const char *value,\n       \tconst char *value;\n       \tstruct strbuf **pair;\n       \tint ret;\n     -+\tstruct key_value_info kvi = { 0 };\n     ++\tstruct key_value_info kvi = KVI_INIT;\n      +\n      +\tkvi_from_param(&kvi);\n       \n     @@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)\n       \tint ret = 0;\n       \tchar *envw = NULL;\n       \tstruct config_source source = CONFIG_SOURCE_INIT;\n     -+\tstruct key_value_info kvi = { 0 };\n     ++\tstruct key_value_info kvi = KVI_INIT;\n       \n       \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n       \tconfig_reader_push_source(&the_reader, &source);\n     @@ config.c: static int configset_find_element(struct config_set *set, const char *\n       }\n       \n      -static int configset_add_value(struct config_reader *reader,\n     -+static int configset_add_value(struct key_value_info *kvi_p,\n     ++static int configset_add_value(const struct key_value_info *kvi_p,\n      +\t\t\t       struct config_reader *reader,\n       \t\t\t       struct config_set *set, const char *key,\n       \t\t\t       const char *value)\n     @@ config.c: struct configset_add_data {\n       #define CONFIGSET_ADD_INIT { 0 }\n       \n       static int config_set_callback(const char *key, const char *value,\n     --\t\t\t       struct key_value_info *kvi UNUSED, void *cb)\n     -+\t\t\t       struct key_value_info *kvi, void *cb)\n     +-\t\t\t       const struct config_context *ctx UNUSED,\n     ++\t\t\t       const struct config_context *ctx,\n     + \t\t\t       void *cb)\n       {\n       \tstruct configset_add_data *data = cb;\n      -\tconfigset_add_value(data->config_reader, data->config_set, key, value);\n     -+\tconfigset_add_value(kvi, data->config_reader, data->config_set, key, value);\n     ++\tconfigset_add_value(ctx->kvi, data->config_reader, data->config_set,\n     ++\t\t\t    key, value);\n       \treturn 0;\n       }\n       \n 10:  7dc0c46b864 !  8:  66572df7beb trace2: plumb config kvi\n     @@ Commit message\n          trace2_def_param_fl(), which gets called by two code paths:\n      \n          - Through tr2_cfg_cb(), which is a config callback, so it trivially\n     -      receives \"kvi\".\n     +      receives \"kvi\" via the \"struct config_context ctx\" parameter.\n      \n          - Through tr2_list_env_vars_fl(), which is a high level function that\n            lists environment variables for tracing. This has been secretly\n     @@ trace2.c: void trace2_thread_exit_fl(const char *file, int line)\n       \n       void trace2_def_param_fl(const char *file, int line, const char *param,\n      -\t\t\t const char *value)\n     -+\t\t\t const char *value, struct key_value_info *kvi)\n     ++\t\t\t const char *value, const struct key_value_info *kvi)\n       {\n       \tstruct tr2_tgt *tgt_j;\n       \tint j;\n     @@ trace2.h: void trace2_thread_exit_fl(const char *file, int line);\n        */\n       void trace2_def_param_fl(const char *file, int line, const char *param,\n      -\t\t\t const char *value);\n     -+\t\t\t const char *value, struct key_value_info *kvi);\n     ++\t\t\t const char *value, const struct key_value_info *kvi);\n       \n       #define trace2_def_param(param, value) \\\n       \ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\n      \n       ## trace2/tr2_cfg.c ##\n     -@@ trace2/tr2_cfg.c: struct tr2_cfg_data {\n     -  * See if the given config key matches any of our patterns of interest.\n     -  */\n     - static int tr2_cfg_cb(const char *key, const char *value,\n     --\t\t      struct key_value_info *kvi UNUSED, void *d)\n     -+\t\t      struct key_value_info *kvi, void *d)\n     - {\n     - \tstruct strbuf **s;\n     - \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n      @@ trace2/tr2_cfg.c: static int tr2_cfg_cb(const char *key, const char *value,\n       \t\tstruct strbuf *buf = *s;\n       \t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n       \t\tif (wm == WM_MATCH) {\n      -\t\t\ttrace2_def_param_fl(data->file, data->line, key, value);\n      +\t\t\ttrace2_def_param_fl(data->file, data->line, key, value,\n     -+\t\t\t\t\t    kvi);\n     ++\t\t\t\t\t    ctx->kvi);\n       \t\t\treturn 0;\n       \t\t}\n       \t}\n     @@ trace2/tr2_cfg.c: void tr2_cfg_list_config_fl(const char *file, int line)\n       \n       void tr2_list_env_vars_fl(const char *file, int line)\n       {\n     -+\tstruct key_value_info kvi = { 0 };\n     ++\tstruct key_value_info kvi = KVI_INIT;\n       \tstruct strbuf **s;\n       \n      +\tkvi_from_param(&kvi);\n     @@ trace2/tr2_tgt.h: typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, i\n       typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,\n      -\t\t\t\t     const char *param, const char *value);\n      +\t\t\t\t     const char *param, const char *value,\n     -+\t\t\t\t     struct key_value_info *kvi);\n     ++\t\t\t\t     const struct key_value_info *kvi);\n       \n       typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,\n       \t\t\t\t    const struct repository *repo);\n     @@ trace2/tr2_tgt_event.c: static void fn_exec_result_fl(const char *file, int line\n       \n       static void fn_param_fl(const char *file, int line, const char *param,\n      -\t\t\tconst char *value)\n     -+\t\t\tconst char *value, struct key_value_info *kvi)\n     ++\t\t\tconst char *value, const struct key_value_info *kvi)\n       {\n       \tconst char *event_name = \"def_param\";\n       \tstruct json_writer jw = JSON_WRITER_INIT;\n     @@ trace2/tr2_tgt_normal.c: static void fn_exec_result_fl(const char *file, int lin\n       \n       static void fn_param_fl(const char *file, int line, const char *param,\n      -\t\t\tconst char *value)\n     -+\t\t\tconst char *value, struct key_value_info *kvi)\n     ++\t\t\tconst char *value, const struct key_value_info *kvi)\n       {\n       \tstruct strbuf buf_payload = STRBUF_INIT;\n      -\tenum config_scope scope = current_config_scope();\n     @@ trace2/tr2_tgt_perf.c: static void fn_exec_result_fl(const char *file, int line,\n       \n       static void fn_param_fl(const char *file, int line, const char *param,\n      -\t\t\tconst char *value)\n     -+\t\t\tconst char *value, struct key_value_info *kvi)\n     ++\t\t\tconst char *value, const struct key_value_info *kvi)\n       {\n       \tconst char *event_name = \"def_param\";\n       \tstruct strbuf buf_payload = STRBUF_INIT;\n 11:  504eb206b5a !  9:  123e19dda4a config: pass kvi to die_bad_number()\n     @@ Commit message\n      \n          In config.c, this requires changing the signature of\n          git_configset_get_value() to 'return' \"kvi\" in an out parameter so that\n     -    git_configset_get_<type>() can pass it to git_config_<type>().\n     +    git_configset_get_<type>() can pass it to git_config_<type>(). Only\n     +    numeric types will use \"kvi\", so for non-numeric types (e.g.\n     +    git_configset_get_string()), pass NULL to indicate that the out\n     +    parameter isn't needed.\n      \n     -    Outside of config.c, config callbacks now need to pass \"kvi\" to any of\n     -    the git_config_<type>() functions that parse a config string into a\n     -    number type. Included is a .cocci patch to make that refactor. In cases\n     -    where \"kvi\" would never be used, pass NULL, e.g.:\n     +    Outside of config.c, config callbacks now need to pass \"ctx->kvi\" to any\n     +    of the git_config_<type>() functions that parse a config string into a\n     +    number type. Included is a .cocci patch to make that refactor.\n      \n     -    - In config.c, when we are parsing a boolean instead of a number\n     -    - In builtin/config.c, when calling normalize_value() before setting\n     -      config to something the user gave us.\n     +    The only exceptional case is builtin/config.c, where git_config_<type>()\n     +    is called outside of a config callback (namely, on user-provided input),\n     +    so config source information has never been available. In this case,\n     +    die_bad_number() defaults to a generic, but perfectly descriptive\n     +    message. Let's provide a safe, non-NULL for \"kvi\" anyway, but make sure\n     +    not to change the message.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n     @@ archive-tar.c: static int tar_filter_config(const char *var, const char *value,\n       }\n       \n       static int git_tar_config(const char *var, const char *value,\n     --\t\t\t  struct key_value_info *kvi UNUSED, void *cb)\n     -+\t\t\t  struct key_value_info *kvi, void *cb)\n     +-\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n     ++\t\t\t  const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, \"tar.umask\")) {\n       \t\tif (value && !strcmp(value, \"user\")) {\n     @@ archive-tar.c: static int tar_filter_config(const char *var, const char *value,\n       \t\t\tumask(tar_umask);\n       \t\t} else {\n      -\t\t\ttar_umask = git_config_int(var, value);\n     -+\t\t\ttar_umask = git_config_int(var, value, kvi);\n     ++\t\t\ttar_umask = git_config_int(var, value, ctx->kvi);\n       \t\t}\n       \t\treturn 0;\n       \t}\n     @@ builtin/commit-graph.c: static int write_option_max_new_filters(const struct opt\n       }\n       \n       static int git_commit_graph_write_config(const char *var, const char *value,\n     --\t\t\t\t\t struct key_value_info *kvi UNUSED,\n     -+\t\t\t\t\t struct key_value_info *kvi,\n     +-\t\t\t\t\t const struct config_context *ctx UNUSED,\n     ++\t\t\t\t\t const struct config_context *ctx,\n       \t\t\t\t\t void *cb UNUSED)\n       {\n       \tif (!strcmp(var, \"commitgraph.maxnewfilters\"))\n      -\t\twrite_opts.max_new_filters = git_config_int(var, value);\n     -+\t\twrite_opts.max_new_filters = git_config_int(var, value, kvi);\n     ++\t\twrite_opts.max_new_filters = git_config_int(var, value, ctx->kvi);\n       \t/*\n       \t * No need to fall-back to 'git_default_config', since this was already\n       \t * called in 'cmd_commit_graph()'.\n     @@ builtin/commit.c: static int git_status_config(const char *k, const char *v,\n       \tif (!strcmp(k, \"status.submodulesummary\")) {\n       \t\tint is_bool;\n      -\t\ts->submodule_summary = git_config_bool_or_int(k, v, &is_bool);\n     -+\t\ts->submodule_summary = git_config_bool_or_int(k, v, kvi,\n     ++\t\ts->submodule_summary = git_config_bool_or_int(k, v, ctx->kvi,\n      +\t\t\t\t\t\t\t      &is_bool);\n       \t\tif (is_bool && s->submodule_summary)\n       \t\t\ts->submodule_summary = -1;\n     @@ builtin/commit.c: static int git_status_config(const char *k, const char *v,\n       \tif (!strcmp(k, \"diff.renamelimit\")) {\n       \t\tif (s->rename_limit == -1)\n      -\t\t\ts->rename_limit = git_config_int(k, v);\n     -+\t\t\ts->rename_limit = git_config_int(k, v, kvi);\n     ++\t\t\ts->rename_limit = git_config_int(k, v, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \tif (!strcmp(k, \"status.renamelimit\")) {\n      -\t\ts->rename_limit = git_config_int(k, v);\n     -+\t\ts->rename_limit = git_config_int(k, v, kvi);\n     ++\t\ts->rename_limit = git_config_int(k, v, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \tif (!strcmp(k, \"diff.renames\")) {\n     @@ builtin/commit.c: static int git_commit_config(const char *k, const char *v,\n       \tif (!strcmp(k, \"commit.verbose\")) {\n       \t\tint is_bool;\n      -\t\tconfig_commit_verbose = git_config_bool_or_int(k, v, &is_bool);\n     -+\t\tconfig_commit_verbose = git_config_bool_or_int(k, v, kvi,\n     ++\t\tconfig_commit_verbose = git_config_bool_or_int(k, v, ctx->kvi,\n      +\t\t\t\t\t\t\t       &is_bool);\n       \t\treturn 0;\n       \t}\n     @@ builtin/config.c: static char *normalize_value(const char *key, const char *valu\n       \t\tif (!is_bool)\n       \t\t\treturn xstrfmt(\"%d\", v);\n       \t\telse\n     +@@ builtin/config.c: int cmd_config(int argc, const char **argv, const char *prefix)\n     + \tchar *value = NULL;\n     + \tint flags = 0;\n     + \tint ret = 0;\n     ++\tstruct key_value_info default_kvi = KVI_INIT;\n     + \n     + \tgiven_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));\n     + \n      @@ builtin/config.c: int cmd_config(int argc, const char **argv, const char *prefix)\n       \telse if (actions == ACTION_SET) {\n       \t\tcheck_write();\n       \t\tcheck_argc(argc, 2, 2);\n      -\t\tvalue = normalize_value(argv[0], argv[1]);\n     -+\t\tvalue = normalize_value(argv[0], argv[1], NULL);\n     ++\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n       \t\tret = git_config_set_in_file_gently(given_config_source.file, argv[0], value);\n       \t\tif (ret == CONFIG_NOTHING_SET)\n       \t\t\terror(_(\"cannot overwrite multiple values with a single value\\n\"\n     @@ builtin/config.c: int cmd_config(int argc, const char **argv, const char *prefix\n       \t\tcheck_write();\n       \t\tcheck_argc(argc, 2, 3);\n      -\t\tvalue = normalize_value(argv[0], argv[1]);\n     -+\t\tvalue = normalize_value(argv[0], argv[1], NULL);\n     ++\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n       \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n       \t\t\t\t\t\t\t     argv[0], value, argv[2],\n       \t\t\t\t\t\t\t     flags);\n     @@ builtin/config.c: int cmd_config(int argc, const char **argv, const char *prefix\n       \t\tcheck_write();\n       \t\tcheck_argc(argc, 2, 2);\n      -\t\tvalue = normalize_value(argv[0], argv[1]);\n     -+\t\tvalue = normalize_value(argv[0], argv[1], NULL);\n     ++\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n       \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n       \t\t\t\t\t\t\t     argv[0], value,\n       \t\t\t\t\t\t\t     CONFIG_REGEX_NONE,\n     @@ builtin/config.c: int cmd_config(int argc, const char **argv, const char *prefix\n       \t\tcheck_write();\n       \t\tcheck_argc(argc, 2, 3);\n      -\t\tvalue = normalize_value(argv[0], argv[1]);\n     -+\t\tvalue = normalize_value(argv[0], argv[1], NULL);\n     ++\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n       \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n       \t\t\t\t\t\t\t     argv[0], value, argv[2],\n       \t\t\t\t\t\t\t     flags | CONFIG_FLAGS_MULTI_REPLACE);\n     @@ builtin/fetch.c: static int git_fetch_config(const char *k, const char *v,\n       \n       \tif (!strcmp(k, \"submodule.fetchjobs\")) {\n      -\t\tsubmodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v);\n     -+\t\tsubmodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v, kvi);\n     ++\t\tsubmodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v, ctx->kvi);\n       \t\treturn 0;\n       \t} else if (!strcmp(k, \"fetch.recursesubmodules\")) {\n       \t\trecurse_submodules = parse_fetch_recurse_submodules_arg(k, v);\n     @@ builtin/fetch.c: static int git_fetch_config(const char *k, const char *v,\n       \n       \tif (!strcmp(k, \"fetch.parallel\")) {\n      -\t\tfetch_parallel_config = git_config_int(k, v);\n     -+\t\tfetch_parallel_config = git_config_int(k, v, kvi);\n     ++\t\tfetch_parallel_config = git_config_int(k, v, ctx->kvi);\n       \t\tif (fetch_parallel_config < 0)\n       \t\t\tdie(_(\"fetch.parallel cannot be negative\"));\n       \t\tif (!fetch_parallel_config)\n      \n       ## builtin/fsmonitor--daemon.c ##\n      @@ builtin/fsmonitor--daemon.c: static int fsmonitor_config(const char *var, const char *value,\n     - \t\t\t    struct key_value_info *kvi, void *cb)\n     + \t\t\t    const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(var, FSMONITOR__IPC_THREADS)) {\n      -\t\tint i = git_config_int(var, value);\n     -+\t\tint i = git_config_int(var, value, kvi);\n     ++\t\tint i = git_config_int(var, value, ctx->kvi);\n       \t\tif (i < 1)\n       \t\t\treturn error(_(\"value of '%s' out of range: %d\"),\n       \t\t\t\t     FSMONITOR__IPC_THREADS, i);\n     @@ builtin/fsmonitor--daemon.c: static int fsmonitor_config(const char *var, const\n       \n       \tif (!strcmp(var, FSMONITOR__START_TIMEOUT)) {\n      -\t\tint i = git_config_int(var, value);\n     -+\t\tint i = git_config_int(var, value, kvi);\n     ++\t\tint i = git_config_int(var, value, ctx->kvi);\n       \t\tif (i < 0)\n       \t\t\treturn error(_(\"value of '%s' out of range: %d\"),\n       \t\t\t\t     FSMONITOR__START_TIMEOUT, i);\n     @@ builtin/fsmonitor--daemon.c: static int fsmonitor_config(const char *var, const\n       \tif (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) {\n       \t\tint is_bool;\n      -\t\tint i = git_config_bool_or_int(var, value, &is_bool);\n     -+\t\tint i = git_config_bool_or_int(var, value, kvi, &is_bool);\n     ++\t\tint i = git_config_bool_or_int(var, value, ctx->kvi, &is_bool);\n       \t\tif (i < 0)\n       \t\t\treturn error(_(\"value of '%s' not bool or int: %d\"),\n       \t\t\t\t     var, i);\n     @@ builtin/grep.c: static int grep_cmd_config(const char *var, const char *value,\n       \n       \tif (!strcmp(var, \"grep.threads\")) {\n      -\t\tnum_threads = git_config_int(var, value);\n     -+\t\tnum_threads = git_config_int(var, value, kvi);\n     ++\t\tnum_threads = git_config_int(var, value, ctx->kvi);\n       \t\tif (num_threads < 0)\n       \t\t\tdie(_(\"invalid number of threads specified (%d) for %s\"),\n       \t\t\t    num_threads, var);\n     @@ builtin/index-pack.c: static int git_index_pack_config(const char *k, const char\n       \n       \tif (!strcmp(k, \"pack.indexversion\")) {\n      -\t\topts->version = git_config_int(k, v);\n     -+\t\topts->version = git_config_int(k, v, kvi);\n     ++\t\topts->version = git_config_int(k, v, ctx->kvi);\n       \t\tif (opts->version > 2)\n       \t\t\tdie(_(\"bad pack.indexVersion=%\"PRIu32), opts->version);\n       \t\treturn 0;\n       \t}\n       \tif (!strcmp(k, \"pack.threads\")) {\n      -\t\tnr_threads = git_config_int(k, v);\n     -+\t\tnr_threads = git_config_int(k, v, kvi);\n     ++\t\tnr_threads = git_config_int(k, v, ctx->kvi);\n       \t\tif (nr_threads < 0)\n       \t\t\tdie(_(\"invalid number of threads specified (%d)\"),\n       \t\t\t    nr_threads);\n     @@ builtin/log.c: static int git_log_config(const char *var, const char *value,\n       \t\treturn git_config_string(&fmt_patch_subject_prefix, var, value);\n       \tif (!strcmp(var, \"format.filenamemaxlength\")) {\n      -\t\tfmt_patch_name_max = git_config_int(var, value);\n     -+\t\tfmt_patch_name_max = git_config_int(var, value, kvi);\n     ++\t\tfmt_patch_name_max = git_config_int(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \tif (!strcmp(var, \"format.encodeemailheaders\")) {\n      \n       ## builtin/pack-objects.c ##\n      @@ builtin/pack-objects.c: static int git_pack_config(const char *k, const char *v,\n     - \t\t\t   struct key_value_info *kvi, void *cb)\n     + \t\t\t   const struct config_context *ctx, void *cb)\n       {\n       \tif (!strcmp(k, \"pack.window\")) {\n      -\t\twindow = git_config_int(k, v);\n     -+\t\twindow = git_config_int(k, v, kvi);\n     ++\t\twindow = git_config_int(k, v, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \tif (!strcmp(k, \"pack.windowmemory\")) {\n      -\t\twindow_memory_limit = git_config_ulong(k, v);\n     -+\t\twindow_memory_limit = git_config_ulong(k, v, kvi);\n     ++\t\twindow_memory_limit = git_config_ulong(k, v, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \tif (!strcmp(k, \"pack.depth\")) {\n      -\t\tdepth = git_config_int(k, v);\n     -+\t\tdepth = git_config_int(k, v, kvi);\n     ++\t\tdepth = git_config_int(k, v, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \tif (!strcmp(k, \"pack.deltacachesize\")) {\n      -\t\tmax_delta_cache_size = git_config_int(k, v);\n     -+\t\tmax_delta_cache_size = git_config_int(k, v, kvi);\n     ++\t\tmax_delta_cache_size = git_config_int(k, v, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \tif (!strcmp(k, \"pack.deltacachelimit\")) {\n      -\t\tcache_max_small_delta_size = git_config_int(k, v);\n     -+\t\tcache_max_small_delta_size = git_config_int(k, v, kvi);\n     ++\t\tcache_max_small_delta_size = git_config_int(k, v, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \tif (!strcmp(k, \"pack.writebitmaphashcache\")) {\n     @@ builtin/pack-objects.c: static int git_pack_config(const char *k, const char *v,\n       \t}\n       \tif (!strcmp(k, \"pack.threads\")) {\n      -\t\tdelta_search_threads = git_config_int(k, v);\n     -+\t\tdelta_search_threads = git_config_int(k, v, kvi);\n     ++\t\tdelta_search_threads = git_config_int(k, v, ctx->kvi);\n       \t\tif (delta_search_threads < 0)\n       \t\t\tdie(_(\"invalid number of threads specified (%d)\"),\n       \t\t\t    delta_search_threads);\n     @@ builtin/pack-objects.c: static int git_pack_config(const char *k, const char *v,\n       \t}\n       \tif (!strcmp(k, \"pack.indexversion\")) {\n      -\t\tpack_idx_opts.version = git_config_int(k, v);\n     -+\t\tpack_idx_opts.version = git_config_int(k, v, kvi);\n     ++\t\tpack_idx_opts.version = git_config_int(k, v, ctx->kvi);\n       \t\tif (pack_idx_opts.version > 2)\n       \t\t\tdie(_(\"bad pack.indexVersion=%\"PRIu32),\n       \t\t\t    pack_idx_opts.version);\n     @@ builtin/receive-pack.c: static int receive_pack_config(const char *var, const ch\n       \n       \tif (strcmp(var, \"receive.unpacklimit\") == 0) {\n      -\t\treceive_unpack_limit = git_config_int(var, value);\n     -+\t\treceive_unpack_limit = git_config_int(var, value, kvi);\n     ++\t\treceive_unpack_limit = git_config_int(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \n       \tif (strcmp(var, \"transfer.unpacklimit\") == 0) {\n      -\t\ttransfer_unpack_limit = git_config_int(var, value);\n     -+\t\ttransfer_unpack_limit = git_config_int(var, value, kvi);\n     ++\t\ttransfer_unpack_limit = git_config_int(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \n     @@ builtin/receive-pack.c: static int receive_pack_config(const char *var, const ch\n       \n       \tif (strcmp(var, \"receive.certnonceslop\") == 0) {\n      -\t\tnonce_stamp_slop_limit = git_config_ulong(var, value);\n     -+\t\tnonce_stamp_slop_limit = git_config_ulong(var, value, kvi);\n     ++\t\tnonce_stamp_slop_limit = git_config_ulong(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \n     @@ builtin/receive-pack.c: static int receive_pack_config(const char *var, const ch\n       \n       \tif (strcmp(var, \"receive.keepalive\") == 0) {\n      -\t\tkeepalive_in_sec = git_config_int(var, value);\n     -+\t\tkeepalive_in_sec = git_config_int(var, value, kvi);\n     ++\t\tkeepalive_in_sec = git_config_int(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \n       \tif (strcmp(var, \"receive.maxinputsize\") == 0) {\n      -\t\tmax_input_size = git_config_int64(var, value);\n     -+\t\tmax_input_size = git_config_int64(var, value, kvi);\n     ++\t\tmax_input_size = git_config_int64(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \n     @@ builtin/submodule--helper.c: static int update_clone_task_finished(int result,\n       }\n       \n       static int git_update_clone_config(const char *var, const char *value,\n     --\t\t\t\t   struct key_value_info *kvi UNUSED,\n     -+\t\t\t\t   struct key_value_info *kvi,\n     +-\t\t\t\t   const struct config_context *ctx UNUSED,\n     ++\t\t\t\t   const struct config_context *ctx,\n       \t\t\t\t   void *cb)\n       {\n       \tint *max_jobs = cb;\n       \n       \tif (!strcmp(var, \"submodule.fetchjobs\"))\n      -\t\t*max_jobs = parse_submodule_fetchjobs(var, value);\n     -+\t\t*max_jobs = parse_submodule_fetchjobs(var, value, kvi);\n     ++\t\t*max_jobs = parse_submodule_fetchjobs(var, value, ctx->kvi);\n       \treturn 0;\n       }\n       \n     @@ config.c: int git_parse_ssize_t(const char *value, ssize_t *ret)\n      -static void die_bad_number(struct config_reader *reader, const char *name,\n      -\t\t\t   const char *value)\n      +static void die_bad_number(const char *name, const char *value,\n     -+\t\t\t   struct key_value_info *kvi)\n     ++\t\t\t   const struct key_value_info *kvi)\n       {\n       \tconst char *error_type = (errno == ERANGE) ?\n       \t\tN_(\"out of range\") : N_(\"invalid unit\");\n       \tconst char *bad_numeric = N_(\"bad numeric config value '%s' for '%s': %s\");\n      -\tconst char *config_name = NULL;\n      -\tenum config_origin_type config_origin = CONFIG_ORIGIN_UNKNOWN;\n     ++\n     ++\tif (!kvi)\n     ++\t\tBUG(\"kvi should not be NULL\");\n       \n       \tif (!value)\n       \t\tvalue = \"\";\n     @@ config.c: int git_parse_ssize_t(const char *value, ssize_t *ret)\n      -\treader_origin_type(reader, &config_origin);\n      -\n      -\tif (!config_name)\n     -+\tif (!kvi || !kvi->filename)\n     ++\tif (!kvi->filename)\n       \t\tdie(_(bad_numeric), value, name, _(error_type));\n       \n      -\tswitch (config_origin) {\n     @@ config.c: int git_parse_ssize_t(const char *value, ssize_t *ret)\n       \n      -int git_config_int(const char *name, const char *value)\n      +int git_config_int(const char *name, const char *value,\n     -+\t\t   struct key_value_info *kvi)\n     ++\t\t   const struct key_value_info *kvi)\n       {\n       \tint ret;\n       \tif (!git_parse_int(value, &ret))\n     @@ config.c: int git_parse_ssize_t(const char *value, ssize_t *ret)\n       }\n       \n      -int64_t git_config_int64(const char *name, const char *value)\n     -+int64_t git_config_int64(const char *name, const char *value, struct key_value_info *kvi)\n     ++int64_t git_config_int64(const char *name, const char *value,\n     ++\t\t\t const struct key_value_info *kvi)\n       {\n       \tint64_t ret;\n       \tif (!git_parse_int64(value, &ret))\n     @@ config.c: int git_parse_ssize_t(const char *value, ssize_t *ret)\n       \n      -unsigned long git_config_ulong(const char *name, const char *value)\n      +unsigned long git_config_ulong(const char *name, const char *value,\n     -+\t\t\t       struct key_value_info *kvi)\n     ++\t\t\t       const struct key_value_info *kvi)\n       {\n       \tunsigned long ret;\n       \tif (!git_parse_ulong(value, &ret))\n     @@ config.c: int git_parse_ssize_t(const char *value, ssize_t *ret)\n       \n      -ssize_t git_config_ssize_t(const char *name, const char *value)\n      +ssize_t git_config_ssize_t(const char *name, const char *value,\n     -+\t\t\t   struct key_value_info *kvi)\n     ++\t\t\t   const struct key_value_info *kvi)\n       {\n       \tssize_t ret;\n       \tif (!git_parse_ssize_t(value, &ret))\n     @@ config.c: int git_parse_maybe_bool(const char *value)\n       \n      -int git_config_bool_or_int(const char *name, const char *value, int *is_bool)\n      +int git_config_bool_or_int(const char *name, const char *value,\n     -+\t\t\t   struct key_value_info *kvi, int *is_bool)\n     ++\t\t\t   const struct key_value_info *kvi, int *is_bool)\n       {\n       \tint v = git_parse_maybe_bool_text(value);\n       \tif (0 <= v) {\n     @@ config.c: static int git_default_core_config(const char *var, const char *value,\n       \t\t\tdefault_abbrev = the_hash_algo->hexsz;\n       \t\telse {\n      -\t\t\tint abbrev = git_config_int(var, value);\n     -+\t\t\tint abbrev = git_config_int(var, value, kvi);\n     ++\t\t\tint abbrev = git_config_int(var, value, ctx->kvi);\n       \t\t\tif (abbrev < minimum_abbrev || abbrev > the_hash_algo->hexsz)\n       \t\t\t\treturn error(_(\"abbrev length out of range: %d\"), abbrev);\n       \t\t\tdefault_abbrev = abbrev;\n     @@ config.c: static int git_default_core_config(const char *var, const char *value,\n       \n       \tif (!strcmp(var, \"core.loosecompression\")) {\n      -\t\tint level = git_config_int(var, value);\n     -+\t\tint level = git_config_int(var, value, kvi);\n     ++\t\tint level = git_config_int(var, value, ctx->kvi);\n       \t\tif (level == -1)\n       \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n       \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n     @@ config.c: static int git_default_core_config(const char *var, const char *value,\n       \n       \tif (!strcmp(var, \"core.compression\")) {\n      -\t\tint level = git_config_int(var, value);\n     -+\t\tint level = git_config_int(var, value, kvi);\n     ++\t\tint level = git_config_int(var, value, ctx->kvi);\n       \t\tif (level == -1)\n       \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n       \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n     @@ config.c: static int git_default_core_config(const char *var, const char *value,\n       \tif (!strcmp(var, \"core.packedgitwindowsize\")) {\n       \t\tint pgsz_x2 = getpagesize() * 2;\n      -\t\tpacked_git_window_size = git_config_ulong(var, value);\n     -+\t\tpacked_git_window_size = git_config_ulong(var, value, kvi);\n     ++\t\tpacked_git_window_size = git_config_ulong(var, value, ctx->kvi);\n       \n       \t\t/* This value must be multiple of (pagesize * 2) */\n       \t\tpacked_git_window_size /= pgsz_x2;\n     @@ config.c: static int git_default_core_config(const char *var, const char *value,\n       \n       \tif (!strcmp(var, \"core.bigfilethreshold\")) {\n      -\t\tbig_file_threshold = git_config_ulong(var, value);\n     -+\t\tbig_file_threshold = git_config_ulong(var, value, kvi);\n     ++\t\tbig_file_threshold = git_config_ulong(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \n       \tif (!strcmp(var, \"core.packedgitlimit\")) {\n      -\t\tpacked_git_limit = git_config_ulong(var, value);\n     -+\t\tpacked_git_limit = git_config_ulong(var, value, kvi);\n     ++\t\tpacked_git_limit = git_config_ulong(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \n       \tif (!strcmp(var, \"core.deltabasecachelimit\")) {\n      -\t\tdelta_base_cache_limit = git_config_ulong(var, value);\n     -+\t\tdelta_base_cache_limit = git_config_ulong(var, value, kvi);\n     ++\t\tdelta_base_cache_limit = git_config_ulong(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \n     @@ config.c: int git_default_config(const char *var, const char *value,\n       \n       \tif (!strcmp(var, \"pack.packsizelimit\")) {\n      -\t\tpack_size_limit_cfg = git_config_ulong(var, value);\n     -+\t\tpack_size_limit_cfg = git_config_ulong(var, value, kvi);\n     ++\t\tpack_size_limit_cfg = git_config_ulong(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \n       \tif (!strcmp(var, \"pack.compression\")) {\n      -\t\tint level = git_config_int(var, value);\n     -+\t\tint level = git_config_int(var, value, kvi);\n     ++\t\tint level = git_config_int(var, value, ctx->kvi);\n       \t\tif (level == -1)\n       \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n       \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n      @@ config.c: static void configset_iter(struct config_reader *reader, struct config_set *set,\n     + \t\tvalue_index = list->items[i].value_index;\n       \t\tvalues = &entry->value_list;\n     - \t\tkvi = values->items[value_index].util;\n       \n      -\t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n     --\n     - \t\tif (fn(entry->key, values->items[value_index].string, kvi, data) < 0)\n     - \t\t\tgit_die_config_linenr(entry->key, kvi->filename, kvi->linenr);\n     + \t\tctx.kvi = values->items[value_index].util;\n     + \t\tif (fn(entry->key, values->items[value_index].string, &ctx, data) < 0)\n     + \t\t\tgit_die_config_linenr(entry->key,\n     + \t\t\t\t\t      ctx.kvi->filename,\n     + \t\t\t\t\t      ctx.kvi->linenr);\n      -\t\tconfig_reader_set_kvi(reader, NULL);\n       \t}\n       }\n     @@ config.h: int git_parse_maybe_bool(const char *);\n        * otherwise, returns the parsed result.\n        */\n      -int git_config_int(const char *, const char *);\n     -+int git_config_int(const char *, const char *, struct key_value_info *);\n     ++int git_config_int(const char *, const char *, const struct key_value_info *);\n       \n      -int64_t git_config_int64(const char *, const char *);\n     -+int64_t git_config_int64(const char *, const char *, struct key_value_info *);\n     ++int64_t git_config_int64(const char *, const char *,\n     ++\t\t\t const struct key_value_info *);\n       \n       /**\n        * Identical to `git_config_int`, but for unsigned longs.\n        */\n      -unsigned long git_config_ulong(const char *, const char *);\n     -+unsigned long git_config_ulong(const char *, const char *, struct key_value_info *);\n     ++unsigned long git_config_ulong(const char *, const char *,\n     ++\t\t\t       const struct key_value_info *);\n       \n      -ssize_t git_config_ssize_t(const char *, const char *);\n     -+ssize_t git_config_ssize_t(const char *, const char *, struct key_value_info *);\n     ++ssize_t git_config_ssize_t(const char *, const char *,\n     ++\t\t\t   const struct key_value_info *);\n       \n       /**\n        * Same as `git_config_bool`, except that integers are returned as-is, and\n        * an `is_bool` flag is unset.\n        */\n      -int git_config_bool_or_int(const char *, const char *, int *);\n     -+int git_config_bool_or_int(const char *, const char *, struct key_value_info *,\n     -+\t\t\t   int *);\n     ++int git_config_bool_or_int(const char *, const char *,\n     ++\t\t\t   const struct key_value_info *, int *);\n       \n       /**\n        * Parse a string into a boolean value, respecting keywords like \"true\" and\n     @@ contrib/coccinelle/git_config_number.cocci (new)\n      +git_config_ssize_t\n      +)\n      +  (C1, C2\n     -++ , kvi\n     +++ , ctx->kvi\n      +  )\n      +|\n      +(\n     @@ contrib/coccinelle/git_config_number.cocci (new)\n      +|\n      +git_config_bool_or_int\n      +)\n     -+  (C1, C2,\n     -++ kvi,\n     -+  C3\n     ++  (C1, C2\n     +++ , ctx->kvi\n     ++ , C3\n      +  )\n      +)\n      \n     @@ diff.c: int git_diff_ui_config(const char *var, const char *value,\n       \t}\n       \tif (!strcmp(var, \"diff.context\")) {\n      -\t\tdiff_context_default = git_config_int(var, value);\n     -+\t\tdiff_context_default = git_config_int(var, value, kvi);\n     ++\t\tdiff_context_default = git_config_int(var, value, ctx->kvi);\n       \t\tif (diff_context_default < 0)\n       \t\t\treturn -1;\n       \t\treturn 0;\n     @@ diff.c: int git_diff_ui_config(const char *var, const char *value,\n       \tif (!strcmp(var, \"diff.interhunkcontext\")) {\n      -\t\tdiff_interhunk_context_default = git_config_int(var, value);\n      +\t\tdiff_interhunk_context_default = git_config_int(var, value,\n     -+\t\t\t\t\t\t\t\tkvi);\n     ++\t\t\t\t\t\t\t\tctx->kvi);\n       \t\tif (diff_interhunk_context_default < 0)\n       \t\t\treturn -1;\n       \t\treturn 0;\n     @@ diff.c: int git_diff_ui_config(const char *var, const char *value,\n       \t}\n       \tif (!strcmp(var, \"diff.statgraphwidth\")) {\n      -\t\tdiff_stat_graph_width = git_config_int(var, value);\n     -+\t\tdiff_stat_graph_width = git_config_int(var, value, kvi);\n     ++\t\tdiff_stat_graph_width = git_config_int(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \tif (!strcmp(var, \"diff.external\"))\n     @@ diff.c: int git_diff_basic_config(const char *var, const char *value,\n       \n       \tif (!strcmp(var, \"diff.renamelimit\")) {\n      -\t\tdiff_rename_limit_default = git_config_int(var, value);\n     -+\t\tdiff_rename_limit_default = git_config_int(var, value, kvi);\n     ++\t\tdiff_rename_limit_default = git_config_int(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \n     @@ fmt-merge-msg.c: int fmt_merge_msg_config(const char *key, const char *value,\n       \tif (!strcmp(key, \"merge.log\") || !strcmp(key, \"merge.summary\")) {\n       \t\tint is_bool;\n      -\t\tmerge_log_config = git_config_bool_or_int(key, value, &is_bool);\n     -+\t\tmerge_log_config = git_config_bool_or_int(key, value, kvi, &is_bool);\n     ++\t\tmerge_log_config = git_config_bool_or_int(key, value, ctx->kvi, &is_bool);\n       \t\tif (!is_bool && merge_log_config < 0)\n       \t\t\treturn error(\"%s: negative length %s\", key, value);\n       \t\tif (is_bool && merge_log_config)\n     @@ help.c: static struct cmdnames aliases;\n       #define AUTOCORRECT_IMMEDIATELY (-1)\n       \n       static int git_unknown_cmd_config(const char *var, const char *value,\n     --\t\t\t\t  struct key_value_info *kvi UNUSED,\n     --\t\t\t\t  void *cb UNUSED)\n     -+\t\t\t\t  struct key_value_info *kvi, void *cb UNUSED)\n     +-\t\t\t\t  const struct config_context *ctx UNUSED,\n     ++\t\t\t\t  const struct config_context *ctx,\n     + \t\t\t\t  void *cb UNUSED)\n       {\n       \tconst char *p;\n     - \n      @@ help.c: static int git_unknown_cmd_config(const char *var, const char *value,\n       \t\t} else if (!strcmp(value, \"prompt\")) {\n       \t\t\tautocorrect = AUTOCORRECT_PROMPT;\n       \t\t} else {\n      -\t\t\tint v = git_config_int(var, value);\n     -+\t\t\tint v = git_config_int(var, value, kvi);\n     ++\t\t\tint v = git_config_int(var, value, ctx->kvi);\n       \t\t\tautocorrect = (v < 0)\n       \t\t\t\t? AUTOCORRECT_IMMEDIATELY : v;\n       \t\t}\n     @@ http.c: static int http_options(const char *var, const char *value,\n       \n       \tif (!strcmp(\"http.minsessions\", var)) {\n      -\t\tmin_curl_sessions = git_config_int(var, value);\n     -+\t\tmin_curl_sessions = git_config_int(var, value, kvi);\n     ++\t\tmin_curl_sessions = git_config_int(var, value, ctx->kvi);\n       \t\tif (min_curl_sessions > 1)\n       \t\t\tmin_curl_sessions = 1;\n       \t\treturn 0;\n       \t}\n       \tif (!strcmp(\"http.maxrequests\", var)) {\n      -\t\tmax_requests = git_config_int(var, value);\n     -+\t\tmax_requests = git_config_int(var, value, kvi);\n     ++\t\tmax_requests = git_config_int(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \tif (!strcmp(\"http.lowspeedlimit\", var)) {\n      -\t\tcurl_low_speed_limit = (long)git_config_int(var, value);\n     -+\t\tcurl_low_speed_limit = (long)git_config_int(var, value, kvi);\n     ++\t\tcurl_low_speed_limit = (long)git_config_int(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \tif (!strcmp(\"http.lowspeedtime\", var)) {\n      -\t\tcurl_low_speed_time = (long)git_config_int(var, value);\n     -+\t\tcurl_low_speed_time = (long)git_config_int(var, value, kvi);\n     ++\t\tcurl_low_speed_time = (long)git_config_int(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t}\n       \n     @@ http.c: static int http_options(const char *var, const char *value,\n       \n       \tif (!strcmp(\"http.postbuffer\", var)) {\n      -\t\thttp_post_buffer = git_config_ssize_t(var, value);\n     -+\t\thttp_post_buffer = git_config_ssize_t(var, value, kvi);\n     ++\t\thttp_post_buffer = git_config_ssize_t(var, value, ctx->kvi);\n       \t\tif (http_post_buffer < 0)\n       \t\t\twarning(_(\"negative value for http.postBuffer; defaulting to %d\"), LARGE_PACKET_MAX);\n       \t\tif (http_post_buffer < LARGE_PACKET_MAX)\n     @@ imap-send.c: static int git_imap_config(const char *var, const char *val,\n       \t\treturn git_config_string(&server.auth_method, var, val);\n       \telse if (!strcmp(\"imap.port\", var))\n      -\t\tserver.port = git_config_int(var, val);\n     -+\t\tserver.port = git_config_int(var, val, kvi);\n     ++\t\tserver.port = git_config_int(var, val, ctx->kvi);\n       \telse if (!strcmp(\"imap.host\", var)) {\n       \t\tif (!val) {\n       \t\t\tgit_die_config(\"imap.host\", \"Missing value for 'imap.host'\");\n     @@ sequencer.c: static int git_config_string_dup(char **dest,\n       }\n       \n       static int populate_opts_cb(const char *key, const char *value,\n     --\t\t\t    struct key_value_info *kvi UNUSED, void *data)\n     -+\t\t\t    struct key_value_info *kvi, void *data)\n     +-\t\t\t    const struct config_context *ctx UNUSED,\n     ++\t\t\t    const struct config_context *ctx,\n     + \t\t\t    void *data)\n       {\n       \tstruct replay_opts *opts = data;\n     - \tint error_flag = 1;\n      @@ sequencer.c: static int populate_opts_cb(const char *key, const char *value,\n       \tif (!value)\n       \t\terror_flag = 0;\n       \telse if (!strcmp(key, \"options.no-commit\"))\n      -\t\topts->no_commit = git_config_bool_or_int(key, value, &error_flag);\n     -+\t\topts->no_commit = git_config_bool_or_int(key, value, kvi, &error_flag);\n     ++\t\topts->no_commit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n       \telse if (!strcmp(key, \"options.edit\"))\n      -\t\topts->edit = git_config_bool_or_int(key, value, &error_flag);\n     -+\t\topts->edit = git_config_bool_or_int(key, value, kvi, &error_flag);\n     ++\t\topts->edit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n       \telse if (!strcmp(key, \"options.allow-empty\"))\n       \t\topts->allow_empty =\n      -\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n     -+\t\t\tgit_config_bool_or_int(key, value, kvi, &error_flag);\n     ++\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n       \telse if (!strcmp(key, \"options.allow-empty-message\"))\n       \t\topts->allow_empty_message =\n      -\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n     -+\t\t\tgit_config_bool_or_int(key, value, kvi, &error_flag);\n     ++\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n       \telse if (!strcmp(key, \"options.keep-redundant-commits\"))\n       \t\topts->keep_redundant_commits =\n      -\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n     -+\t\t\tgit_config_bool_or_int(key, value, kvi, &error_flag);\n     ++\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n       \telse if (!strcmp(key, \"options.signoff\"))\n      -\t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n     -+\t\topts->signoff = git_config_bool_or_int(key, value, kvi, &error_flag);\n     ++\t\topts->signoff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n       \telse if (!strcmp(key, \"options.record-origin\"))\n      -\t\topts->record_origin = git_config_bool_or_int(key, value, &error_flag);\n     -+\t\topts->record_origin = git_config_bool_or_int(key, value, kvi, &error_flag);\n     ++\t\topts->record_origin = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n       \telse if (!strcmp(key, \"options.allow-ff\"))\n      -\t\topts->allow_ff = git_config_bool_or_int(key, value, &error_flag);\n     -+\t\topts->allow_ff = git_config_bool_or_int(key, value, kvi, &error_flag);\n     ++\t\topts->allow_ff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n       \telse if (!strcmp(key, \"options.mainline\"))\n      -\t\topts->mainline = git_config_int(key, value);\n     -+\t\topts->mainline = git_config_int(key, value, kvi);\n     ++\t\topts->mainline = git_config_int(key, value, ctx->kvi);\n       \telse if (!strcmp(key, \"options.strategy\"))\n       \t\tgit_config_string_dup(&opts->strategy, key, value);\n       \telse if (!strcmp(key, \"options.gpg-sign\"))\n      @@ sequencer.c: static int populate_opts_cb(const char *key, const char *value,\n     - \t\topts->xopts[opts->xopts_nr++] = xstrdup(value);\n     + \t\tstrvec_push(&opts->xopts, value);\n       \t} else if (!strcmp(key, \"options.allow-rerere-auto\"))\n       \t\topts->allow_rerere_auto =\n      -\t\t\tgit_config_bool_or_int(key, value, &error_flag) ?\n     -+\t\t\tgit_config_bool_or_int(key, value, kvi, &error_flag) ?\n     ++\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag) ?\n       \t\t\t\tRERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;\n       \telse if (!strcmp(key, \"options.default-msg-cleanup\")) {\n       \t\topts->explicit_cleanup = 1;\n     @@ setup.c: static int check_repo_format(const char *var, const char *value,\n       \n       \tif (strcmp(var, \"core.repositoryformatversion\") == 0)\n      -\t\tdata->version = git_config_int(var, value);\n     -+\t\tdata->version = git_config_int(var, value, kvi);\n     ++\t\tdata->version = git_config_int(var, value, ctx->kvi);\n       \telse if (skip_prefix(var, \"extensions.\", &ext)) {\n       \t\tswitch (handle_extension_v0(var, value, ext, data)) {\n       \t\tcase EXTENSION_ERROR:\n     @@ submodule-config.c: static int parse_fetch_recurse(const char *opt, const char *\n       \n      -int parse_submodule_fetchjobs(const char *var, const char *value)\n      +int parse_submodule_fetchjobs(const char *var, const char *value,\n     -+\t\t\t      struct key_value_info *kvi)\n     ++\t\t\t      const struct key_value_info *kvi)\n       {\n      -\tint fetchjobs = git_config_int(var, value);\n      +\tint fetchjobs = git_config_int(var, value, kvi);\n     @@ submodule-config.c: struct fetch_config {\n       };\n       \n       static int gitmodules_fetch_config(const char *var, const char *value,\n     --\t\t\t\t   struct key_value_info *kvi UNUSED,\n     --\t\t\t\t   void *cb)\n     -+\t\t\t\t   struct key_value_info *kvi, void *cb)\n     +-\t\t\t\t   const struct config_context *ctx UNUSED,\n     ++\t\t\t\t   const struct config_context *ctx,\n     + \t\t\t\t   void *cb)\n       {\n       \tstruct fetch_config *config = cb;\n       \tif (!strcmp(var, \"submodule.fetchjobs\")) {\n       \t\tif (config->max_children)\n       \t\t\t*(config->max_children) =\n      -\t\t\t\tparse_submodule_fetchjobs(var, value);\n     -+\t\t\t\tparse_submodule_fetchjobs(var, value, kvi);\n     ++\t\t\t\tparse_submodule_fetchjobs(var, value, ctx->kvi);\n       \t\treturn 0;\n       \t} else if (!strcmp(var, \"fetch.recursesubmodules\")) {\n       \t\tif (config->recurse_submodules)\n     @@ submodule-config.c: void fetch_config_from_gitmodules(int *max_children, int *re\n       }\n       \n       static int gitmodules_update_clone_config(const char *var, const char *value,\n     --\t\t\t\t\t  struct key_value_info *kvi UNUSED,\n     --\t\t\t\t\t  void *cb)\n     -+\t\t\t\t\t  struct key_value_info *kvi, void *cb)\n     +-\t\t\t\t\t  const struct config_context *ctx UNUSED,\n     ++\t\t\t\t\t  const struct config_context *ctx,\n     + \t\t\t\t\t  void *cb)\n       {\n       \tint *max_jobs = cb;\n       \tif (!strcmp(var, \"submodule.fetchjobs\"))\n      -\t\t*max_jobs = parse_submodule_fetchjobs(var, value);\n     -+\t\t*max_jobs = parse_submodule_fetchjobs(var, value, kvi);\n     ++\t\t*max_jobs = parse_submodule_fetchjobs(var, value, ctx->kvi);\n       \treturn 0;\n       }\n       \n     @@ submodule-config.h: struct repository;\n       \n      -int parse_submodule_fetchjobs(const char *var, const char *value);\n      +int parse_submodule_fetchjobs(const char *var, const char *value,\n     -+\t\t\t      struct key_value_info *kvi);\n     ++\t\t\t      const struct key_value_info *kvi);\n       int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);\n       struct option;\n       int option_fetch_parse_recurse_submodules(const struct option *opt,\n     @@ t/helper/test-config.c: static int iterate_cb(const char *var, const char *value\n       }\n       \n       static int parse_int_cb(const char *var, const char *value,\n     --\t\t\tstruct key_value_info *kvi UNUSED, void *data)\n     -+\t\t\tstruct key_value_info *kvi, void *data)\n     +-\t\t\tconst struct config_context *ctx UNUSED, void *data)\n     ++\t\t\tconst struct config_context *ctx, void *data)\n       {\n       \tconst char *key_to_match = data;\n       \n       \tif (!strcmp(key_to_match, var)) {\n      -\t\tint parsed = git_config_int(value, value);\n     -+\t\tint parsed = git_config_int(value, value, kvi);\n     ++\t\tint parsed = git_config_int(value, value, ctx->kvi);\n       \t\tprintf(\"%d\\n\", parsed);\n       \t}\n       \treturn 0;\n     @@ t/helper/test-config.c: int cmd__config(int argc, const char **argv)\n       \t\t\t\tprintf(\"(NULL)\\n\");\n       \t\t\telse\n      \n     + ## trace2/tr2_cfg.c ##\n     +@@ trace2/tr2_cfg.c: struct tr2_cfg_data {\n     +  * See if the given config key matches any of our patterns of interest.\n     +  */\n     + static int tr2_cfg_cb(const char *key, const char *value,\n     +-\t\t      const struct config_context *ctx UNUSED, void *d)\n     ++\t\t      const struct config_context *ctx, void *d)\n     + {\n     + \tstruct strbuf **s;\n     + \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n     +\n       ## upload-pack.c ##\n      @@ upload-pack.c: static int find_symref(const char *refname,\n       }\n       \n       static int parse_object_filter_config(const char *var, const char *value,\n      -\t\t\t\t       struct upload_pack_data *data)\n     -+\t\t\t\t      struct key_value_info *kvi,\n     ++\t\t\t\t      const struct key_value_info *kvi,\n      +\t\t\t\t      struct upload_pack_data *data)\n       {\n       \tstruct strbuf buf = STRBUF_INIT;\n     @@ upload-pack.c: static int parse_object_filter_config(const char *var, const char\n       }\n       \n       static int upload_pack_config(const char *var, const char *value,\n     --\t\t\t      struct key_value_info *kvi UNUSED,\n     -+\t\t\t      struct key_value_info *kvi,\n     +-\t\t\t      const struct config_context *ctx UNUSED,\n     ++\t\t\t      const struct config_context *ctx,\n       \t\t\t      void *cb_data)\n       {\n       \tstruct upload_pack_data *data = cb_data;\n     @@ upload-pack.c: static int upload_pack_config(const char *var, const char *value,\n       \t\t\tdata->allow_uor &= ~ALLOW_ANY_SHA1;\n       \t} else if (!strcmp(\"uploadpack.keepalive\", var)) {\n      -\t\tdata->keepalive = git_config_int(var, value);\n     -+\t\tdata->keepalive = git_config_int(var, value, kvi);\n     ++\t\tdata->keepalive = git_config_int(var, value, ctx->kvi);\n       \t\tif (!data->keepalive)\n       \t\t\tdata->keepalive = -1;\n       \t} else if (!strcmp(\"uploadpack.allowfilter\", var)) {\n     @@ upload-pack.c: static int upload_pack_config(const char *var, const char *value,\n       \t}\n       \n      -\tif (parse_object_filter_config(var, value, data) < 0)\n     -+\tif (parse_object_filter_config(var, value, kvi, data) < 0)\n     ++\tif (parse_object_filter_config(var, value, ctx->kvi, data) < 0)\n       \t\treturn -1;\n       \n       \treturn parse_hide_refs_config(var, value, \"uploadpack\", &data->hidden_refs);\n 12:  52db0d3be82 ! 10:  8ec24b018e9 config.c: remove config_reader from configsets\n     @@ config.c: int config_with_options(config_fn_t fn, void *data,\n      @@ config.c: static int configset_find_element(struct config_set *set, const char *key,\n       }\n       \n     - static int configset_add_value(struct key_value_info *kvi_p,\n     + static int configset_add_value(const struct key_value_info *kvi_p,\n      -\t\t\t       struct config_reader *reader,\n       \t\t\t       struct config_set *set, const char *key,\n       \t\t\t       const char *value)\n       {\n     -@@ config.c: static int configset_add_value(struct key_value_info *kvi_p,\n     +@@ config.c: static int configset_add_value(const struct key_value_info *kvi_p,\n       \tl_item->e = e;\n       \tl_item->value_index = e->value_list.nr - 1;\n       \n     @@ config.c: void git_configset_clear(struct config_set *set)\n      -#define CONFIGSET_ADD_INIT { 0 }\n      -\n       static int config_set_callback(const char *key, const char *value,\n     - \t\t\t       struct key_value_info *kvi, void *cb)\n     + \t\t\t       const struct config_context *ctx,\n     + \t\t\t       void *cb)\n       {\n      -\tstruct configset_add_data *data = cb;\n     --\tconfigset_add_value(kvi, data->config_reader, data->config_set, key, value);\n     +-\tconfigset_add_value(ctx->kvi, data->config_reader, data->config_set,\n     +-\t\t\t    key, value);\n      +\tstruct config_set *set = cb;\n     -+\tconfigset_add_value(kvi, set, key, value);\n     ++\tconfigset_add_value(ctx->kvi, set, key, value);\n       \treturn 0;\n       }\n       \n 13:  7d9b9eefc78 ! 11:  8ae115cff88 config: add kvi.path, use it to evaluate includes\n     @@ Commit message\n          Include directives are evaluated using the path of the config file. To\n          reduce the dependence on \"config_reader.source\", add a new\n          \"key_value_info.path\" member and use that instead of\n     -    \"config_source.path\".\n     +    \"config_source.path\". This allows us to remove a \"struct config_reader\n     +    *\" field from \"struct config_include_data\", which will subsequently\n     +    allow us to remove \"struct config_reader\" entirely.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n     @@ config.c: static const char include_depth_advice[] = N_(\n       \"\t%s\\n\"\n       \"This might be due to circular includes.\");\n      -static int handle_path_include(struct config_source *cs,\n     --\t\t\t       struct key_value_info *kvi,\n     --\t\t\t       const char *path,\n     -+static int handle_path_include(struct key_value_info *kvi, const char *path,\n     +-\t\t\t       const struct key_value_info *kvi,\n     ++static int handle_path_include(const struct key_value_info *kvi,\n     + \t\t\t       const char *path,\n       \t\t\t       struct config_include_data *inc)\n       {\n     - \tint ret = 0;\n      @@ config.c: static int handle_path_include(struct config_source *cs,\n       \tif (!is_absolute_path(path)) {\n       \t\tchar *slash;\n     @@ config.c: static void add_trailing_starstar_for_dir(struct strbuf *pat)\n       }\n       \n      -static int prepare_include_condition_pattern(struct config_source *cs,\n     -+static int prepare_include_condition_pattern(struct key_value_info *kvi,\n     ++static int prepare_include_condition_pattern(const struct key_value_info *kvi,\n       \t\t\t\t\t     struct strbuf *pat)\n       {\n       \tstruct strbuf path = STRBUF_INIT;\n     @@ config.c: static int prepare_include_condition_pattern(struct config_source *cs,\n       }\n       \n      -static int include_by_gitdir(struct config_source *cs,\n     -+static int include_by_gitdir(struct key_value_info *kvi,\n     ++static int include_by_gitdir(const struct key_value_info *kvi,\n       \t\t\t     const struct config_options *opts,\n       \t\t\t     const char *cond, size_t cond_len, int icase)\n       {\n     @@ config.c: static int include_by_remote_url(struct config_include_data *inc,\n       }\n       \n      -static int include_condition_is_true(struct config_source *cs,\n     -+static int include_condition_is_true(struct key_value_info *kvi,\n     ++static int include_condition_is_true(const struct key_value_info *kvi,\n       \t\t\t\t     struct config_include_data *inc,\n       \t\t\t\t     const char *cond, size_t cond_len)\n       {\n     @@ config.c: static int include_by_remote_url(struct config_include_data *inc,\n       \t\treturn include_by_branch(cond, cond_len);\n       \telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.*.url:\", &cond,\n      @@ config.c: static int git_config_include(const char *var, const char *value,\n     - \t\t\t      struct key_value_info *kvi, void *data)\n     + \t\t\t      void *data)\n       {\n       \tstruct config_include_data *inc = data;\n      -\tstruct config_source *cs = inc->config_reader->source;\n     @@ config.c: static int git_config_include(const char *var, const char *value,\n       \t\treturn ret;\n       \n       \tif (!strcmp(var, \"include.path\"))\n     --\t\tret = handle_path_include(cs, kvi, value, inc);\n     -+\t\tret = handle_path_include(kvi, value, inc);\n     +-\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n     ++\t\tret = handle_path_include(ctx->kvi, value, inc);\n       \n       \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n      -\t    cond && include_condition_is_true(cs, inc, cond, cond_len) &&\n     -+\t    cond && include_condition_is_true(kvi, inc, cond, cond_len) &&\n     ++\t    cond && include_condition_is_true(ctx->kvi, inc, cond, cond_len) &&\n       \t    !strcmp(key, \"path\")) {\n       \t\tconfig_fn_t old_fn = inc->fn;\n       \n       \t\tif (inc->opts->unconditional_remote_url)\n       \t\t\tinc->fn = forbid_remote_url;\n     --\t\tret = handle_path_include(cs, kvi, value, inc);\n     -+\t\tret = handle_path_include(kvi, value, inc);\n     +-\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n     ++\t\tret = handle_path_include(ctx->kvi, value, inc);\n       \t\tinc->fn = old_fn;\n       \t}\n       \n     @@ config.h: struct key_value_info {\n       \tenum config_scope scope;\n      +\tconst char *path;\n       };\n     + #define KVI_INIT { \\\n     + \t.filename = NULL, \\\n     + \t.linenr = -1, \\\n     + \t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n     + \t.scope = CONFIG_SCOPE_UNKNOWN, \\\n     ++\t.path = NULL, \\\n     + }\n       \n     - /**\n     + /* Captures additional information that a config callback can use. */\n 14:  9e35b5b1f4d ! 12:  484d553cc7d config: pass source to config_parser_event_fn_t\n     @@ Commit message\n      \n          ..so that the callback can use a \"struct config_source\" parameter\n          instead of \"config_reader.source\". \"struct config_source\" is internal to\n     -    config.c, but this refactor is okay because this function has only ever\n     -    been (and probably ever will be) used internally by config.c.\n     +    config.c, so we are adding a pointer to a struct defined in config.c\n     +    into a public function signature defined in config.h, but this is okay\n     +    because this function has only ever been (and probably ever will be)\n     +    used internally by config.c.\n      \n          As a result, the_reader isn't used anywhere, so \"struct config_reader\"\n          is obsolete (it was only intended to be used with the_reader). Remove\n     @@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)\n       \tint ret = 0;\n       \tchar *envw = NULL;\n      -\tstruct config_source source = CONFIG_SOURCE_INIT;\n     - \tstruct key_value_info kvi = { 0 };\n     + \tstruct key_value_info kvi = KVI_INIT;\n       \n      -\tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n      -\tconfig_reader_push_source(&the_reader, &source);\n\n-- \ngitgitgadget\n"},{"id":"478585","messageId":"66572df7beb220b8f7d5ce2eb724fda55c128b94.1687290233.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"[PATCH v3 08/12] trace2: plumb config kvi","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-20T19:43:47Z","receivedAt":"2023-06-20T19:44:37Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThere is a code path starting from trace2_def_param_fl() that eventually\ncalls current_config_scope(), and thus it needs to have \"kvi\" plumbed\nthrough it. Additional plumbing is also needed to get \"kvi\" to\ntrace2_def_param_fl(), which gets called by two code paths:\n\n- Through tr2_cfg_cb(), which is a config callback, so it trivially\n  receives \"kvi\" via the \"struct config_context ctx\" parameter.\n\n- Through tr2_list_env_vars_fl(), which is a high level function that\n  lists environment variables for tracing. This has been secretly\n  behaving like git_config_from_parameters() (in that it parses config\n  from environment variables/the CLI), but does not set config source\n  information.\n\n  Teach tr2_list_env_vars_fl() to be well-behaved by using\n  kvi_from_param(), which is used elsewhere for CLI/environment\n  variable-based config.\n\nAs a result, current_config_scope() has no more callers, so remove it.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c                | 46 -----------------------------------------\n config.h                |  1 -\n trace2.c                |  4 ++--\n trace2.h                |  3 ++-\n trace2/tr2_cfg.c        |  7 +++++--\n trace2/tr2_tgt.h        |  4 +++-\n trace2/tr2_tgt_event.c  |  4 ++--\n trace2/tr2_tgt_normal.c |  4 ++--\n trace2/tr2_tgt_perf.c   |  4 ++--\n 9 files changed, 18 insertions(+), 59 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex cfc5c7f10b3..f519656ebcf 100644\n--- a/config.c\n+++ b/config.c\n@@ -85,16 +85,6 @@ struct config_reader {\n \t */\n \tstruct config_source *source;\n \tstruct key_value_info *config_kvi;\n-\t/*\n-\t * The \"scope\" of the current config source being parsed (repo, global,\n-\t * etc). Like \"source\", this is only set when parsing a config source.\n-\t * It's not part of \"source\" because it transcends a single file (i.e.,\n-\t * a file included from .git/config is still in \"repo\" scope).\n-\t *\n-\t * When iterating through a configset, the equivalent value is\n-\t * \"config_kvi.scope\" (see above).\n-\t */\n-\tenum config_scope parsing_scope;\n };\n /*\n  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n@@ -125,19 +115,9 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\n static inline void config_reader_set_kvi(struct config_reader *reader,\n \t\t\t\t\t struct key_value_info *kvi)\n {\n-\tif (kvi && (reader->source || reader->parsing_scope))\n-\t\tBUG(\"kvi should not be set while parsing a config source\");\n \treader->config_kvi = kvi;\n }\n \n-static inline void config_reader_set_scope(struct config_reader *reader,\n-\t\t\t\t\t   enum config_scope scope)\n-{\n-\tif (scope && reader->config_kvi)\n-\t\tBUG(\"scope should only be set when iterating through a config source\");\n-\treader->parsing_scope = scope;\n-}\n-\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -411,18 +391,12 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tenum config_scope store_scope = inc->config_reader->parsing_scope;\n-\n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tconfig_reader_set_scope(inc->config_reader, 0);\n-\n \tinc->remote_urls = xmalloc(sizeof(*inc->remote_urls));\n \tstring_list_init_dup(inc->remote_urls);\n \tconfig_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);\n-\n-\tconfig_reader_set_scope(inc->config_reader, store_scope);\n }\n \n static int forbid_remote_url(const char *var, const char *value UNUSED,\n@@ -2256,7 +2230,6 @@ static int do_git_config_sequence(struct config_reader *reader,\n \tchar *xdg_config = NULL;\n \tchar *user_config = NULL;\n \tchar *repo_config;\n-\tenum config_scope prev_parsing_scope = reader->parsing_scope;\n \n \tif (opts->commondir)\n \t\trepo_config = mkpathdup(\"%s/config\", opts->commondir);\n@@ -2265,7 +2238,6 @@ static int do_git_config_sequence(struct config_reader *reader,\n \telse\n \t\trepo_config = NULL;\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_SYSTEM);\n \tif (git_config_system() && system_config &&\n \t    !access_or_die(system_config, R_OK,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n@@ -2273,7 +2245,6 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n \t\t\t\t\t\t\t NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);\n \tgit_global_config(&user_config, &xdg_config);\n \n \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n@@ -2284,13 +2255,11 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\tret += git_config_from_file_with_options(fn, user_config, data,\n \t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);\n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n \t\tret += git_config_from_file_with_options(fn, repo_config, data,\n \t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n \tif (!opts->ignore_worktree && repository_format_worktree_config) {\n \t\tchar *path = git_pathdup(\"config.worktree\");\n \t\tif (!access_or_die(path, R_OK, 0))\n@@ -2300,11 +2269,9 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\tfree(path);\n \t}\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);\n \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n \t\tdie(_(\"unable to parse command-line config\"));\n \n-\tconfig_reader_set_scope(reader, prev_parsing_scope);\n \tfree(system_config);\n \tfree(xdg_config);\n \tfree(user_config);\n@@ -2317,7 +2284,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\t\tconst struct config_options *opts)\n {\n \tstruct config_include_data inc = CONFIG_INCLUDE_INIT;\n-\tenum config_scope prev_scope = the_reader.parsing_scope;\n \tint ret;\n \n \tif (opts->respect_includes) {\n@@ -2330,9 +2296,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tdata = &inc;\n \t}\n \n-\tif (config_source)\n-\t\tconfig_reader_set_scope(&the_reader, config_source->scope);\n-\n \t/*\n \t * If we have a specific filename, use it. Otherwise, follow the\n \t * regular lookup sequence.\n@@ -2356,7 +2319,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tstring_list_clear(inc.remote_urls, 0);\n \t\tFREE_AND_NULL(inc.remote_urls);\n \t}\n-\tconfig_reader_set_scope(&the_reader, prev_scope);\n \treturn ret;\n }\n \n@@ -4076,14 +4038,6 @@ static int reader_config_name(struct config_reader *reader, const char **out)\n \treturn 0;\n }\n \n-enum config_scope current_config_scope(void)\n-{\n-\tif (the_reader.config_kvi)\n-\t\treturn the_reader.config_kvi->scope;\n-\telse\n-\t\treturn the_reader.parsing_scope;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex 7954a8cb104..092db17d2e4 100644\n--- a/config.h\n+++ b/config.h\n@@ -386,7 +386,6 @@ void git_global_config(char **user, char **xdg);\n \n int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n \n-enum config_scope current_config_scope(void);\n const char *config_origin_type_name(enum config_origin_type type);\n void kvi_from_param(struct key_value_info *out);\n \ndiff --git a/trace2.c b/trace2.c\nindex 0efc4e7b958..49c23bfd05a 100644\n--- a/trace2.c\n+++ b/trace2.c\n@@ -634,7 +634,7 @@ void trace2_thread_exit_fl(const char *file, int line)\n }\n \n void trace2_def_param_fl(const char *file, int line, const char *param,\n-\t\t\t const char *value)\n+\t\t\t const char *value, const struct key_value_info *kvi)\n {\n \tstruct tr2_tgt *tgt_j;\n \tint j;\n@@ -644,7 +644,7 @@ void trace2_def_param_fl(const char *file, int line, const char *param,\n \n \tfor_each_wanted_builtin (j, tgt_j)\n \t\tif (tgt_j->pfn_param_fl)\n-\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n+\t\t\ttgt_j->pfn_param_fl(file, line, param, value, kvi);\n }\n \n void trace2_def_repo_fl(const char *file, int line, struct repository *repo)\ndiff --git a/trace2.h b/trace2.h\nindex 4ced30c0db3..f5c5a9e6bac 100644\n--- a/trace2.h\n+++ b/trace2.h\n@@ -325,6 +325,7 @@ void trace2_thread_exit_fl(const char *file, int line);\n \n #define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)\n \n+struct key_value_info;\n /*\n  * Emits a \"def_param\" message containing a key/value pair.\n  *\n@@ -334,7 +335,7 @@ void trace2_thread_exit_fl(const char *file, int line);\n  * `core.abbrev`, `status.showUntrackedFiles`, or `--no-ahead-behind`.\n  */\n void trace2_def_param_fl(const char *file, int line, const char *param,\n-\t\t\t const char *value);\n+\t\t\t const char *value, const struct key_value_info *kvi);\n \n #define trace2_def_param(param, value) \\\n \ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 83bc4fd109c..00d546a27cd 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -109,7 +109,8 @@ static int tr2_cfg_cb(const char *key, const char *value,\n \t\tstruct strbuf *buf = *s;\n \t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n \t\tif (wm == WM_MATCH) {\n-\t\t\ttrace2_def_param_fl(data->file, data->line, key, value);\n+\t\t\ttrace2_def_param_fl(data->file, data->line, key, value,\n+\t\t\t\t\t    ctx->kvi);\n \t\t\treturn 0;\n \t\t}\n \t}\n@@ -127,8 +128,10 @@ void tr2_cfg_list_config_fl(const char *file, int line)\n \n void tr2_list_env_vars_fl(const char *file, int line)\n {\n+\tstruct key_value_info kvi = KVI_INIT;\n \tstruct strbuf **s;\n \n+\tkvi_from_param(&kvi);\n \tif (tr2_load_env_vars() <= 0)\n \t\treturn;\n \n@@ -136,7 +139,7 @@ void tr2_list_env_vars_fl(const char *file, int line)\n \t\tstruct strbuf *buf = *s;\n \t\tconst char *val = getenv(buf->buf);\n \t\tif (val && *val)\n-\t\t\ttrace2_def_param_fl(file, line, buf->buf, val);\n+\t\t\ttrace2_def_param_fl(file, line, buf->buf, val, &kvi);\n \t}\n }\n \ndiff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h\nindex bf8745c4f05..1f626cffea0 100644\n--- a/trace2/tr2_tgt.h\n+++ b/trace2/tr2_tgt.h\n@@ -69,8 +69,10 @@ typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, int line,\n \t\t\t\t\t   uint64_t us_elapsed_absolute,\n \t\t\t\t\t   int exec_id, int code);\n \n+struct key_value_info;\n typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,\n-\t\t\t\t     const char *param, const char *value);\n+\t\t\t\t     const char *param, const char *value,\n+\t\t\t\t     const struct key_value_info *kvi);\n \n typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,\n \t\t\t\t    const struct repository *repo);\ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nindex 2af53e5d4de..53091781eca 100644\n--- a/trace2/tr2_tgt_event.c\n+++ b/trace2/tr2_tgt_event.c\n@@ -477,11 +477,11 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, const struct key_value_info *kvi)\n {\n \tconst char *event_name = \"def_param\";\n \tstruct json_writer jw = JSON_WRITER_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tjw_object_begin(&jw, 0);\ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nindex 1ebfb464d54..d25ea131643 100644\n--- a/trace2/tr2_tgt_normal.c\n+++ b/trace2/tr2_tgt_normal.c\n@@ -297,10 +297,10 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, const struct key_value_info *kvi)\n {\n \tstruct strbuf buf_payload = STRBUF_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tstrbuf_addf(&buf_payload, \"def_param scope:%s %s=%s\", scope_name, param,\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nindex 328e483a05e..a6f9a8a193e 100644\n--- a/trace2/tr2_tgt_perf.c\n+++ b/trace2/tr2_tgt_perf.c\n@@ -439,12 +439,12 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, const struct key_value_info *kvi)\n {\n \tconst char *event_name = \"def_param\";\n \tstruct strbuf buf_payload = STRBUF_INIT;\n \tstruct strbuf scope_payload = STRBUF_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tstrbuf_addf(&buf_payload, \"%s:%s\", param, value);\n-- \ngitgitgadget\n\n"},{"id":"478586","messageId":"8ec24b018e9a8a0767b1811aeb604eb97647bb4e.1687290233.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"[PATCH v3 10/12] config.c: remove config_reader from configsets","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-20T19:43:49Z","receivedAt":"2023-06-20T19:44:41Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nRemove the last usage of \"struct config_reader\" from configsets by\ncopying the \"kvi\" arg instead of recomputing \"kvi\" from\nconfig_reader.source. Since we no longer need to pass both \"struct\nconfig_reader\" and \"struct config_set\" in a single \"void *cb\", remove\n\"struct configset_add_data\" too.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 42 ++++++++++--------------------------------\n 1 file changed, 10 insertions(+), 32 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex a2887b856aa..958ba166cf9 100644\n--- a/config.c\n+++ b/config.c\n@@ -2303,8 +2303,7 @@ int config_with_options(config_fn_t fn, void *data,\n \treturn ret;\n }\n \n-static void configset_iter(struct config_reader *reader, struct config_set *set,\n-\t\t\t   config_fn_t fn, void *data)\n+static void configset_iter(struct config_set *set, config_fn_t fn, void *data)\n {\n \tint i, value_index;\n \tstruct string_list *values;\n@@ -2398,7 +2397,6 @@ static int configset_find_element(struct config_set *set, const char *key,\n }\n \n static int configset_add_value(const struct key_value_info *kvi_p,\n-\t\t\t       struct config_reader *reader,\n \t\t\t       struct config_set *set, const char *key,\n \t\t\t       const char *value)\n {\n@@ -2429,11 +2427,7 @@ static int configset_add_value(const struct key_value_info *kvi_p,\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (reader->source->name) {\n-\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n-\t} else {\n-\t\tkvi_from_param(kv_info);\n-\t}\n+\tmemcpy(kv_info, kvi_p, sizeof(struct key_value_info));\n \tsi->util = kv_info;\n \n \treturn 0;\n@@ -2481,28 +2475,18 @@ void git_configset_clear(struct config_set *set)\n \tset->list.items = NULL;\n }\n \n-struct configset_add_data {\n-\tstruct config_set *config_set;\n-\tstruct config_reader *config_reader;\n-};\n-#define CONFIGSET_ADD_INIT { 0 }\n-\n static int config_set_callback(const char *key, const char *value,\n \t\t\t       const struct config_context *ctx,\n \t\t\t       void *cb)\n {\n-\tstruct configset_add_data *data = cb;\n-\tconfigset_add_value(ctx->kvi, data->config_reader, data->config_set,\n-\t\t\t    key, value);\n+\tstruct config_set *set = cb;\n+\tconfigset_add_value(ctx->kvi, set, key, value);\n \treturn 0;\n }\n \n int git_configset_add_file(struct config_set *set, const char *filename)\n {\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n-\tdata.config_reader = &the_reader;\n-\tdata.config_set = set;\n-\treturn git_config_from_file(config_set_callback, filename, &data);\n+\treturn git_config_from_file(config_set_callback, filename, set);\n }\n \n int git_configset_get_value(struct config_set *set, const char *key,\n@@ -2668,7 +2652,6 @@ int git_configset_get_pathname(struct config_set *set, const char *key, const ch\n static void repo_read_config(struct repository *repo)\n {\n \tstruct config_options opts = { 0 };\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n \n \topts.respect_includes = 1;\n \topts.commondir = repo->commondir;\n@@ -2680,10 +2663,8 @@ static void repo_read_config(struct repository *repo)\n \t\tgit_configset_clear(repo->config);\n \n \tgit_configset_init(repo->config);\n-\tdata.config_set = repo->config;\n-\tdata.config_reader = &the_reader;\n-\n-\tif (config_with_options(config_set_callback, &data, NULL, &opts) < 0)\n+\tif (config_with_options(config_set_callback, repo->config, NULL,\n+\t\t\t\t&opts) < 0)\n \t\t/*\n \t\t * config_with_options() normally returns only\n \t\t * zero, as most errors are fatal, and\n@@ -2715,7 +2696,7 @@ static void repo_config_clear(struct repository *repo)\n void repo_config(struct repository *repo, config_fn_t fn, void *data)\n {\n \tgit_config_check_init(repo);\n-\tconfigset_iter(&the_reader, repo->config, fn, data);\n+\tconfigset_iter(repo->config, fn, data);\n }\n \n int repo_config_get(struct repository *repo, const char *key)\n@@ -2822,19 +2803,16 @@ static void read_protected_config(void)\n \t\t.ignore_worktree = 1,\n \t\t.system_gently = 1,\n \t};\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n \n \tgit_configset_init(&protected_config);\n-\tdata.config_set = &protected_config;\n-\tdata.config_reader = &the_reader;\n-\tconfig_with_options(config_set_callback, &data, NULL, &opts);\n+\tconfig_with_options(config_set_callback, &protected_config, NULL, &opts);\n }\n \n void git_protected_config(config_fn_t fn, void *data)\n {\n \tif (!protected_config.hash_initialized)\n \t\tread_protected_config();\n-\tconfigset_iter(&the_reader, &protected_config, fn, data);\n+\tconfigset_iter(&protected_config, fn, data);\n }\n \n /* Functions used historically to read configuration from 'the_repository' */\n-- \ngitgitgadget\n\n"},{"id":"478589","messageId":"d3eb439aad28b46acc8c7a370da82f95de37abbf.1687290232.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"[PATCH v3 03/12] config: add ctx arg to config_fn_t","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-20T19:43:42Z","receivedAt":"2023-06-20T19:44:42Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nAdd a new \"const struct config_context *ctx\" arg to config_fn_t to hold\nadditional information about the config iteration operation.\nconfig_context has a \"struct key_value_info kvi\" member that holds\nmetadata about the config source being read (e.g. what kind of config\nsource it is, the filename, etc). In this series, we're only interested\nin .kvi, so we could have just used \"struct key_value_info\" as an arg,\nbut config_context makes it possible to add/adjust members in the future\nwithout changing the config_fn_t signature. We could also consider other\nways of organizing the args (e.g. moving the config name and value into\nconfig_context or key_value_info), but in my experiments, the\nincremental benefit doesn't justify the added complexity (e.g. a\nconfig_fn_t will sometimes invoke another config_fn_t but with a\ndifferent config value).\n\nIn subsequent commits, the .kvi member will replace the global \"struct\nconfig_reader\" in config.c, making config iteration a global-free\noperation. It requires much more work for the machinery to provide\nmeaningful values of .kvi, so for now, merely change the signature and\ncall sites, pass NULL as a placeholder value, and don't rely on the arg\nin any meaningful way.\n\nMost of the changes are performed by\ncontrib/coccinelle/config_fn_ctx.pending.cocci, which, for every\nconfig_fn_t:\n\n- Modifies the signature to accept \"const struct config_context *ctx\"\n- Passes \"ctx\" to any inner config_fn_t, if needed\n- Adds UNUSED attributes to \"ctx\", if needed\n\nMost config_fn_t instances are easily identified by seeing if they are\ncalled by the various config functions. Most of the remaining ones are\nmanually named in the .cocci patch. Manual cleanups are still needed,\nbut the majority of it is trivial; it's either adjusting config_fn_t\nthat the .cocci patch didn't catch, or adding forward declarations of\n\"struct config_context ctx\" to make the signatures make sense.\n\nThe non-trivial changes are in cases where we are invoking a config_fn_t\noutside of config machinery, and we now need to decide what value of\n\"ctx\" to pass. These cases are:\n\n- trace2/tr2_cfg.c:tr2_cfg_set_fl()\n\n  This is indirectly called by git_config_set() so that the trace2\n  machinery can notice the new config values and update its settings\n  using the tr2 config parsing function, i.e. tr2_cfg_cb().\n\n- builtin/checkout.c:checkout_main()\n\n  This calls git_xmerge_config() as a shorthand for parsing a CLI arg.\n  This might be worth refactoring away in the future, since\n  git_xmerge_config() can call git_default_config(), which can do much\n  more than just parsing.\n\nHandle them by creating a KVI_INIT macro that initializes \"struct\nkey_value_info\" to a reasonable default, and use that to construct the\n\"ctx\" arg.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n alias.c                                       |   3 +-\n archive-tar.c                                 |   3 +-\n archive-zip.c                                 |   1 +\n builtin/add.c                                 |   5 +-\n builtin/blame.c                               |   5 +-\n builtin/branch.c                              |   5 +-\n builtin/cat-file.c                            |   5 +-\n builtin/checkout.c                            |  12 +-\n builtin/clean.c                               |   5 +-\n builtin/clone.c                               |  11 +-\n builtin/column.c                              |   3 +-\n builtin/commit-graph.c                        |   1 +\n builtin/commit.c                              |  10 +-\n builtin/config.c                              |  10 +-\n builtin/difftool.c                            |   5 +-\n builtin/fetch.c                               |   9 +-\n builtin/fsmonitor--daemon.c                   |   5 +-\n builtin/grep.c                                |   7 +-\n builtin/help.c                                |   5 +-\n builtin/index-pack.c                          |   5 +-\n builtin/log.c                                 |  10 +-\n builtin/merge.c                               |   7 +-\n builtin/multi-pack-index.c                    |   1 +\n builtin/pack-objects.c                        |   5 +-\n builtin/patch-id.c                            |   5 +-\n builtin/pull.c                                |   5 +-\n builtin/push.c                                |   5 +-\n builtin/read-tree.c                           |   5 +-\n builtin/rebase.c                              |   5 +-\n builtin/receive-pack.c                        |   5 +-\n builtin/reflog.c                              |   7 +-\n builtin/remote.c                              |   7 +-\n builtin/repack.c                              |   5 +-\n builtin/reset.c                               |   5 +-\n builtin/send-pack.c                           |   5 +-\n builtin/show-branch.c                         |   5 +-\n builtin/stash.c                               |   5 +-\n builtin/submodule--helper.c                   |   1 +\n builtin/tag.c                                 |   5 +-\n builtin/var.c                                 |   5 +-\n builtin/worktree.c                            |   5 +-\n bundle-uri.c                                  |   8 +-\n compat/mingw.c                                |   3 +-\n compat/mingw.h                                |   4 +-\n config.c                                      |  38 +++--\n config.h                                      |  41 +++--\n connect.c                                     |   4 +-\n .../coccinelle/config_fn_ctx.pending.cocci    | 144 ++++++++++++++++++\n convert.c                                     |   4 +-\n credential.c                                  |   1 +\n delta-islands.c                               |   4 +-\n diff.c                                        |  10 +-\n diff.h                                        |   7 +-\n fetch-pack.c                                  |   5 +-\n fmt-merge-msg.c                               |   5 +-\n fmt-merge-msg.h                               |   3 +-\n fsck.c                                        |   9 +-\n fsck.h                                        |   4 +-\n git-compat-util.h                             |   2 +\n gpg-interface.c                               |   7 +-\n grep.c                                        |   7 +-\n grep.h                                        |   4 +-\n help.c                                        |   7 +-\n http.c                                        |   5 +-\n ident.c                                       |   4 +-\n ident.h                                       |   4 +-\n imap-send.c                                   |   5 +-\n ll-merge.c                                    |   1 +\n ls-refs.c                                     |   1 +\n mailinfo.c                                    |   5 +-\n notes-utils.c                                 |   4 +-\n notes.c                                       |   4 +-\n pager.c                                       |   5 +-\n pretty.c                                      |   1 +\n promisor-remote.c                             |   4 +-\n remote.c                                      |   3 +-\n revision.c                                    |   4 +-\n scalar.c                                      |   4 +-\n sequencer.c                                   |   9 +-\n setup.c                                       |  16 +-\n submodule-config.c                            |  17 ++-\n t/helper/test-config.c                        |  11 +-\n t/helper/test-userdiff.c                      |   4 +-\n trace2/tr2_cfg.c                              |   9 +-\n trace2/tr2_sysenv.c                           |   3 +-\n trailer.c                                     |   2 +\n upload-pack.c                                 |   8 +-\n urlmatch.c                                    |   7 +-\n urlmatch.h                                    |   3 +-\n xdiff-interface.c                             |   5 +-\n xdiff-interface.h                             |   4 +-\n 91 files changed, 515 insertions(+), 181 deletions(-)\n create mode 100644 contrib/coccinelle/config_fn_ctx.pending.cocci\n\ndiff --git a/alias.c b/alias.c\nindex 54a1a23d2cf..910dd252a01 100644\n--- a/alias.c\n+++ b/alias.c\n@@ -12,7 +12,8 @@ struct config_alias_data {\n \tstruct string_list *list;\n };\n \n-static int config_alias_cb(const char *key, const char *value, void *d)\n+static int config_alias_cb(const char *key, const char *value,\n+\t\t\t   const struct config_context *ctx UNUSED, void *d)\n {\n \tstruct config_alias_data *data = d;\n \tconst char *p;\ndiff --git a/archive-tar.c b/archive-tar.c\nindex 4cd81d8161e..ef06e516b1f 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -411,7 +411,8 @@ static int tar_filter_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-static int git_tar_config(const char *var, const char *value, void *cb)\n+static int git_tar_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n {\n \tif (!strcmp(var, \"tar.umask\")) {\n \t\tif (value && !strcmp(value, \"user\")) {\ndiff --git a/archive-zip.c b/archive-zip.c\nindex d0d065a312e..b6811951955 100644\n--- a/archive-zip.c\n+++ b/archive-zip.c\n@@ -617,6 +617,7 @@ static void dos_time(timestamp_t *timestamp, int *dos_date, int *dos_time)\n }\n \n static int archive_zip_config(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx UNUSED,\n \t\t\t      void *data UNUSED)\n {\n \treturn userdiff_config(var, value);\ndiff --git a/builtin/add.c b/builtin/add.c\nindex 76cc026a68a..23a93b71d93 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -357,7 +357,8 @@ static struct option builtin_add_options[] = {\n \tOPT_END(),\n };\n \n-static int add_config(const char *var, const char *value, void *cb)\n+static int add_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"add.ignoreerrors\") ||\n \t    !strcmp(var, \"add.ignore-errors\")) {\n@@ -365,7 +366,7 @@ static int add_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static const char embedded_advice[] = N_(\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 2df6039a6e0..d0970a1ab13 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -694,7 +694,8 @@ static const char *add_prefix(const char *prefix, const char *path)\n \treturn prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n }\n \n-static int git_blame_config(const char *var, const char *value, void *cb)\n+static int git_blame_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"blame.showroot\")) {\n \t\tshow_root = git_config_bool(var, value);\n@@ -767,7 +768,7 @@ static int git_blame_config(const char *var, const char *value, void *cb)\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int blame_copy_callback(const struct option *option, const char *arg, int unset)\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex df99e38847b..23bfb4a771a 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -83,7 +83,8 @@ static unsigned int colopts;\n \n define_list_config_array(color_branch_slots);\n \n-static int git_branch_config(const char *var, const char *value, void *cb)\n+static int git_branch_config(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -120,7 +121,7 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static const char *branch_get_color(enum color_branch ix)\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 0bafc14e6c0..a7d88c055a0 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -885,12 +885,13 @@ static int batch_objects(struct batch_options *opt)\n \treturn retval;\n }\n \n-static int git_cat_file_config(const char *var, const char *value, void *cb)\n+static int git_cat_file_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int batch_option_callback(const struct option *opt,\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 715eeb5048f..4e1f7dc26c2 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1186,7 +1186,8 @@ static int switch_branches(const struct checkout_opts *opts,\n \treturn ret || writeout_error;\n }\n \n-static int git_checkout_config(const char *var, const char *value, void *cb)\n+static int git_checkout_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tstruct checkout_opts *opts = cb;\n \n@@ -1202,7 +1203,7 @@ static int git_checkout_config(const char *var, const char *value, void *cb)\n \tif (starts_with(var, \"submodule.\"))\n \t\treturn git_default_submodule_config(var, value, NULL);\n \n-\treturn git_xmerge_config(var, value, NULL);\n+\treturn git_xmerge_config(var, value, ctx, NULL);\n }\n \n static void setup_new_branch_info_and_source_tree(\n@@ -1689,8 +1690,13 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t}\n \n \tif (opts->conflict_style) {\n+\t\tstruct key_value_info kvi = KVI_INIT;\n+\t\tstruct config_context ctx = {\n+\t\t\t.kvi = &kvi,\n+\t\t};\n \t\topts->merge = 1; /* implied */\n-\t\tgit_xmerge_config(\"merge.conflictstyle\", opts->conflict_style, NULL);\n+\t\tgit_xmerge_config(\"merge.conflictstyle\", opts->conflict_style,\n+\t\t\t\t  &ctx, NULL);\n \t}\n \tif (opts->force) {\n \t\topts->discard_changes = 1;\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 57e7f7cac64..5eff1b802a7 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -103,7 +103,8 @@ struct menu_stuff {\n \n define_list_config_array(color_interactive_slots);\n \n-static int git_clean_config(const char *var, const char *value, void *cb)\n+static int git_clean_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -133,7 +134,7 @@ static int git_clean_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static const char *clean_get_color(enum color_clean ix)\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 15f9912b4ca..c0f6e067493 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -790,7 +790,8 @@ static int checkout(int submodule_progress, int filter_submodules)\n \treturn err;\n }\n \n-static int git_clone_config(const char *k, const char *v, void *cb)\n+static int git_clone_config(const char *k, const char *v,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(k, \"clone.defaultremotename\")) {\n \t\tfree(remote_name);\n@@ -801,17 +802,19 @@ static int git_clone_config(const char *k, const char *v, void *cb)\n \tif (!strcmp(k, \"clone.filtersubmodules\"))\n \t\tconfig_filter_submodules = git_config_bool(k, v);\n \n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n-static int write_one_config(const char *key, const char *value, void *data)\n+static int write_one_config(const char *key, const char *value,\n+\t\t\t    const struct config_context *ctx,\n+\t\t\t    void *data)\n {\n \t/*\n \t * give git_clone_config a chance to write config values back to the\n \t * environment, since git_config_set_multivar_gently only deals with\n \t * config-file writes\n \t */\n-\tint apply_failed = git_clone_config(key, value, data);\n+\tint apply_failed = git_clone_config(key, value, ctx, data);\n \tif (apply_failed)\n \t\treturn apply_failed;\n \ndiff --git a/builtin/column.c b/builtin/column.c\nindex de623a16c2d..4a6148ca479 100644\n--- a/builtin/column.c\n+++ b/builtin/column.c\n@@ -13,7 +13,8 @@ static const char * const builtin_column_usage[] = {\n };\n static unsigned int colopts;\n \n-static int column_config(const char *var, const char *value, void *cb)\n+static int column_config(const char *var, const char *value,\n+\t\t\t const struct config_context *ctx UNUSED, void *cb)\n {\n \treturn git_column_config(var, value, cb, &colopts);\n }\ndiff --git a/builtin/commit-graph.c b/builtin/commit-graph.c\nindex a3d00fa232b..13d35b00ca8 100644\n--- a/builtin/commit-graph.c\n+++ b/builtin/commit-graph.c\n@@ -186,6 +186,7 @@ static int write_option_max_new_filters(const struct option *opt,\n }\n \n static int git_commit_graph_write_config(const char *var, const char *value,\n+\t\t\t\t\t const struct config_context *ctx UNUSED,\n \t\t\t\t\t void *cb UNUSED)\n {\n \tif (!strcmp(var, \"commitgraph.maxnewfilters\"))\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex e67c4be2211..3bc87e5fc97 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1405,7 +1405,8 @@ static int parse_status_slot(const char *slot)\n \treturn LOOKUP_CONFIG(color_status_slots, slot);\n }\n \n-static int git_status_config(const char *k, const char *v, void *cb)\n+static int git_status_config(const char *k, const char *v,\n+\t\t\t     const struct config_context *ctx, void *cb)\n {\n \tstruct wt_status *s = cb;\n \tconst char *slot_name;\n@@ -1490,7 +1491,7 @@ static int git_status_config(const char *k, const char *v, void *cb)\n \t\ts->detect_rename = git_config_rename(k, v);\n \t\treturn 0;\n \t}\n-\treturn git_diff_ui_config(k, v, NULL);\n+\treturn git_diff_ui_config(k, v, ctx, NULL);\n }\n \n int cmd_status(int argc, const char **argv, const char *prefix)\n@@ -1605,7 +1606,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)\n \treturn 0;\n }\n \n-static int git_commit_config(const char *k, const char *v, void *cb)\n+static int git_commit_config(const char *k, const char *v,\n+\t\t\t     const struct config_context *ctx, void *cb)\n {\n \tstruct wt_status *s = cb;\n \n@@ -1627,7 +1629,7 @@ static int git_commit_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_status_config(k, v, s);\n+\treturn git_status_config(k, v, ctx, s);\n }\n \n int cmd_commit(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/config.c b/builtin/config.c\nindex ff2fe8ef125..e14f967bbea 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -216,6 +216,7 @@ static void show_config_scope(struct strbuf *buf)\n }\n \n static int show_all_config(const char *key_, const char *value_,\n+\t\t\t   const struct config_context *ctx UNUSED,\n \t\t\t   void *cb UNUSED)\n {\n \tif (show_origin || show_scope) {\n@@ -300,7 +301,8 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value\n \treturn 0;\n }\n \n-static int collect_config(const char *key_, const char *value_, void *cb)\n+static int collect_config(const char *key_, const char *value_,\n+\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n {\n \tstruct strbuf_list *values = cb;\n \n@@ -468,6 +470,7 @@ static const char *get_colorbool_slot;\n static char parsed_color[COLOR_MAXLEN];\n \n static int git_get_color_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n \t\t\t\tvoid *cb UNUSED)\n {\n \tif (!strcmp(var, get_color_slot)) {\n@@ -500,6 +503,7 @@ static int get_colorbool_found;\n static int get_diff_color_found;\n static int get_color_ui_found;\n static int git_get_colorbool_config(const char *var, const char *value,\n+\t\t\t\t    const struct config_context *ctx UNUSED,\n \t\t\t\t    void *data UNUSED)\n {\n \tif (!strcmp(var, get_colorbool_slot))\n@@ -557,7 +561,9 @@ struct urlmatch_current_candidate_value {\n \tstruct strbuf value;\n };\n \n-static int urlmatch_collect_fn(const char *var, const char *value, void *cb)\n+static int urlmatch_collect_fn(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       void *cb)\n {\n \tstruct string_list *values = cb;\n \tstruct string_list_item *item = string_list_insert(values, var);\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 0049342f5c0..f289530068b 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -40,14 +40,15 @@ static const char *const builtin_difftool_usage[] = {\n \tNULL\n };\n \n-static int difftool_config(const char *var, const char *value, void *cb)\n+static int difftool_config(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"difftool.trustexitcode\")) {\n \t\ttrust_exit_code = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int print_tool_help(void)\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 849a9be421d..b607605b491 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -111,7 +111,8 @@ struct fetch_config {\n \tenum display_format display_format;\n };\n \n-static int git_fetch_config(const char *k, const char *v, void *cb)\n+static int git_fetch_config(const char *k, const char *v,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tstruct fetch_config *fetch_config = cb;\n \n@@ -165,7 +166,7 @@ static int git_fetch_config(const char *k, const char *v, void *cb)\n \t\t\t    \"fetch.output\", v);\n \t}\n \n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n static int parse_refmap_arg(const struct option *opt, const char *arg, int unset)\n@@ -1794,7 +1795,9 @@ struct remote_group_data {\n \tstruct string_list *list;\n };\n \n-static int get_remote_group(const char *key, const char *value, void *priv)\n+static int get_remote_group(const char *key, const char *value,\n+\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    void *priv)\n {\n \tstruct remote_group_data *g = priv;\n \ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex f6dd9a784c1..91a776e2f17 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -37,7 +37,8 @@ static int fsmonitor__start_timeout_sec = 60;\n #define FSMONITOR__ANNOUNCE_STARTUP \"fsmonitor.announcestartup\"\n static int fsmonitor__announce_startup = 0;\n \n-static int fsmonitor_config(const char *var, const char *value, void *cb)\n+static int fsmonitor_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, FSMONITOR__IPC_THREADS)) {\n \t\tint i = git_config_int(var, value);\n@@ -67,7 +68,7 @@ static int fsmonitor_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n /*\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 76cf999d310..757d52b94ec 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -290,13 +290,14 @@ static int wait_all(void)\n \treturn hit;\n }\n \n-static int grep_cmd_config(const char *var, const char *value, void *cb)\n+static int grep_cmd_config(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n-\tint st = grep_config(var, value, cb);\n+\tint st = grep_config(var, value, ctx, cb);\n \n \tif (git_color_config(var, value, cb) < 0)\n \t\tst = -1;\n-\telse if (git_default_config(var, value, cb) < 0)\n+\telse if (git_default_config(var, value, ctx, cb) < 0)\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\ndiff --git a/builtin/help.c b/builtin/help.c\nindex d3cf4af3f6e..c348f201254 100644\n--- a/builtin/help.c\n+++ b/builtin/help.c\n@@ -398,7 +398,8 @@ static int add_man_viewer_info(const char *var, const char *value)\n \treturn 0;\n }\n \n-static int git_help_config(const char *var, const char *value, void *cb)\n+static int git_help_config(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"help.format\")) {\n \t\tif (!value)\n@@ -421,7 +422,7 @@ static int git_help_config(const char *var, const char *value, void *cb)\n \tif (starts_with(var, \"man.\"))\n \t\treturn add_man_viewer_info(var, value);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static struct cmdnames main_cmds, other_cmds;\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex bb67e166559..31914aac298 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1581,7 +1581,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name,\n \tstrbuf_release(&pack_name);\n }\n \n-static int git_index_pack_config(const char *k, const char *v, void *cb)\n+static int git_index_pack_config(const char *k, const char *v,\n+\t\t\t\t const struct config_context *ctx, void *cb)\n {\n \tstruct pack_idx_option *opts = cb;\n \n@@ -1608,7 +1609,7 @@ static int git_index_pack_config(const char *k, const char *v, void *cb)\n \t\telse\n \t\t\topts->flags &= ~WRITE_REV;\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n static int cmp_uint32(const void *a_, const void *b_)\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 4c45a47ecf4..ac5f74b6b06 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -564,7 +564,8 @@ static int cmd_log_walk(struct rev_info *rev)\n \treturn retval;\n }\n \n-static int git_log_config(const char *var, const char *value, void *cb)\n+static int git_log_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -613,7 +614,7 @@ static int git_log_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_diff_ui_config(var, value, cb);\n+\treturn git_diff_ui_config(var, value, ctx, cb);\n }\n \n int cmd_whatchanged(int argc, const char **argv, const char *prefix)\n@@ -979,7 +980,8 @@ static enum cover_from_description parse_cover_from_description(const char *arg)\n \t\tdie(_(\"%s: invalid cover from description mode\"), arg);\n }\n \n-static int git_format_config(const char *var, const char *value, void *cb)\n+static int git_format_config(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"format.headers\")) {\n \t\tif (!value)\n@@ -1108,7 +1110,7 @@ static int git_format_config(const char *var, const char *value, void *cb)\n \tif (!strcmp(var, \"diff.noprefix\"))\n \t\treturn 0;\n \n-\treturn git_log_config(var, value, cb);\n+\treturn git_log_config(var, value, ctx, cb);\n }\n \n static const char *output_directory = NULL;\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 8da3e46abb0..cad624fb797 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -623,7 +623,8 @@ static void parse_branch_merge_options(char *bmo)\n \tfree(argv);\n }\n \n-static int git_merge_config(const char *k, const char *v, void *cb)\n+static int git_merge_config(const char *k, const char *v,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tint status;\n \tconst char *str;\n@@ -668,10 +669,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\tstatus = fmt_merge_msg_config(k, v, cb);\n+\tstatus = fmt_merge_msg_config(k, v, ctx, cb);\n \tif (status)\n \t\treturn status;\n-\treturn git_diff_ui_config(k, v, cb);\n+\treturn git_diff_ui_config(k, v, ctx, cb);\n }\n \n static int read_tree_trivial(struct object_id *common, struct object_id *head,\ndiff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\nindex 1b5083f8b26..a0a7b82cc69 100644\n--- a/builtin/multi-pack-index.c\n+++ b/builtin/multi-pack-index.c\n@@ -82,6 +82,7 @@ static struct option *add_common_options(struct option *prev)\n }\n \n static int git_multi_pack_index_write_config(const char *var, const char *value,\n+\t\t\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t\t\t     void *cb UNUSED)\n {\n \tif (!strcmp(var, \"pack.writebitmaphashcache\")) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 9cfc8801f9b..ad4d8fafb0b 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3135,7 +3135,8 @@ static void prepare_pack(int window, int depth)\n \tfree(delta_list);\n }\n \n-static int git_pack_config(const char *k, const char *v, void *cb)\n+static int git_pack_config(const char *k, const char *v,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(k, \"pack.window\")) {\n \t\twindow = git_config_int(k, v);\n@@ -3227,7 +3228,7 @@ static int git_pack_config(const char *k, const char *v, void *cb)\n \t\tex->uri = xstrdup(pack_end + 1);\n \t\toidmap_put(&configured_exclusions, ex);\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n /* Counters for trace2 output when in --stdin-packs mode. */\ndiff --git a/builtin/patch-id.c b/builtin/patch-id.c\nindex 9d5585d3a72..03eddd0fb82 100644\n--- a/builtin/patch-id.c\n+++ b/builtin/patch-id.c\n@@ -196,7 +196,8 @@ struct patch_id_opts {\n \tint verbatim;\n };\n \n-static int git_patch_id_config(const char *var, const char *value, void *cb)\n+static int git_patch_id_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tstruct patch_id_opts *opts = cb;\n \n@@ -209,7 +210,7 @@ static int git_patch_id_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n int cmd_patch_id(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 0c7bac97b75..83fca5b1d46 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -361,7 +361,8 @@ static enum rebase_type config_get_rebase(int *rebase_unspecified)\n /**\n  * Read config variables.\n  */\n-static int git_pull_config(const char *var, const char *value, void *cb)\n+static int git_pull_config(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"rebase.autostash\")) {\n \t\tconfig_autostash = git_config_bool(var, value);\n@@ -374,7 +375,7 @@ static int git_pull_config(const char *var, const char *value, void *cb)\n \t\tcheck_trust_level = 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n /**\ndiff --git a/builtin/push.c b/builtin/push.c\nindex dbdf609daf3..a2f68f77324 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -510,7 +510,8 @@ static void set_push_cert_flags(int *flags, int v)\n }\n \n \n-static int git_push_config(const char *k, const char *v, void *cb)\n+static int git_push_config(const char *k, const char *v,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tconst char *slot_name;\n \tint *flags = cb;\n@@ -577,7 +578,7 @@ static int git_push_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(k, v, NULL);\n+\treturn git_default_config(k, v, ctx, NULL);\n }\n \n int cmd_push(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/read-tree.c b/builtin/read-tree.c\nindex 440f19b1b87..8877dd6d4b5 100644\n--- a/builtin/read-tree.c\n+++ b/builtin/read-tree.c\n@@ -102,12 +102,13 @@ static int debug_merge(const struct cache_entry * const *stages,\n \treturn 0;\n }\n \n-static int git_read_tree_config(const char *var, const char *value, void *cb)\n+static int git_read_tree_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"submodule.recurse\"))\n \t\treturn git_default_submodule_config(var, value, cb);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ace1d5e8d11..60930e2d8e0 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -772,7 +772,8 @@ static void parse_rebase_merges_value(struct rebase_options *options, const char\n \t\tdie(_(\"Unknown rebase-merges mode: %s\"), value);\n }\n \n-static int rebase_config(const char *var, const char *value, void *data)\n+static int rebase_config(const char *var, const char *value,\n+\t\t\t const struct config_context *ctx, void *data)\n {\n \tstruct rebase_options *opts = data;\n \n@@ -831,7 +832,7 @@ static int rebase_config(const char *var, const char *value, void *data)\n \t\treturn git_config_string(&opts->default_backend, var, value);\n \t}\n \n-\treturn git_default_config(var, value, data);\n+\treturn git_default_config(var, value, ctx, data);\n }\n \n static int checkout_up_to_date(struct rebase_options *options)\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 1a31a583674..94d9898aff7 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -139,7 +139,8 @@ static enum deny_action parse_deny_action(const char *var, const char *value)\n \treturn DENY_IGNORE;\n }\n \n-static int receive_pack_config(const char *var, const char *value, void *cb)\n+static int receive_pack_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tint status = parse_hide_refs_config(var, value, \"receive\", &hidden_refs);\n \n@@ -266,7 +267,7 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static void show_ref(const char *path, const struct object_id *oid)\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex a1fa0c855f4..84251cc9517 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -108,7 +108,8 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)\n #define EXPIRE_TOTAL   01\n #define EXPIRE_UNREACH 02\n \n-static int reflog_expire_config(const char *var, const char *value, void *cb)\n+static int reflog_expire_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx, void *cb)\n {\n \tconst char *pattern, *key;\n \tsize_t pattern_len;\n@@ -117,7 +118,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)\n \tstruct reflog_expire_cfg *ent;\n \n \tif (parse_config_key(var, \"gc\", &pattern, &pattern_len, &key) < 0)\n-\t\treturn git_default_config(var, value, cb);\n+\t\treturn git_default_config(var, value, ctx, cb);\n \n \tif (!strcmp(key, \"reflogexpire\")) {\n \t\tslot = EXPIRE_TOTAL;\n@@ -128,7 +129,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)\n \t\tif (git_config_expiry_date(&expire, var, value))\n \t\t\treturn -1;\n \t} else\n-\t\treturn git_default_config(var, value, cb);\n+\t\treturn git_default_config(var, value, ctx, cb);\n \n \tif (!pattern) {\n \t\tswitch (slot) {\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 1e0b137d977..87de81105e2 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -268,6 +268,7 @@ static const char *abbrev_ref(const char *name, const char *prefix)\n #define abbrev_branch(name) abbrev_ref((name), \"refs/heads/\")\n \n static int config_read_branches(const char *key, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n \t\t\t\tvoid *data UNUSED)\n {\n \tconst char *orig_key = key;\n@@ -645,7 +646,7 @@ struct push_default_info\n };\n \n static int config_read_push_default(const char *key, const char *value,\n-\tvoid *cb)\n+\tconst struct config_context *ctx UNUSED, void *cb)\n {\n \tstruct push_default_info* info = cb;\n \tif (strcmp(key, \"remote.pushdefault\") ||\n@@ -1494,7 +1495,9 @@ static int prune(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n-static int get_remote_default(const char *key, const char *value UNUSED, void *priv)\n+static int get_remote_default(const char *key, const char *value UNUSED,\n+\t\t\t      const struct config_context *ctx UNUSED,\n+\t\t\t      void *priv)\n {\n \tif (strcmp(key, \"remotes.default\") == 0) {\n \t\tint *found = priv;\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 0541c3ce157..6f74570bf94 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -59,7 +59,8 @@ struct pack_objects_args {\n \tint local;\n };\n \n-static int repack_config(const char *var, const char *value, void *cb)\n+static int repack_config(const char *var, const char *value,\n+\t\t\t const struct config_context *ctx, void *cb)\n {\n \tstruct pack_objects_args *cruft_po_args = cb;\n \tif (!strcmp(var, \"repack.usedeltabaseoffset\")) {\n@@ -91,7 +92,7 @@ static int repack_config(const char *var, const char *value, void *cb)\n \t\treturn git_config_string(&cruft_po_args->depth, var, value);\n \tif (!strcmp(var, \"repack.cruftthreads\"))\n \t\treturn git_config_string(&cruft_po_args->threads, var, value);\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n /*\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex f99f32d5802..1ae82f2e89c 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -312,12 +312,13 @@ static int reset_refs(const char *rev, const struct object_id *oid)\n \treturn update_ref_status;\n }\n \n-static int git_reset_config(const char *var, const char *value, void *cb)\n+static int git_reset_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"submodule.recurse\"))\n \t\treturn git_default_submodule_config(var, value, cb);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n int cmd_reset(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 4784143004d..cd6d9e41129 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -131,7 +131,8 @@ static void print_helper_status(struct ref *ref)\n \tstrbuf_release(&buf);\n }\n \n-static int send_pack_config(const char *k, const char *v, void *cb)\n+static int send_pack_config(const char *k, const char *v,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(k, \"push.gpgsign\")) {\n \t\tconst char *value;\n@@ -151,7 +152,7 @@ static int send_pack_config(const char *k, const char *v, void *cb)\n \t\t\t}\n \t\t}\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n int cmd_send_pack(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex a2461270d4b..f2fd245b838 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -559,7 +559,8 @@ static void append_one_rev(const char *av)\n \tdie(\"bad sha1 reference %s\", av);\n }\n \n-static int git_show_branch_config(const char *var, const char *value, void *cb)\n+static int git_show_branch_config(const char *var, const char *value,\n+\t\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"showbranch.default\")) {\n \t\tif (!value)\n@@ -582,7 +583,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int omit_in_dense(struct commit *commit, struct commit **rev, int n)\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex a7e17ffe384..e5c4246d2d4 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -837,7 +837,8 @@ static int show_stat = 1;\n static int show_patch;\n static int show_include_untracked;\n \n-static int git_stash_config(const char *var, const char *value, void *cb)\n+static int git_stash_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"stash.showstat\")) {\n \t\tshow_stat = git_config_bool(var, value);\n@@ -851,7 +852,7 @@ static int git_stash_config(const char *var, const char *value, void *cb)\n \t\tshow_include_untracked = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\treturn git_diff_basic_config(var, value, cb);\n+\treturn git_diff_basic_config(var, value, ctx, cb);\n }\n \n static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 6bf8d666ce9..3f8f3692937 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2189,6 +2189,7 @@ static int update_clone_task_finished(int result,\n }\n \n static int git_update_clone_config(const char *var, const char *value,\n+\t\t\t\t   const struct config_context *ctx UNUSED,\n \t\t\t\t   void *cb)\n {\n \tint *max_jobs = cb;\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 1acf5f7a59f..b7dfb5e2cad 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -188,7 +188,8 @@ static const char tag_template_nocleanup[] =\n \t\"Lines starting with '%c' will be kept; you may remove them\"\n \t\" yourself if you want to.\\n\");\n \n-static int git_tag_config(const char *var, const char *value, void *cb)\n+static int git_tag_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"tag.gpgsign\")) {\n \t\tconfig_sign_tag = git_config_bool(var, value);\n@@ -213,7 +214,7 @@ static int git_tag_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static void write_tag_body(int fd, const struct object_id *oid)\ndiff --git a/builtin/var.c b/builtin/var.c\nindex 21499989807..ae011bdf409 100644\n--- a/builtin/var.c\n+++ b/builtin/var.c\n@@ -71,13 +71,14 @@ static const struct git_var *get_git_var(const char *var)\n \treturn NULL;\n }\n \n-static int show_config(const char *var, const char *value, void *cb)\n+static int show_config(const char *var, const char *value,\n+\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (value)\n \t\tprintf(\"%s=%s\\n\", var, value);\n \telse\n \t\tprintf(\"%s\\n\", var);\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n int cmd_var(int argc, const char **argv, const char *prefix UNUSED)\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex f3180463be2..d6f6d82bbec 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -107,14 +107,15 @@ static int verbose;\n static int guess_remote;\n static timestamp_t expire;\n \n-static int git_worktree_config(const char *var, const char *value, void *cb)\n+static int git_worktree_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"worktree.guessremote\")) {\n \t\tguess_remote = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int delete_git_dir(const char *id)\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex 2a2db1a1d39..0d5acc3dc51 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -224,7 +224,9 @@ static int bundle_list_update(const char *key, const char *value,\n \treturn 0;\n }\n \n-static int config_to_bundle_list(const char *key, const char *value, void *data)\n+static int config_to_bundle_list(const char *key, const char *value,\n+\t\t\t\t const struct config_context *ctx UNUSED,\n+\t\t\t\t void *data)\n {\n \tstruct bundle_list *list = data;\n \treturn bundle_list_update(key, value, list);\n@@ -871,7 +873,9 @@ cached:\n \treturn advertise_bundle_uri;\n }\n \n-static int config_to_packet_line(const char *key, const char *value, void *data)\n+static int config_to_packet_line(const char *key, const char *value,\n+\t\t\t\t const struct config_context *ctx UNUSED,\n+\t\t\t\t void *data)\n {\n \tstruct packet_reader *writer = data;\n \ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex d06cdc6254f..4186bc7a417 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -244,7 +244,8 @@ static int core_restrict_inherited_handles = -1;\n static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;\n static char *unset_environment_variables;\n \n-int mingw_core_config(const char *var, const char *value, void *cb)\n+int mingw_core_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"core.hidedotfiles\")) {\n \t\tif (value && !strcasecmp(value, \"dotgitonly\"))\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 209cf7cebad..5e34c873473 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -11,7 +11,9 @@ typedef _sigset_t sigset_t;\n #undef _POSIX_THREAD_SAFE_FUNCTIONS\n #endif\n \n-int mingw_core_config(const char *var, const char *value, void *cb);\n+struct config_context;\n+int mingw_core_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb);\n #define platform_core_config mingw_core_config\n \n /*\ndiff --git a/config.c b/config.c\nindex b79baf83e35..670d92cd76b 100644\n--- a/config.c\n+++ b/config.c\n@@ -208,7 +208,8 @@ struct config_include_data {\n };\n #define CONFIG_INCLUDE_INIT { 0 }\n \n-static int git_config_include(const char *var, const char *value, void *data);\n+static int git_config_include(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx, void *data);\n \n #define MAX_INCLUDE_DEPTH 10\n static const char include_depth_advice[] = N_(\n@@ -387,7 +388,8 @@ static int include_by_branch(const char *cond, size_t cond_len)\n \treturn ret;\n }\n \n-static int add_remote_url(const char *var, const char *value, void *data)\n+static int add_remote_url(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx UNUSED, void *data)\n {\n \tstruct string_list *remote_urls = data;\n \tconst char *remote_name;\n@@ -421,6 +423,7 @@ static void populate_remote_urls(struct config_include_data *inc)\n }\n \n static int forbid_remote_url(const char *var, const char *value UNUSED,\n+\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t     void *data UNUSED)\n {\n \tconst char *remote_name;\n@@ -484,7 +487,9 @@ static int include_condition_is_true(struct config_source *cs,\n \treturn 0;\n }\n \n-static int git_config_include(const char *var, const char *value, void *data)\n+static int git_config_include(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx,\n+\t\t\t      void *data)\n {\n \tstruct config_include_data *inc = data;\n \tstruct config_source *cs = inc->config_reader->source;\n@@ -496,7 +501,7 @@ static int git_config_include(const char *var, const char *value, void *data)\n \t * Pass along all values, including \"include\" directives; this makes it\n \t * possible to query information on the includes themselves.\n \t */\n-\tret = inc->fn(var, value, inc->data);\n+\tret = inc->fn(var, value, NULL, inc->data);\n \tif (ret < 0)\n \t\treturn ret;\n \n@@ -678,7 +683,7 @@ static int config_parse_pair(const char *key, const char *value,\n \tif (git_config_parse_key(key, &canonical_name, NULL))\n \t\treturn -1;\n \n-\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n+\tret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;\n \tfree(canonical_name);\n \treturn ret;\n }\n@@ -966,7 +971,7 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \t * accurate line number in error messages.\n \t */\n \tcs->linenr--;\n-\tret = fn(name->buf, value, data);\n+\tret = fn(name->buf, value, NULL, data);\n \tif (ret >= 0)\n \t\tcs->linenr++;\n \treturn ret;\n@@ -1560,7 +1565,8 @@ int git_config_color(char *dest, const char *var, const char *value)\n \treturn 0;\n }\n \n-static int git_default_core_config(const char *var, const char *value, void *cb)\n+static int git_default_core_config(const char *var, const char *value,\n+\t\t\t\t   const struct config_context *ctx, void *cb)\n {\n \t/* This needs a better name */\n \tif (!strcmp(var, \"core.filemode\")) {\n@@ -1845,7 +1851,7 @@ static int git_default_core_config(const char *var, const char *value, void *cb)\n \t}\n \n \t/* Add other config variables here and to Documentation/config.txt. */\n-\treturn platform_core_config(var, value, cb);\n+\treturn platform_core_config(var, value, ctx, cb);\n }\n \n static int git_default_sparse_config(const char *var, const char *value)\n@@ -1947,15 +1953,16 @@ static int git_default_mailmap_config(const char *var, const char *value)\n \treturn 0;\n }\n \n-int git_default_config(const char *var, const char *value, void *cb)\n+int git_default_config(const char *var, const char *value,\n+\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (starts_with(var, \"core.\"))\n-\t\treturn git_default_core_config(var, value, cb);\n+\t\treturn git_default_core_config(var, value, ctx, cb);\n \n \tif (starts_with(var, \"user.\") ||\n \t    starts_with(var, \"author.\") ||\n \t    starts_with(var, \"committer.\"))\n-\t\treturn git_ident_config(var, value, cb);\n+\t\treturn git_ident_config(var, value, ctx, cb);\n \n \tif (starts_with(var, \"i18n.\"))\n \t\treturn git_default_i18n_config(var, value);\n@@ -2310,7 +2317,7 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \n \t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n \n-\t\tif (fn(entry->key, values->items[value_index].string, data) < 0)\n+\t\tif (fn(entry->key, values->items[value_index].string, NULL, data) < 0)\n \t\t\tgit_die_config_linenr(entry->key,\n \t\t\t\t\t      reader->config_kvi->filename,\n \t\t\t\t\t      reader->config_kvi->linenr);\n@@ -2488,7 +2495,9 @@ struct configset_add_data {\n };\n #define CONFIGSET_ADD_INIT { 0 }\n \n-static int config_set_callback(const char *key, const char *value, void *cb)\n+static int config_set_callback(const char *key, const char *value,\n+\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       void *cb)\n {\n \tstruct configset_add_data *data = cb;\n \tconfigset_add_value(data->config_reader, data->config_set, key, value);\n@@ -3098,7 +3107,8 @@ static int store_aux_event(enum config_event_t type,\n \treturn 0;\n }\n \n-static int store_aux(const char *key, const char *value, void *cb)\n+static int store_aux(const char *key, const char *value,\n+\t\t     const struct config_context *ctx UNUSED, void *cb)\n {\n \tstruct config_store_data *store = cb;\n \ndiff --git a/config.h b/config.h\nindex 247b572b37b..bc52ceb72f2 100644\n--- a/config.h\n+++ b/config.h\n@@ -111,8 +111,29 @@ struct config_options {\n \t} error_action;\n };\n \n+/* Config source metadata for a given config key-value pair */\n+struct key_value_info {\n+\tconst char *filename;\n+\tint linenr;\n+\tenum config_origin_type origin_type;\n+\tenum config_scope scope;\n+};\n+#define KVI_INIT { \\\n+\t.filename = NULL, \\\n+\t.linenr = -1, \\\n+\t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n+\t.scope = CONFIG_SCOPE_UNKNOWN, \\\n+}\n+\n+/* Captures additional information that a config callback can use. */\n+struct config_context {\n+\t/* Config source metadata for key and value. */\n+\tconst struct key_value_info *kvi;\n+};\n+#define CONFIG_CONTEXT_INIT { 0 }\n+\n /**\n- * A config callback function takes three parameters:\n+ * A config callback function takes four parameters:\n  *\n  * - the name of the parsed variable. This is in canonical \"flat\" form: the\n  *   section, subsection, and variable segments will be separated by dots,\n@@ -123,15 +144,22 @@ struct config_options {\n  *   value specified, the value will be NULL (typically this means it\n  *   should be interpreted as boolean true).\n  *\n+ * - the 'config context', that is, additional information about the config\n+ *   iteration operation provided by the config machinery. For example, this\n+ *   includes information about the config source being parsed (e.g. the\n+ *   filename).\n+ *\n  * - a void pointer passed in by the caller of the config API; this can\n  *   contain callback-specific data\n  *\n  * A config callback should return 0 for success, or -1 if the variable\n  * could not be parsed properly.\n  */\n-typedef int (*config_fn_t)(const char *, const char *, void *);\n+typedef int (*config_fn_t)(const char *, const char *,\n+\t\t\t   const struct config_context *, void *);\n \n-int git_default_config(const char *, const char *, void *);\n+int git_default_config(const char *, const char *,\n+\t\t       const struct config_context *, void *);\n \n /**\n  * Read a specific file in git-config format.\n@@ -667,13 +695,6 @@ int git_config_get_expiry(const char *key, const char **output);\n /* parse either \"this many days\" integer, or \"5.days.ago\" approxidate */\n int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t now);\n \n-struct key_value_info {\n-\tconst char *filename;\n-\tint linenr;\n-\tenum config_origin_type origin_type;\n-\tenum config_scope scope;\n-};\n-\n /**\n  * First prints the error message specified by the caller in `err` and then\n  * dies printing the line number and the file name of the highest priority\ndiff --git a/connect.c b/connect.c\nindex 3a0186280c4..cddac1d96b8 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -964,7 +964,7 @@ static struct child_process *git_tcp_connect(int fd[2], char *host, int flags)\n static char *git_proxy_command;\n \n static int git_proxy_command_options(const char *var, const char *value,\n-\t\tvoid *cb)\n+\t\tconst struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"core.gitproxy\")) {\n \t\tconst char *for_pos;\n@@ -1010,7 +1010,7 @@ static int git_proxy_command_options(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int git_use_proxy(const char *host)\ndiff --git a/contrib/coccinelle/config_fn_ctx.pending.cocci b/contrib/coccinelle/config_fn_ctx.pending.cocci\nnew file mode 100644\nindex 00000000000..6d3d1000a96\n--- /dev/null\n+++ b/contrib/coccinelle/config_fn_ctx.pending.cocci\n@@ -0,0 +1,144 @@\n+@ get_fn @\n+identifier fn, R;\n+@@\n+(\n+(\n+git_config_from_file\n+|\n+git_config_from_file_with_options\n+|\n+git_config_from_mem\n+|\n+git_config_from_blob_oid\n+|\n+read_early_config\n+|\n+read_very_early_config\n+|\n+config_with_options\n+|\n+git_config\n+|\n+git_protected_config\n+|\n+config_from_gitmodules\n+)\n+  (fn, ...)\n+|\n+repo_config(R, fn, ...)\n+)\n+\n+@ extends get_fn @\n+identifier C1, C2, D;\n+@@\n+int fn(const char *C1, const char *C2,\n++ const struct config_context *ctx,\n+  void *D);\n+\n+@ extends get_fn @\n+@@\n+int fn(const char *, const char *,\n++ const struct config_context *,\n+  void *);\n+\n+@ extends get_fn @\n+// Don't change fns that look like callback fns but aren't\n+identifier fn2 != tar_filter_config && != git_diff_heuristic_config &&\n+  != git_default_submodule_config && != git_color_config &&\n+  != bundle_list_update && != parse_object_filter_config;\n+identifier C1, C2, D1, D2, S;\n+attribute name UNUSED;\n+@@\n+int fn(const char *C1, const char *C2,\n++ const struct config_context *ctx,\n+  void *D1) {\n+<+...\n+(\n+fn2(C1, C2\n++ , ctx\n+, D2);\n+|\n+if(fn2(C1, C2\n++ , ctx\n+, D2) < 0) { ... }\n+|\n+return fn2(C1, C2\n++ , ctx\n+, D2);\n+|\n+S = fn2(C1, C2\n++ , ctx\n+, D2);\n+)\n+...+>\n+  }\n+\n+@ extends get_fn@\n+identifier C1, C2, D;\n+attribute name UNUSED;\n+@@\n+int fn(const char *C1, const char *C2,\n++ const struct config_context *ctx UNUSED,\n+  void *D) {...}\n+\n+\n+// The previous rules don't catch all callbacks, especially if they're defined\n+// in a separate file from the git_config() call. Fix these manually.\n+@@\n+identifier C1, C2, D;\n+attribute name UNUSED;\n+@@\n+int\n+(\n+git_ident_config\n+|\n+urlmatch_collect_fn\n+|\n+write_one_config\n+|\n+forbid_remote_url\n+|\n+credential_config_callback\n+)\n+  (const char *C1, const char *C2,\n++ const struct config_context *ctx UNUSED,\n+  void *D) {...}\n+\n+@@\n+identifier C1, C2, D, D2, S, fn2;\n+@@\n+int\n+(\n+http_options\n+|\n+git_status_config\n+|\n+git_commit_config\n+|\n+git_default_core_config\n+|\n+grep_config\n+)\n+  (const char *C1, const char *C2,\n++ const struct config_context *ctx,\n+  void *D) {\n+<+...\n+(\n+fn2(C1, C2\n++ , ctx\n+, D2);\n+|\n+if(fn2(C1, C2\n++ , ctx\n+, D2) < 0) { ... }\n+|\n+return fn2(C1, C2\n++ , ctx\n+, D2);\n+|\n+S = fn2(C1, C2\n++ , ctx\n+, D2);\n+)\n+...+>\n+  }\ndiff --git a/convert.c b/convert.c\nindex 9ee79fe4699..8e96cf83030 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1015,7 +1015,9 @@ static int apply_filter(const char *path, const char *src, size_t len,\n \treturn 0;\n }\n \n-static int read_convert_config(const char *var, const char *value, void *cb UNUSED)\n+static int read_convert_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       void *cb UNUSED)\n {\n \tconst char *key, *name;\n \tsize_t namelen;\ndiff --git a/credential.c b/credential.c\nindex 023b59d5711..92b8d9707da 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -48,6 +48,7 @@ static int credential_from_potentially_partial_url(struct credential *c,\n \t\t\t\t\t\t   const char *url);\n \n static int credential_config_callback(const char *var, const char *value,\n+\t\t\t\t      const struct config_context *ctx UNUSED,\n \t\t\t\t      void *data)\n {\n \tstruct credential *c = data;\ndiff --git a/delta-islands.c b/delta-islands.c\nindex c824a5f6a42..5fc6ea6ff55 100644\n--- a/delta-islands.c\n+++ b/delta-islands.c\n@@ -341,7 +341,9 @@ static void free_remote_islands(kh_str_t *remote_islands)\n \tkh_destroy_str(remote_islands);\n }\n \n-static int island_config_callback(const char *k, const char *v, void *cb)\n+static int island_config_callback(const char *k, const char *v,\n+\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t  void *cb)\n {\n \tstruct island_load_data *ild = cb;\n \ndiff --git a/diff.c b/diff.c\nindex 1cdac6ed363..07be3bee137 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -357,7 +357,8 @@ static unsigned parse_color_moved_ws(const char *arg)\n \treturn ret;\n }\n \n-int git_diff_ui_config(const char *var, const char *value, void *cb)\n+int git_diff_ui_config(const char *var, const char *value,\n+\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"diff.color\") || !strcmp(var, \"color.diff\")) {\n \t\tdiff_use_color_default = git_config_colorbool(var, value);\n@@ -440,10 +441,11 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_diff_basic_config(var, value, cb);\n+\treturn git_diff_basic_config(var, value, ctx, cb);\n }\n \n-int git_diff_basic_config(const char *var, const char *value, void *cb)\n+int git_diff_basic_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tconst char *name;\n \n@@ -495,7 +497,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)\n \tif (git_diff_heuristic_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static char *quote_two(const char *one, const char *two)\ndiff --git a/diff.h b/diff.h\nindex 3a7a9e8b888..1deea9cb64a 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -531,10 +531,13 @@ void free_diffstat_info(struct diffstat_t *diffstat);\n int parse_long_opt(const char *opt, const char **argv,\n \t\t   const char **optarg);\n \n-int git_diff_basic_config(const char *var, const char *value, void *cb);\n+struct config_context;\n+int git_diff_basic_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb);\n int git_diff_heuristic_config(const char *var, const char *value, void *cb);\n void init_diff_ui_defaults(void);\n-int git_diff_ui_config(const char *var, const char *value, void *cb);\n+int git_diff_ui_config(const char *var, const char *value,\n+\t\t       const struct config_context *ctx, void *cb);\n void repo_diff_setup(struct repository *, struct diff_options *);\n struct option *add_diff_options(const struct option *, struct diff_options *);\n int diff_opt_parse(struct diff_options *, const char **, int, const char *);\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 0f71054fbae..5f3d40f3d09 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1860,7 +1860,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \treturn ref;\n }\n \n-static int fetch_pack_config_cb(const char *var, const char *value, void *cb)\n+static int fetch_pack_config_cb(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx, void *cb)\n {\n \tif (strcmp(var, \"fetch.fsck.skiplist\") == 0) {\n \t\tconst char *path;\n@@ -1882,7 +1883,7 @@ static int fetch_pack_config_cb(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static void fetch_pack_config(void)\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 5af0d4715ba..10137444321 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -20,7 +20,8 @@ static int use_branch_desc;\n static int suppress_dest_pattern_seen;\n static struct string_list suppress_dest_patterns = STRING_LIST_INIT_DUP;\n \n-int fmt_merge_msg_config(const char *key, const char *value, void *cb)\n+int fmt_merge_msg_config(const char *key, const char *value,\n+\t\t\t const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(key, \"merge.log\") || !strcmp(key, \"merge.summary\")) {\n \t\tint is_bool;\n@@ -40,7 +41,7 @@ int fmt_merge_msg_config(const char *key, const char *value, void *cb)\n \t\t\tstring_list_append(&suppress_dest_patterns, value);\n \t\tsuppress_dest_pattern_seen = 1;\n \t} else {\n-\t\treturn git_default_config(key, value, cb);\n+\t\treturn git_default_config(key, value, ctx, cb);\n \t}\n \treturn 0;\n }\ndiff --git a/fmt-merge-msg.h b/fmt-merge-msg.h\nindex 99054042dc5..73ca3e44652 100644\n--- a/fmt-merge-msg.h\n+++ b/fmt-merge-msg.h\n@@ -13,7 +13,8 @@ struct fmt_merge_msg_opts {\n };\n \n extern int merge_log_config;\n-int fmt_merge_msg_config(const char *key, const char *value, void *cb);\n+int fmt_merge_msg_config(const char *key, const char *value,\n+\t\t\t const struct config_context *ctx, void *cb);\n int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \t\t  struct fmt_merge_msg_opts *);\n \ndiff --git a/fsck.c b/fsck.c\nindex 3261ef9ec28..55b6a694853 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1163,7 +1163,9 @@ struct fsck_gitmodules_data {\n \tint ret;\n };\n \n-static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)\n+static int fsck_gitmodules_fn(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx UNUSED,\n+\t\t\t      void *vdata)\n {\n \tstruct fsck_gitmodules_data *data = vdata;\n \tconst char *subsection, *key;\n@@ -1373,7 +1375,8 @@ int fsck_finish(struct fsck_options *options)\n \treturn ret;\n }\n \n-int git_fsck_config(const char *var, const char *value, void *cb)\n+int git_fsck_config(const char *var, const char *value,\n+\t\t    const struct config_context *ctx, void *cb)\n {\n \tstruct fsck_options *options = cb;\n \tif (strcmp(var, \"fsck.skiplist\") == 0) {\n@@ -1394,7 +1397,7 @@ int git_fsck_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n /*\ndiff --git a/fsck.h b/fsck.h\nindex e17730e9da9..6359ba359bd 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -233,10 +233,12 @@ void fsck_put_object_name(struct fsck_options *options,\n const char *fsck_describe_object(struct fsck_options *options,\n \t\t\t\t const struct object_id *oid);\n \n+struct key_value_info;\n /*\n  * git_config() callback for use by fsck-y tools that want to support\n  * fsck.<msg> fsck.skipList etc.\n  */\n-int git_fsck_config(const char *var, const char *value, void *cb);\n+int git_fsck_config(const char *var, const char *value,\n+\t\t    const struct config_context *ctx, void *cb);\n \n #endif\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 5b2b99c17c5..14e8aacb957 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -440,8 +440,10 @@ typedef uintmax_t timestamp_t;\n #endif\n \n #ifndef platform_core_config\n+struct config_context;\n static inline int noop_core_config(const char *var UNUSED,\n \t\t\t\t   const char *value UNUSED,\n+\t\t\t\t   const struct config_context *ctx UNUSED,\n \t\t\t\t   void *cb UNUSED)\n {\n \treturn 0;\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 19a3471a0b5..57c862a3a22 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -14,7 +14,8 @@\n #include \"alias.h\"\n #include \"wrapper.h\"\n \n-static int git_gpg_config(const char *, const char *, void *);\n+static int git_gpg_config(const char *, const char *,\n+\t\t\t  const struct config_context *, void *);\n \n static void gpg_interface_lazy_init(void)\n {\n@@ -720,7 +721,9 @@ void set_signing_key(const char *key)\n \tconfigured_signing_key = xstrdup(key);\n }\n \n-static int git_gpg_config(const char *var, const char *value, void *cb UNUSED)\n+static int git_gpg_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t  void *cb UNUSED)\n {\n \tstruct gpg_format *fmt = NULL;\n \tchar *fmtname = NULL;\ndiff --git a/grep.c b/grep.c\nindex f00986c451a..fc22c3e2afb 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -56,7 +56,8 @@ define_list_config_array_extra(color_grep_slots, {\"match\"});\n  * Read the configuration file once and store it in\n  * the grep_defaults template.\n  */\n-int grep_config(const char *var, const char *value, void *cb)\n+int grep_config(const char *var, const char *value,\n+\t\tconst struct config_context *ctx, void *cb)\n {\n \tstruct grep_opt *opt = cb;\n \tconst char *slot;\n@@ -91,9 +92,9 @@ int grep_config(const char *var, const char *value, void *cb)\n \tif (!strcmp(var, \"color.grep\"))\n \t\topt->color = git_config_colorbool(var, value);\n \tif (!strcmp(var, \"color.grep.match\")) {\n-\t\tif (grep_config(\"color.grep.matchcontext\", value, cb) < 0)\n+\t\tif (grep_config(\"color.grep.matchcontext\", value, ctx, cb) < 0)\n \t\t\treturn -1;\n-\t\tif (grep_config(\"color.grep.matchselected\", value, cb) < 0)\n+\t\tif (grep_config(\"color.grep.matchselected\", value, ctx, cb) < 0)\n \t\t\treturn -1;\n \t} else if (skip_prefix(var, \"color.grep.\", &slot)) {\n \t\tint i = LOOKUP_CONFIG(color_grep_slots, slot);\ndiff --git a/grep.h b/grep.h\nindex c59592e3bdb..926c0875c42 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -202,7 +202,9 @@ struct grep_opt {\n \t.output = std_output, \\\n }\n \n-int grep_config(const char *var, const char *value, void *);\n+struct config_context;\n+int grep_config(const char *var, const char *value,\n+\t\tconst struct config_context *ctx, void *data);\n void grep_init(struct grep_opt *, struct repository *repo);\n \n void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);\ndiff --git a/help.c b/help.c\nindex 5d7637dce92..ac0ae5ac0dc 100644\n--- a/help.c\n+++ b/help.c\n@@ -309,7 +309,8 @@ void load_command_list(const char *prefix,\n \texclude_cmds(other_cmds, main_cmds);\n }\n \n-static int get_colopts(const char *var, const char *value, void *data)\n+static int get_colopts(const char *var, const char *value,\n+\t\t       const struct config_context *ctx UNUSED, void *data)\n {\n \tunsigned int *colopts = data;\n \n@@ -459,7 +460,8 @@ void list_developer_interfaces_help(void)\n \tputchar('\\n');\n }\n \n-static int get_alias(const char *var, const char *value, void *data)\n+static int get_alias(const char *var, const char *value,\n+\t\t     const struct config_context *ctx UNUSED, void *data)\n {\n \tstruct string_list *list = data;\n \n@@ -543,6 +545,7 @@ static struct cmdnames aliases;\n #define AUTOCORRECT_IMMEDIATELY (-1)\n \n static int git_unknown_cmd_config(const char *var, const char *value,\n+\t\t\t\t  const struct config_context *ctx UNUSED,\n \t\t\t\t  void *cb UNUSED)\n {\n \tconst char *p;\ndiff --git a/http.c b/http.c\nindex bb58bb3e6a3..762502828c9 100644\n--- a/http.c\n+++ b/http.c\n@@ -363,7 +363,8 @@ static void process_curl_messages(void)\n \t}\n }\n \n-static int http_options(const char *var, const char *value, void *cb)\n+static int http_options(const char *var, const char *value,\n+\t\t\tconst struct config_context *ctx, void *data)\n {\n \tif (!strcmp(\"http.version\", var)) {\n \t\treturn git_config_string(&curl_http_version, var, value);\n@@ -534,7 +535,7 @@ static int http_options(const char *var, const char *value, void *cb)\n \t}\n \n \t/* Fall back on the default ones */\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, data);\n }\n \n static int curl_empty_auth_enabled(void)\ndiff --git a/ident.c b/ident.c\nindex 8fad92d7007..08be4d0747d 100644\n--- a/ident.c\n+++ b/ident.c\n@@ -671,7 +671,9 @@ static int set_ident(const char *var, const char *value)\n \treturn 0;\n }\n \n-int git_ident_config(const char *var, const char *value, void *data UNUSED)\n+int git_ident_config(const char *var, const char *value,\n+\t\t     const struct config_context *ctx UNUSED,\n+\t\t     void *data UNUSED)\n {\n \tif (!strcmp(var, \"user.useconfigonly\")) {\n \t\tident_use_config_only = git_config_bool(var, value);\ndiff --git a/ident.h b/ident.h\nindex 96a64896a01..6a79febba15 100644\n--- a/ident.h\n+++ b/ident.h\n@@ -62,6 +62,8 @@ const char *fmt_name(enum want_ident);\n int committer_ident_sufficiently_given(void);\n int author_ident_sufficiently_given(void);\n \n-int git_ident_config(const char *, const char *, void *);\n+struct config_context;\n+int git_ident_config(const char *, const char *, const struct config_context *,\n+\t\t     void *);\n \n #endif\ndiff --git a/imap-send.c b/imap-send.c\nindex 7f5426177a1..47777e76861 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1323,7 +1323,8 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)\n \treturn 1;\n }\n \n-static int git_imap_config(const char *var, const char *val, void *cb)\n+static int git_imap_config(const char *var, const char *val,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \n \tif (!strcmp(\"imap.sslverify\", var))\n@@ -1357,7 +1358,7 @@ static int git_imap_config(const char *var, const char *val, void *cb)\n \t\t\tserver.host = xstrdup(val);\n \t\t}\n \t} else\n-\t\treturn git_default_config(var, val, cb);\n+\t\treturn git_default_config(var, val, ctx, cb);\n \n \treturn 0;\n }\ndiff --git a/ll-merge.c b/ll-merge.c\nindex 07ec16e8e5b..3936d112e00 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -254,6 +254,7 @@ static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;\n static const char *default_ll_merge;\n \n static int read_merge_config(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t     void *cb UNUSED)\n {\n \tstruct ll_merge_driver *fn;\ndiff --git a/ls-refs.c b/ls-refs.c\nindex f385938b64c..a29c2364a59 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -137,6 +137,7 @@ static void send_possibly_unborn_head(struct ls_refs_data *data)\n }\n \n static int ls_refs_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx UNUSED,\n \t\t\t  void *cb_data)\n {\n \tstruct ls_refs_data *data = cb_data;\ndiff --git a/mailinfo.c b/mailinfo.c\nindex 2aeb20e5e62..931505363cd 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -1241,12 +1241,13 @@ int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)\n \treturn 0;\n }\n \n-static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n+static int git_mailinfo_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *mi_)\n {\n \tstruct mailinfo *mi = mi_;\n \n \tif (!starts_with(var, \"mailinfo.\"))\n-\t\treturn git_default_config(var, value, NULL);\n+\t\treturn git_default_config(var, value, ctx, NULL);\n \tif (!strcmp(var, \"mailinfo.scissors\")) {\n \t\tmi->use_scissors = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/notes-utils.c b/notes-utils.c\nindex 4a793eb347f..97c031c26ec 100644\n--- a/notes-utils.c\n+++ b/notes-utils.c\n@@ -94,7 +94,9 @@ static combine_notes_fn parse_combine_notes_fn(const char *v)\n \t\treturn NULL;\n }\n \n-static int notes_rewrite_config(const char *k, const char *v, void *cb)\n+static int notes_rewrite_config(const char *k, const char *v,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *cb)\n {\n \tstruct notes_rewrite_cfg *c = cb;\n \tif (starts_with(k, \"notes.rewrite.\") && !strcmp(k+14, c->cmd)) {\ndiff --git a/notes.c b/notes.c\nindex f51a2d3630e..e68645a4b89 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -974,7 +974,9 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,\n \tfree(globs_copy);\n }\n \n-static int notes_display_config(const char *k, const char *v, void *cb)\n+static int notes_display_config(const char *k, const char *v,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *cb)\n {\n \tint *load_refs = cb;\n \ndiff --git a/pager.c b/pager.c\nindex 63055d0873f..b8822a9381e 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -43,6 +43,7 @@ static void wait_for_pager_signal(int signo)\n }\n \n static int core_pager_config(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t     void *data UNUSED)\n {\n \tif (!strcmp(var, \"core.pager\"))\n@@ -228,7 +229,9 @@ struct pager_command_config_data {\n \tchar *value;\n };\n \n-static int pager_command_config(const char *var, const char *value, void *vdata)\n+static int pager_command_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *vdata)\n {\n \tstruct pager_command_config_data *data = vdata;\n \tconst char *cmd;\ndiff --git a/pretty.c b/pretty.c\nindex 0bb938021ba..87245353452 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -56,6 +56,7 @@ static void save_user_format(struct rev_info *rev, const char *cp, int is_tforma\n }\n \n static int git_pretty_formats_config(const char *var, const char *value,\n+\t\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t\t     void *cb UNUSED)\n {\n \tstruct cmt_fmt_map *commit_format = NULL;\ndiff --git a/promisor-remote.c b/promisor-remote.c\nindex 1adcd6fb0a5..c22abb85b15 100644\n--- a/promisor-remote.c\n+++ b/promisor-remote.c\n@@ -100,7 +100,9 @@ static void promisor_remote_move_to_tail(struct promisor_remote_config *config,\n \tconfig->promisors_tail = &r->next;\n }\n \n-static int promisor_remote_config(const char *var, const char *value, void *data)\n+static int promisor_remote_config(const char *var, const char *value,\n+\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t  void *data)\n {\n \tstruct promisor_remote_config *config = data;\n \tconst char *name;\ndiff --git a/remote.c b/remote.c\nindex 0764fca0db9..dafb1acdc38 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -349,7 +349,8 @@ static void read_branches_file(struct remote_state *remote_state,\n \tremote->fetch_tags = 1; /* always auto-follow */\n }\n \n-static int handle_config(const char *key, const char *value, void *cb)\n+static int handle_config(const char *key, const char *value,\n+\t\t\t const struct config_context *ctx UNUSED, void *cb)\n {\n \tconst char *name;\n \tsize_t namelen;\ndiff --git a/revision.c b/revision.c\nindex b33cc1d106a..87ed8ccd444 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1572,7 +1572,9 @@ struct exclude_hidden_refs_cb {\n \tconst char *section;\n };\n \n-static int hide_refs_config(const char *var, const char *value, void *cb_data)\n+static int hide_refs_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    void *cb_data)\n {\n \tstruct exclude_hidden_refs_cb *cb = cb_data;\n \tcb->exclusions->hidden_refs_configured = 1;\ndiff --git a/scalar.c b/scalar.c\nindex 1326e1f6089..df7358f481c 100644\n--- a/scalar.c\n+++ b/scalar.c\n@@ -594,7 +594,9 @@ static int cmd_register(int argc, const char **argv)\n \treturn register_dir();\n }\n \n-static int get_scalar_repos(const char *key, const char *value, void *data)\n+static int get_scalar_repos(const char *key, const char *value,\n+\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    void *data)\n {\n \tstruct string_list *list = data;\n \ndiff --git a/sequencer.c b/sequencer.c\nindex bceb6abcb6c..34754d17596 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -219,7 +219,8 @@ static struct update_ref_record *init_update_ref_record(const char *ref)\n \treturn rec;\n }\n \n-static int git_sequencer_config(const char *k, const char *v, void *cb)\n+static int git_sequencer_config(const char *k, const char *v,\n+\t\t\t\tconst struct config_context *ctx, void *cb)\n {\n \tstruct replay_opts *opts = cb;\n \tint status;\n@@ -274,7 +275,7 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)\n \tif (opts->action == REPLAY_REVERT && !strcmp(k, \"revert.reference\"))\n \t\topts->commit_use_reference = git_config_bool(k, v);\n \n-\treturn git_diff_basic_config(k, v, NULL);\n+\treturn git_diff_basic_config(k, v, ctx, NULL);\n }\n \n void sequencer_init_config(struct replay_opts *opts)\n@@ -2881,7 +2882,9 @@ static int git_config_string_dup(char **dest,\n \treturn 0;\n }\n \n-static int populate_opts_cb(const char *key, const char *value, void *data)\n+static int populate_opts_cb(const char *key, const char *value,\n+\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    void *data)\n {\n \tstruct replay_opts *opts = data;\n \tint error_flag = 1;\ndiff --git a/setup.c b/setup.c\nindex 458582207ea..e7f81151ef0 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -517,7 +517,9 @@ no_prevention_needed:\n \tstartup_info->original_cwd = NULL;\n }\n \n-static int read_worktree_config(const char *var, const char *value, void *vdata)\n+static int read_worktree_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *vdata)\n {\n \tstruct repository_format *data = vdata;\n \n@@ -588,7 +590,8 @@ static enum extension_result handle_extension(const char *var,\n \treturn EXTENSION_UNKNOWN;\n }\n \n-static int check_repo_format(const char *var, const char *value, void *vdata)\n+static int check_repo_format(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx, void *vdata)\n {\n \tstruct repository_format *data = vdata;\n \tconst char *ext;\n@@ -617,7 +620,7 @@ static int check_repo_format(const char *var, const char *value, void *vdata)\n \t\t}\n \t}\n \n-\treturn read_worktree_config(var, value, vdata);\n+\treturn read_worktree_config(var, value, ctx, vdata);\n }\n \n static int check_repository_format_gently(const char *gitdir, struct repository_format *candidate, int *nongit_ok)\n@@ -1116,7 +1119,8 @@ struct safe_directory_data {\n \tint is_safe;\n };\n \n-static int safe_directory_cb(const char *key, const char *value, void *d)\n+static int safe_directory_cb(const char *key, const char *value,\n+\t\t\t     const struct config_context *ctx UNUSED, void *d)\n {\n \tstruct safe_directory_data *data = d;\n \n@@ -1172,7 +1176,9 @@ static int ensure_valid_ownership(const char *gitfile,\n \treturn data.is_safe;\n }\n \n-static int allowed_bare_repo_cb(const char *key, const char *value, void *d)\n+static int allowed_bare_repo_cb(const char *key, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *d)\n {\n \tenum allowed_bare_repo *allowed_bare_repo = d;\n \ndiff --git a/submodule-config.c b/submodule-config.c\nindex 58dfbde9ae5..b364244102e 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -426,7 +426,8 @@ struct parse_config_parameter {\n  * config store (.git/config, etc).  Callers are responsible for\n  * checking for overrides in the main config store when appropriate.\n  */\n-static int parse_config(const char *var, const char *value, void *data)\n+static int parse_config(const char *var, const char *value,\n+\t\t\tconst struct config_context *ctx UNUSED, void *data)\n {\n \tstruct parse_config_parameter *me = data;\n \tstruct submodule *submodule;\n@@ -675,7 +676,8 @@ out:\n \t}\n }\n \n-static int gitmodules_cb(const char *var, const char *value, void *data)\n+static int gitmodules_cb(const char *var, const char *value,\n+\t\t\t const struct config_context *ctx, void *data)\n {\n \tstruct repository *repo = data;\n \tstruct parse_config_parameter parameter;\n@@ -685,7 +687,7 @@ static int gitmodules_cb(const char *var, const char *value, void *data)\n \tparameter.gitmodules_oid = null_oid();\n \tparameter.overwrite = 1;\n \n-\treturn parse_config(var, value, &parameter);\n+\treturn parse_config(var, value, ctx, &parameter);\n }\n \n void repo_read_gitmodules(struct repository *repo, int skip_if_read)\n@@ -802,7 +804,9 @@ void submodule_free(struct repository *r)\n \t\tsubmodule_cache_clear(r->submodule_cache);\n }\n \n-static int config_print_callback(const char *var, const char *value, void *cb_data)\n+static int config_print_callback(const char *var, const char *value,\n+\t\t\t\t const struct config_context *ctx UNUSED,\n+\t\t\t\t void *cb_data)\n {\n \tchar *wanted_key = cb_data;\n \n@@ -844,7 +848,9 @@ struct fetch_config {\n \tint *recurse_submodules;\n };\n \n-static int gitmodules_fetch_config(const char *var, const char *value, void *cb)\n+static int gitmodules_fetch_config(const char *var, const char *value,\n+\t\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t\t   void *cb)\n {\n \tstruct fetch_config *config = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\")) {\n@@ -872,6 +878,7 @@ void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)\n }\n \n static int gitmodules_update_clone_config(const char *var, const char *value,\n+\t\t\t\t\t  const struct config_context *ctx UNUSED,\n \t\t\t\t\t  void *cb)\n {\n \tint *max_jobs = cb;\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex ad78fc17683..85ad815358e 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -42,7 +42,9 @@\n  *\n  */\n \n-static int iterate_cb(const char *var, const char *value, void *data UNUSED)\n+static int iterate_cb(const char *var, const char *value,\n+\t\t      const struct config_context *ctx UNUSED,\n+\t\t      void *data UNUSED)\n {\n \tstatic int nr;\n \n@@ -59,7 +61,8 @@ static int iterate_cb(const char *var, const char *value, void *data UNUSED)\n \treturn 0;\n }\n \n-static int parse_int_cb(const char *var, const char *value, void *data)\n+static int parse_int_cb(const char *var, const char *value,\n+\t\t\tconst struct config_context *ctx UNUSED, void *data)\n {\n \tconst char *key_to_match = data;\n \n@@ -70,7 +73,9 @@ static int parse_int_cb(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n-static int early_config_cb(const char *var, const char *value, void *vdata)\n+static int early_config_cb(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t   void *vdata)\n {\n \tconst char *key = vdata;\n \ndiff --git a/t/helper/test-userdiff.c b/t/helper/test-userdiff.c\nindex 680124a6760..0ce31ce59f5 100644\n--- a/t/helper/test-userdiff.c\n+++ b/t/helper/test-userdiff.c\n@@ -12,7 +12,9 @@ static int driver_cb(struct userdiff_driver *driver,\n \treturn 0;\n }\n \n-static int cmd__userdiff_config(const char *var, const char *value, void *cb UNUSED)\n+static int cmd__userdiff_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *cb UNUSED)\n {\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 78cfc15d52d..83bc4fd109c 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -99,7 +99,8 @@ struct tr2_cfg_data {\n /*\n  * See if the given config key matches any of our patterns of interest.\n  */\n-static int tr2_cfg_cb(const char *key, const char *value, void *d)\n+static int tr2_cfg_cb(const char *key, const char *value,\n+\t\t      const struct config_context *ctx UNUSED, void *d)\n {\n \tstruct strbuf **s;\n \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n@@ -142,8 +143,12 @@ void tr2_list_env_vars_fl(const char *file, int line)\n void tr2_cfg_set_fl(const char *file, int line, const char *key,\n \t\t    const char *value)\n {\n+\tstruct key_value_info kvi = KVI_INIT;\n+\tstruct config_context ctx = {\n+\t\t.kvi = &kvi,\n+\t};\n \tstruct tr2_cfg_data data = { file, line };\n \n \tif (tr2_cfg_load_patterns() > 0)\n-\t\ttr2_cfg_cb(key, value, &data);\n+\t\ttr2_cfg_cb(key, value, &ctx, &data);\n }\ndiff --git a/trace2/tr2_sysenv.c b/trace2/tr2_sysenv.c\nindex 069786cb927..f26ec95ab4d 100644\n--- a/trace2/tr2_sysenv.c\n+++ b/trace2/tr2_sysenv.c\n@@ -57,7 +57,8 @@ static struct tr2_sysenv_entry tr2_sysenv_settings[] = {\n };\n /* clang-format on */\n \n-static int tr2_sysenv_cb(const char *key, const char *value, void *d)\n+static int tr2_sysenv_cb(const char *key, const char *value,\n+\t\t\t const struct config_context *ctx UNUSED, void *d)\n {\n \tint k;\n \ndiff --git a/trailer.c b/trailer.c\nindex a2c3ed6f28c..06dc0b7f683 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -482,6 +482,7 @@ static struct {\n };\n \n static int git_trailer_default_config(const char *conf_key, const char *value,\n+\t\t\t\t      const struct config_context *ctx UNUSED,\n \t\t\t\t      void *cb UNUSED)\n {\n \tconst char *trailer_item, *variable_name;\n@@ -514,6 +515,7 @@ static int git_trailer_default_config(const char *conf_key, const char *value,\n }\n \n static int git_trailer_config(const char *conf_key, const char *value,\n+\t\t\t      const struct config_context *ctx UNUSED,\n \t\t\t      void *cb UNUSED)\n {\n \tconst char *trailer_item, *variable_name;\ndiff --git a/upload-pack.c b/upload-pack.c\nindex d3312006a32..951fd1f9c25 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1309,7 +1309,9 @@ static int parse_object_filter_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-static int upload_pack_config(const char *var, const char *value, void *cb_data)\n+static int upload_pack_config(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx UNUSED,\n+\t\t\t      void *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n \n@@ -1350,7 +1352,9 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data)\n \treturn parse_hide_refs_config(var, value, \"uploadpack\", &data->hidden_refs);\n }\n \n-static int upload_pack_protected_config(const char *var, const char *value, void *cb_data)\n+static int upload_pack_protected_config(const char *var, const char *value,\n+\t\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\t\tvoid *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n \ndiff --git a/urlmatch.c b/urlmatch.c\nindex eba0bdd77fe..1c45f23adf2 100644\n--- a/urlmatch.c\n+++ b/urlmatch.c\n@@ -551,7 +551,8 @@ static int cmp_matches(const struct urlmatch_item *a,\n \treturn 0;\n }\n \n-int urlmatch_config_entry(const char *var, const char *value, void *cb)\n+int urlmatch_config_entry(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tstruct string_list_item *item;\n \tstruct urlmatch_config *collect = cb;\n@@ -565,7 +566,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)\n \n \tif (!skip_prefix(var, collect->section, &key) || *(key++) != '.') {\n \t\tif (collect->cascade_fn)\n-\t\t\treturn collect->cascade_fn(var, value, cb);\n+\t\t\treturn collect->cascade_fn(var, value, ctx, cb);\n \t\treturn 0; /* not interested */\n \t}\n \tdot = strrchr(key, '.');\n@@ -609,7 +610,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)\n \tstrbuf_addstr(&synthkey, collect->section);\n \tstrbuf_addch(&synthkey, '.');\n \tstrbuf_addstr(&synthkey, key);\n-\tretval = collect->collect_fn(synthkey.buf, value, collect->cb);\n+\tretval = collect->collect_fn(synthkey.buf, value, ctx, collect->cb);\n \n \tstrbuf_release(&synthkey);\n \treturn retval;\ndiff --git a/urlmatch.h b/urlmatch.h\nindex bee374a642c..5ba85cea139 100644\n--- a/urlmatch.h\n+++ b/urlmatch.h\n@@ -71,7 +71,8 @@ struct urlmatch_config {\n \t.vars = STRING_LIST_INIT_DUP, \\\n }\n \n-int urlmatch_config_entry(const char *var, const char *value, void *cb);\n+int urlmatch_config_entry(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb);\n void urlmatch_config_release(struct urlmatch_config *config);\n \n #endif /* URL_MATCH_H */\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex 0460e03f5ed..dcbb5e09857 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -307,7 +307,8 @@ int xdiff_compare_lines(const char *l1, long s1,\n \n int git_xmerge_style = -1;\n \n-int git_xmerge_config(const char *var, const char *value, void *cb)\n+int git_xmerge_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"merge.conflictstyle\")) {\n \t\tif (!value)\n@@ -327,5 +328,5 @@ int git_xmerge_config(const char *var, const char *value, void *cb)\n \t\t\t    value, var);\n \t\treturn 0;\n \t}\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex 733c364d26c..e6f80df0462 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -50,7 +50,9 @@ int buffer_is_binary(const char *ptr, unsigned long size);\n \n void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n-int git_xmerge_config(const char *var, const char *value, void *cb);\n+struct config_context;\n+int git_xmerge_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"478587","messageId":"8ae115cff8855368f1410eb73ff618a69c20072b.1687290233.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"[PATCH v3 11/12] config: add kvi.path, use it to evaluate includes","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-20T19:43:50Z","receivedAt":"2023-06-20T19:44:44Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nInclude directives are evaluated using the path of the config file. To\nreduce the dependence on \"config_reader.source\", add a new\n\"key_value_info.path\" member and use that instead of\n\"config_source.path\". This allows us to remove a \"struct config_reader\n*\" field from \"struct config_include_data\", which will subsequently\nallow us to remove \"struct config_reader\" entirely.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 40 +++++++++++++++++++---------------------\n config.h |  2 ++\n 2 files changed, 21 insertions(+), 21 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 958ba166cf9..04c7db6f1be 100644\n--- a/config.c\n+++ b/config.c\n@@ -161,7 +161,6 @@ struct config_include_data {\n \tvoid *data;\n \tconst struct config_options *opts;\n \tstruct git_config_source *config_source;\n-\tstruct config_reader *config_reader;\n \n \t/*\n \t * All remote URLs discovered when reading all config files.\n@@ -180,8 +179,7 @@ static const char include_depth_advice[] = N_(\n \"from\\n\"\n \"\t%s\\n\"\n \"This might be due to circular includes.\");\n-static int handle_path_include(struct config_source *cs,\n-\t\t\t       const struct key_value_info *kvi,\n+static int handle_path_include(const struct key_value_info *kvi,\n \t\t\t       const char *path,\n \t\t\t       struct config_include_data *inc)\n {\n@@ -204,14 +202,14 @@ static int handle_path_include(struct config_source *cs,\n \tif (!is_absolute_path(path)) {\n \t\tchar *slash;\n \n-\t\tif (!cs || !cs->path) {\n+\t\tif (!kvi || !kvi->path) {\n \t\t\tret = error(_(\"relative config includes must come from files\"));\n \t\t\tgoto cleanup;\n \t\t}\n \n-\t\tslash = find_last_dir_sep(cs->path);\n+\t\tslash = find_last_dir_sep(kvi->path);\n \t\tif (slash)\n-\t\t\tstrbuf_add(&buf, cs->path, slash - cs->path + 1);\n+\t\t\tstrbuf_add(&buf, kvi->path, slash - kvi->path + 1);\n \t\tstrbuf_addstr(&buf, path);\n \t\tpath = buf.buf;\n \t}\n@@ -219,8 +217,8 @@ static int handle_path_include(struct config_source *cs,\n \tif (!access_or_die(path, R_OK, 0)) {\n \t\tif (++inc->depth > MAX_INCLUDE_DEPTH)\n \t\t\tdie(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,\n-\t\t\t    !cs ? \"<unknown>\" :\n-\t\t\t    cs->name ? cs->name :\n+\t\t\t    !kvi ? \"<unknown>\" :\n+\t\t\t    kvi->filename ? kvi->filename :\n \t\t\t    \"the command line\");\n \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n \t\t\t\t\t\t\tkvi->scope, NULL);\n@@ -238,7 +236,7 @@ static void add_trailing_starstar_for_dir(struct strbuf *pat)\n \t\tstrbuf_addstr(pat, \"**\");\n }\n \n-static int prepare_include_condition_pattern(struct config_source *cs,\n+static int prepare_include_condition_pattern(const struct key_value_info *kvi,\n \t\t\t\t\t     struct strbuf *pat)\n {\n \tstruct strbuf path = STRBUF_INIT;\n@@ -255,11 +253,11 @@ static int prepare_include_condition_pattern(struct config_source *cs,\n \tif (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {\n \t\tconst char *slash;\n \n-\t\tif (!cs || !cs->path)\n+\t\tif (!kvi || !kvi->path)\n \t\t\treturn error(_(\"relative config include \"\n \t\t\t\t       \"conditionals must come from files\"));\n \n-\t\tstrbuf_realpath(&path, cs->path, 1);\n+\t\tstrbuf_realpath(&path, kvi->path, 1);\n \t\tslash = find_last_dir_sep(path.buf);\n \t\tif (!slash)\n \t\t\tBUG(\"how is this possible?\");\n@@ -274,7 +272,7 @@ static int prepare_include_condition_pattern(struct config_source *cs,\n \treturn prefix;\n }\n \n-static int include_by_gitdir(struct config_source *cs,\n+static int include_by_gitdir(const struct key_value_info *kvi,\n \t\t\t     const struct config_options *opts,\n \t\t\t     const char *cond, size_t cond_len, int icase)\n {\n@@ -291,7 +289,7 @@ static int include_by_gitdir(struct config_source *cs,\n \n \tstrbuf_realpath(&text, git_dir, 1);\n \tstrbuf_add(&pattern, cond, cond_len);\n-\tprefix = prepare_include_condition_pattern(cs, &pattern);\n+\tprefix = prepare_include_condition_pattern(kvi, &pattern);\n \n again:\n \tif (prefix < 0)\n@@ -426,16 +424,16 @@ static int include_by_remote_url(struct config_include_data *inc,\n \t\t\t\t\t     inc->remote_urls);\n }\n \n-static int include_condition_is_true(struct config_source *cs,\n+static int include_condition_is_true(const struct key_value_info *kvi,\n \t\t\t\t     struct config_include_data *inc,\n \t\t\t\t     const char *cond, size_t cond_len)\n {\n \tconst struct config_options *opts = inc->opts;\n \n \tif (skip_prefix_mem(cond, cond_len, \"gitdir:\", &cond, &cond_len))\n-\t\treturn include_by_gitdir(cs, opts, cond, cond_len, 0);\n+\t\treturn include_by_gitdir(kvi, opts, cond, cond_len, 0);\n \telse if (skip_prefix_mem(cond, cond_len, \"gitdir/i:\", &cond, &cond_len))\n-\t\treturn include_by_gitdir(cs, opts, cond, cond_len, 1);\n+\t\treturn include_by_gitdir(kvi, opts, cond, cond_len, 1);\n \telse if (skip_prefix_mem(cond, cond_len, \"onbranch:\", &cond, &cond_len))\n \t\treturn include_by_branch(cond, cond_len);\n \telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.*.url:\", &cond,\n@@ -451,7 +449,6 @@ static int git_config_include(const char *var, const char *value,\n \t\t\t      void *data)\n {\n \tstruct config_include_data *inc = data;\n-\tstruct config_source *cs = inc->config_reader->source;\n \tconst char *cond, *key;\n \tsize_t cond_len;\n \tint ret;\n@@ -465,16 +462,16 @@ static int git_config_include(const char *var, const char *value,\n \t\treturn ret;\n \n \tif (!strcmp(var, \"include.path\"))\n-\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n+\t\tret = handle_path_include(ctx->kvi, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n-\t    cond && include_condition_is_true(cs, inc, cond, cond_len) &&\n+\t    cond && include_condition_is_true(ctx->kvi, inc, cond, cond_len) &&\n \t    !strcmp(key, \"path\")) {\n \t\tconfig_fn_t old_fn = inc->fn;\n \n \t\tif (inc->opts->unconditional_remote_url)\n \t\t\tinc->fn = forbid_remote_url;\n-\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n+\t\tret = handle_path_include(ctx->kvi, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -659,6 +656,7 @@ void kvi_from_param(struct key_value_info *out)\n \tout->linenr = -1;\n \tout->origin_type = CONFIG_ORIGIN_CMDLINE;\n \tout->scope = CONFIG_SCOPE_COMMAND;\n+\tout->path = NULL;\n }\n \n int git_config_parse_parameter(const char *text,\n@@ -1062,6 +1060,7 @@ static void kvi_from_source(struct config_source *cs,\n \tout->origin_type = cs->origin_type;\n \tout->linenr = cs->linenr;\n \tout->scope = scope;\n+\tout->path = cs->path;\n }\n \n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n@@ -2272,7 +2271,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tinc.data = data;\n \t\tinc.opts = opts;\n \t\tinc.config_source = config_source;\n-\t\tinc.config_reader = &the_reader;\n \t\tfn = git_config_include;\n \t\tdata = &inc;\n \t}\ndiff --git a/config.h b/config.h\nindex a8f41c59e6c..8841b7a1cf6 100644\n--- a/config.h\n+++ b/config.h\n@@ -117,12 +117,14 @@ struct key_value_info {\n \tint linenr;\n \tenum config_origin_type origin_type;\n \tenum config_scope scope;\n+\tconst char *path;\n };\n #define KVI_INIT { \\\n \t.filename = NULL, \\\n \t.linenr = -1, \\\n \t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n \t.scope = CONFIG_SCOPE_UNKNOWN, \\\n+\t.path = NULL, \\\n }\n \n /* Captures additional information that a config callback can use. */\n-- \ngitgitgadget\n\n"},{"id":"478588","messageId":"484d553cc7da360b134e7cfcc02e587073b1f9fa.1687290233.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"[PATCH v3 12/12] config: pass source to config_parser_event_fn_t","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-20T19:43:51Z","receivedAt":"2023-06-20T19:44:46Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\n..so that the callback can use a \"struct config_source\" parameter\ninstead of \"config_reader.source\". \"struct config_source\" is internal to\nconfig.c, so we are adding a pointer to a struct defined in config.c\ninto a public function signature defined in config.h, but this is okay\nbecause this function has only ever been (and probably ever will be)\nused internally by config.c.\n\nAs a result, the_reader isn't used anywhere, so \"struct config_reader\"\nis obsolete (it was only intended to be used with the_reader). Remove\nthem.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 77 ++++++++++----------------------------------------------\n config.h |  6 +++++\n 2 files changed, 19 insertions(+), 64 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 04c7db6f1be..c58e44b15c5 100644\n--- a/config.c\n+++ b/config.c\n@@ -66,40 +66,6 @@ struct config_source {\n };\n #define CONFIG_SOURCE_INIT { 0 }\n \n-struct config_reader {\n-\t/*\n-\t * These members record the \"current\" config source, which can be\n-\t * accessed by parsing callbacks.\n-\t *\n-\t * The \"source\" variable will be non-NULL only when we are actually\n-\t * parsing a real config source (file, blob, cmdline, etc).\n-\t */\n-\tstruct config_source *source;\n-};\n-/*\n- * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n- * \"the_reader\". \"the_reader\" should only be used if that is infeasible, e.g. in\n- * a public function.\n- */\n-static struct config_reader the_reader;\n-\n-static inline void config_reader_push_source(struct config_reader *reader,\n-\t\t\t\t\t     struct config_source *top)\n-{\n-\ttop->prev = reader->source;\n-\treader->source = top;\n-}\n-\n-static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n-{\n-\tstruct config_source *ret;\n-\tif (!reader->source)\n-\t\tBUG(\"tried to pop config source, but we weren't reading config\");\n-\tret = reader->source;\n-\treader->source = reader->source->prev;\n-\treturn ret;\n-}\n-\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -750,14 +716,9 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tstruct strvec to_free = STRVEC_INIT;\n \tint ret = 0;\n \tchar *envw = NULL;\n-\tstruct config_source source = CONFIG_SOURCE_INIT;\n \tstruct key_value_info kvi = KVI_INIT;\n \n-\tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n-\tconfig_reader_push_source(&the_reader, &source);\n-\n \tkvi_from_param(&kvi);\n-\n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n \t\tunsigned long count;\n@@ -814,7 +775,6 @@ out:\n \tstrbuf_release(&envvar);\n \tstrvec_clear(&to_free);\n \tfree(envw);\n-\tconfig_reader_pop_source(&the_reader);\n \treturn ret;\n }\n \n@@ -1043,7 +1003,7 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \n \tif (data->previous_type != CONFIG_EVENT_EOF &&\n \t    data->opts->event_fn(data->previous_type, data->previous_offset,\n-\t\t\t\t offset, data->opts->event_fn_data) < 0)\n+\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n \t\treturn -1;\n \n \tdata->previous_type = type;\n@@ -2005,8 +1965,7 @@ int git_default_config(const char *var, const char *value,\n  * fgetc, ungetc, ftell of top need to be initialized before calling\n  * this function.\n  */\n-static int do_config_from(struct config_reader *reader,\n-\t\t\t  struct config_source *top, config_fn_t fn,\n+static int do_config_from(struct config_source *top, config_fn_t fn,\n \t\t\t  void *data, enum config_scope scope,\n \t\t\t  const struct config_options *opts)\n {\n@@ -2019,21 +1978,17 @@ static int do_config_from(struct config_reader *reader,\n \ttop->total_len = 0;\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n-\tconfig_reader_push_source(reader, top);\n \tkvi_from_source(top, scope, &kvi);\n \n \tret = git_parse_source(top, fn, &kvi, data, opts);\n \n-\t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n \tstrbuf_release(&top->var);\n-\tconfig_reader_pop_source(reader);\n \n \treturn ret;\n }\n \n-static int do_config_from_file(struct config_reader *reader,\n-\t\t\t       config_fn_t fn,\n+static int do_config_from_file(config_fn_t fn,\n \t\t\t       const enum config_origin_type origin_type,\n \t\t\t       const char *name, const char *path, FILE *f,\n \t\t\t       void *data, enum config_scope scope,\n@@ -2052,7 +2007,7 @@ static int do_config_from_file(struct config_reader *reader,\n \ttop.do_ftell = config_file_ftell;\n \n \tflockfile(f);\n-\tret = do_config_from(reader, &top, fn, data, scope, opts);\n+\tret = do_config_from(&top, fn, data, scope, opts);\n \tfunlockfile(f);\n \treturn ret;\n }\n@@ -2060,8 +2015,8 @@ static int do_config_from_file(struct config_reader *reader,\n static int git_config_from_stdin(config_fn_t fn, void *data,\n \t\t\t\t enum config_scope scope)\n {\n-\treturn do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, \"\",\n-\t\t\t\t   NULL, stdin, data, scope, NULL);\n+\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n+\t\t\t\t   data, scope, NULL);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2075,9 +2030,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \t\tBUG(\"filename cannot be NULL\");\n \tf = fopen_or_warn(filename, \"r\");\n \tif (f) {\n-\t\tret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,\n-\t\t\t\t\t  filename, filename, f, data, scope,\n-\t\t\t\t\t  opts);\n+\t\tret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,\n+\t\t\t\t\t  filename, f, data, scope, opts);\n \t\tfclose(f);\n \t}\n \treturn ret;\n@@ -2108,7 +2062,7 @@ int git_config_from_mem(config_fn_t fn,\n \ttop.do_ungetc = config_buf_ungetc;\n \ttop.do_ftell = config_buf_ftell;\n \n-\treturn do_config_from(&the_reader, &top, fn, data, scope, opts);\n+\treturn do_config_from(&top, fn, data, scope, opts);\n }\n \n int git_config_from_blob_oid(config_fn_t fn,\n@@ -2201,8 +2155,7 @@ int git_config_system(void)\n \treturn !git_env_bool(\"GIT_CONFIG_NOSYSTEM\", 0);\n }\n \n-static int do_git_config_sequence(struct config_reader *reader,\n-\t\t\t\t  const struct config_options *opts,\n+static int do_git_config_sequence(const struct config_options *opts,\n \t\t\t\t  config_fn_t fn, void *data)\n {\n \tint ret = 0;\n@@ -2291,7 +2244,7 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n \t\t\t\t\t       data, config_source->scope);\n \t} else {\n-\t\tret = do_git_config_sequence(&the_reader, opts, fn, data);\n+\t\tret = do_git_config_sequence(opts, fn, data);\n \t}\n \n \tif (inc.remote_urls) {\n@@ -3000,7 +2953,6 @@ void git_die_config(const char *key, const char *err, ...)\n  */\n \n struct config_store_data {\n-\tstruct config_reader *config_reader;\n \tsize_t baselen;\n \tchar *key;\n \tint do_not_match;\n@@ -3046,11 +2998,10 @@ static int matches(const char *key, const char *value,\n \t\t(value && !regexec(store->value_pattern, value, 0, NULL, 0));\n }\n \n-static int store_aux_event(enum config_event_t type,\n-\t\t\t   size_t begin, size_t end, void *data)\n+static int store_aux_event(enum config_event_t type, size_t begin, size_t end,\n+\t\t\t   struct config_source *cs, void *data)\n {\n \tstruct config_store_data *store = data;\n-\tstruct config_source *cs = store->config_reader->source;\n \n \tALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);\n \tstore->parsed[store->parsed_nr].begin = begin;\n@@ -3371,8 +3322,6 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \tsize_t contents_sz;\n \tstruct config_store_data store = CONFIG_STORE_INIT;\n \n-\tstore.config_reader = &the_reader;\n-\n \t/* parse-key returns negative; flip the sign to feed exit(3) */\n \tret = 0 - git_config_parse_key(key, &store.key, &store.baselen);\n \tif (ret)\ndiff --git a/config.h b/config.h\nindex 8841b7a1cf6..f663cdd8e1b 100644\n--- a/config.h\n+++ b/config.h\n@@ -73,6 +73,7 @@ enum config_event_t {\n \tCONFIG_EVENT_ERROR\n };\n \n+struct config_source;\n /*\n  * The parser event function (if not NULL) is called with the event type and\n  * the begin/end offsets of the parsed elements.\n@@ -82,6 +83,7 @@ enum config_event_t {\n  */\n typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n \t\t\t\t\tsize_t begin_offset, size_t end_offset,\n+\t\t\t\t\tstruct config_source *cs,\n \t\t\t\t\tvoid *event_fn_data);\n \n struct config_options {\n@@ -101,6 +103,10 @@ struct config_options {\n \n \tconst char *commondir;\n \tconst char *git_dir;\n+\t/*\n+\t * event_fn and event_fn_data are for internal use only. Handles events\n+\t * emitted by the config parser.\n+\t */\n \tconfig_parser_event_fn_t event_fn;\n \tvoid *event_fn_data;\n \tenum config_error_action {\n-- \ngitgitgadget\n"},{"id":"478590","messageId":"123e19dda4ad9458142943ab53e3a48534d5cfd7.1687290233.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"[PATCH v3 09/12] config: pass kvi to die_bad_number()","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-20T19:43:48Z","receivedAt":"2023-06-20T19:44:47Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPlumb \"struct key_value_info\" through all code paths that end in\ndie_bad_number(), which lets us remove the helper functions that read\nanalogous values from \"struct config_reader\". As a result, nothing reads\nconfig_reader.config_kvi any more, so remove that too.\n\nIn config.c, this requires changing the signature of\ngit_configset_get_value() to 'return' \"kvi\" in an out parameter so that\ngit_configset_get_<type>() can pass it to git_config_<type>(). Only\nnumeric types will use \"kvi\", so for non-numeric types (e.g.\ngit_configset_get_string()), pass NULL to indicate that the out\nparameter isn't needed.\n\nOutside of config.c, config callbacks now need to pass \"ctx->kvi\" to any\nof the git_config_<type>() functions that parse a config string into a\nnumber type. Included is a .cocci patch to make that refactor.\n\nThe only exceptional case is builtin/config.c, where git_config_<type>()\nis called outside of a config callback (namely, on user-provided input),\nso config source information has never been available. In this case,\ndie_bad_number() defaults to a generic, but perfectly descriptive\nmessage. Let's provide a safe, non-NULL for \"kvi\" anyway, but make sure\nnot to change the message.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n archive-tar.c                              |   4 +-\n builtin/commit-graph.c                     |   4 +-\n builtin/commit.c                           |  10 +-\n builtin/config.c                           |  21 +--\n builtin/fetch.c                            |   4 +-\n builtin/fsmonitor--daemon.c                |   6 +-\n builtin/grep.c                             |   2 +-\n builtin/index-pack.c                       |   4 +-\n builtin/log.c                              |   2 +-\n builtin/pack-objects.c                     |  14 +-\n builtin/receive-pack.c                     |  10 +-\n builtin/submodule--helper.c                |   4 +-\n config.c                                   | 156 ++++++++-------------\n config.h                                   |  17 ++-\n contrib/coccinelle/git_config_number.cocci |  27 ++++\n diff.c                                     |   9 +-\n fmt-merge-msg.c                            |   2 +-\n help.c                                     |   4 +-\n http.c                                     |  10 +-\n imap-send.c                                |   2 +-\n sequencer.c                                |  22 +--\n setup.c                                    |   2 +-\n submodule-config.c                         |  13 +-\n submodule-config.h                         |   3 +-\n t/helper/test-config.c                     |   6 +-\n trace2/tr2_cfg.c                           |   2 +-\n upload-pack.c                              |  12 +-\n worktree.c                                 |   2 +-\n 28 files changed, 191 insertions(+), 183 deletions(-)\n create mode 100644 contrib/coccinelle/git_config_number.cocci\n\ndiff --git a/archive-tar.c b/archive-tar.c\nindex ef06e516b1f..3df8af6d1b1 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -412,14 +412,14 @@ static int tar_filter_config(const char *var, const char *value,\n }\n \n static int git_tar_config(const char *var, const char *value,\n-\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"tar.umask\")) {\n \t\tif (value && !strcmp(value, \"user\")) {\n \t\t\ttar_umask = umask(0);\n \t\t\tumask(tar_umask);\n \t\t} else {\n-\t\t\ttar_umask = git_config_int(var, value);\n+\t\t\ttar_umask = git_config_int(var, value, ctx->kvi);\n \t\t}\n \t\treturn 0;\n \t}\ndiff --git a/builtin/commit-graph.c b/builtin/commit-graph.c\nindex 13d35b00ca8..c7e27a8c0a2 100644\n--- a/builtin/commit-graph.c\n+++ b/builtin/commit-graph.c\n@@ -186,11 +186,11 @@ static int write_option_max_new_filters(const struct option *opt,\n }\n \n static int git_commit_graph_write_config(const char *var, const char *value,\n-\t\t\t\t\t const struct config_context *ctx UNUSED,\n+\t\t\t\t\t const struct config_context *ctx,\n \t\t\t\t\t void *cb UNUSED)\n {\n \tif (!strcmp(var, \"commitgraph.maxnewfilters\"))\n-\t\twrite_opts.max_new_filters = git_config_int(var, value);\n+\t\twrite_opts.max_new_filters = git_config_int(var, value, ctx->kvi);\n \t/*\n \t * No need to fall-back to 'git_default_config', since this was already\n \t * called in 'cmd_commit_graph()'.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 3bc87e5fc97..4e5e7722238 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1415,7 +1415,8 @@ static int git_status_config(const char *k, const char *v,\n \t\treturn git_column_config(k, v, \"status\", &s->colopts);\n \tif (!strcmp(k, \"status.submodulesummary\")) {\n \t\tint is_bool;\n-\t\ts->submodule_summary = git_config_bool_or_int(k, v, &is_bool);\n+\t\ts->submodule_summary = git_config_bool_or_int(k, v, ctx->kvi,\n+\t\t\t\t\t\t\t      &is_bool);\n \t\tif (is_bool && s->submodule_summary)\n \t\t\ts->submodule_summary = -1;\n \t\treturn 0;\n@@ -1475,11 +1476,11 @@ static int git_status_config(const char *k, const char *v,\n \t}\n \tif (!strcmp(k, \"diff.renamelimit\")) {\n \t\tif (s->rename_limit == -1)\n-\t\t\ts->rename_limit = git_config_int(k, v);\n+\t\t\ts->rename_limit = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"status.renamelimit\")) {\n-\t\ts->rename_limit = git_config_int(k, v);\n+\t\ts->rename_limit = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"diff.renames\")) {\n@@ -1625,7 +1626,8 @@ static int git_commit_config(const char *k, const char *v,\n \t}\n \tif (!strcmp(k, \"commit.verbose\")) {\n \t\tint is_bool;\n-\t\tconfig_commit_verbose = git_config_bool_or_int(k, v, &is_bool);\n+\t\tconfig_commit_verbose = git_config_bool_or_int(k, v, ctx->kvi,\n+\t\t\t\t\t\t\t       &is_bool);\n \t\treturn 0;\n \t}\n \ndiff --git a/builtin/config.c b/builtin/config.c\nindex 0c01ded5f05..c61988a7bf3 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -261,13 +261,14 @@ static int format_config(struct strbuf *buf, const char *key_,\n \n \t\tif (type == TYPE_INT)\n \t\t\tstrbuf_addf(buf, \"%\"PRId64,\n-\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\"));\n+\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\", kvi));\n \t\telse if (type == TYPE_BOOL)\n \t\t\tstrbuf_addstr(buf, git_config_bool(key_, value_) ?\n \t\t\t\t      \"true\" : \"false\");\n \t\telse if (type == TYPE_BOOL_OR_INT) {\n \t\t\tint is_bool, v;\n-\t\t\tv = git_config_bool_or_int(key_, value_, &is_bool);\n+\t\t\tv = git_config_bool_or_int(key_, value_, kvi,\n+\t\t\t\t\t\t   &is_bool);\n \t\t\tif (is_bool)\n \t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n \t\t\telse\n@@ -422,7 +423,8 @@ free_strings:\n \treturn ret;\n }\n \n-static char *normalize_value(const char *key, const char *value)\n+static char *normalize_value(const char *key, const char *value,\n+\t\t\t     struct key_value_info *kvi)\n {\n \tif (!value)\n \t\treturn NULL;\n@@ -437,12 +439,12 @@ static char *normalize_value(const char *key, const char *value)\n \t\t */\n \t\treturn xstrdup(value);\n \tif (type == TYPE_INT)\n-\t\treturn xstrfmt(\"%\"PRId64, git_config_int64(key, value));\n+\t\treturn xstrfmt(\"%\"PRId64, git_config_int64(key, value, kvi));\n \tif (type == TYPE_BOOL)\n \t\treturn xstrdup(git_config_bool(key, value) ?  \"true\" : \"false\");\n \tif (type == TYPE_BOOL_OR_INT) {\n \t\tint is_bool, v;\n-\t\tv = git_config_bool_or_int(key, value, &is_bool);\n+\t\tv = git_config_bool_or_int(key, value, kvi, &is_bool);\n \t\tif (!is_bool)\n \t\t\treturn xstrfmt(\"%d\", v);\n \t\telse\n@@ -669,6 +671,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \tchar *value = NULL;\n \tint flags = 0;\n \tint ret = 0;\n+\tstruct key_value_info default_kvi = KVI_INIT;\n \n \tgiven_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));\n \n@@ -886,7 +889,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_SET) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 2);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n \t\tret = git_config_set_in_file_gently(given_config_source.file, argv[0], value);\n \t\tif (ret == CONFIG_NOTHING_SET)\n \t\t\terror(_(\"cannot overwrite multiple values with a single value\\n\"\n@@ -895,7 +898,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_SET_ALL) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 3);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value, argv[2],\n \t\t\t\t\t\t\t     flags);\n@@ -903,7 +906,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_ADD) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 2);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value,\n \t\t\t\t\t\t\t     CONFIG_REGEX_NONE,\n@@ -912,7 +915,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_REPLACE_ALL) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 3);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value, argv[2],\n \t\t\t\t\t\t\t     flags | CONFIG_FLAGS_MULTI_REPLACE);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex b607605b491..033d1fe65d2 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -138,7 +138,7 @@ static int git_fetch_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"submodule.fetchjobs\")) {\n-\t\tsubmodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v);\n+\t\tsubmodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v, ctx->kvi);\n \t\treturn 0;\n \t} else if (!strcmp(k, \"fetch.recursesubmodules\")) {\n \t\trecurse_submodules = parse_fetch_recurse_submodules_arg(k, v);\n@@ -146,7 +146,7 @@ static int git_fetch_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"fetch.parallel\")) {\n-\t\tfetch_parallel_config = git_config_int(k, v);\n+\t\tfetch_parallel_config = git_config_int(k, v, ctx->kvi);\n \t\tif (fetch_parallel_config < 0)\n \t\t\tdie(_(\"fetch.parallel cannot be negative\"));\n \t\tif (!fetch_parallel_config)\ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex 91a776e2f17..6d2826b07da 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -41,7 +41,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, FSMONITOR__IPC_THREADS)) {\n-\t\tint i = git_config_int(var, value);\n+\t\tint i = git_config_int(var, value, ctx->kvi);\n \t\tif (i < 1)\n \t\t\treturn error(_(\"value of '%s' out of range: %d\"),\n \t\t\t\t     FSMONITOR__IPC_THREADS, i);\n@@ -50,7 +50,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, FSMONITOR__START_TIMEOUT)) {\n-\t\tint i = git_config_int(var, value);\n+\t\tint i = git_config_int(var, value, ctx->kvi);\n \t\tif (i < 0)\n \t\t\treturn error(_(\"value of '%s' out of range: %d\"),\n \t\t\t\t     FSMONITOR__START_TIMEOUT, i);\n@@ -60,7 +60,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \n \tif (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) {\n \t\tint is_bool;\n-\t\tint i = git_config_bool_or_int(var, value, &is_bool);\n+\t\tint i = git_config_bool_or_int(var, value, ctx->kvi, &is_bool);\n \t\tif (i < 0)\n \t\t\treturn error(_(\"value of '%s' not bool or int: %d\"),\n \t\t\t\t     var, i);\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 757d52b94ec..3a464e6faab 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -301,7 +301,7 @@ static int grep_cmd_config(const char *var, const char *value,\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\n-\t\tnum_threads = git_config_int(var, value);\n+\t\tnum_threads = git_config_int(var, value, ctx->kvi);\n \t\tif (num_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d) for %s\"),\n \t\t\t    num_threads, var);\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex 31914aac298..857518fdf83 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1587,13 +1587,13 @@ static int git_index_pack_config(const char *k, const char *v,\n \tstruct pack_idx_option *opts = cb;\n \n \tif (!strcmp(k, \"pack.indexversion\")) {\n-\t\topts->version = git_config_int(k, v);\n+\t\topts->version = git_config_int(k, v, ctx->kvi);\n \t\tif (opts->version > 2)\n \t\t\tdie(_(\"bad pack.indexVersion=%\"PRIu32), opts->version);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.threads\")) {\n-\t\tnr_threads = git_config_int(k, v);\n+\t\tnr_threads = git_config_int(k, v, ctx->kvi);\n \t\tif (nr_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d)\"),\n \t\t\t    nr_threads);\ndiff --git a/builtin/log.c b/builtin/log.c\nindex ac5f74b6b06..675cf358f7a 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -574,7 +574,7 @@ static int git_log_config(const char *var, const char *value,\n \tif (!strcmp(var, \"format.subjectprefix\"))\n \t\treturn git_config_string(&fmt_patch_subject_prefix, var, value);\n \tif (!strcmp(var, \"format.filenamemaxlength\")) {\n-\t\tfmt_patch_name_max = git_config_int(var, value);\n+\t\tfmt_patch_name_max = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"format.encodeemailheaders\")) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex ad4d8fafb0b..d6d9079fd93 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3139,23 +3139,23 @@ static int git_pack_config(const char *k, const char *v,\n \t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(k, \"pack.window\")) {\n-\t\twindow = git_config_int(k, v);\n+\t\twindow = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.windowmemory\")) {\n-\t\twindow_memory_limit = git_config_ulong(k, v);\n+\t\twindow_memory_limit = git_config_ulong(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.depth\")) {\n-\t\tdepth = git_config_int(k, v);\n+\t\tdepth = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.deltacachesize\")) {\n-\t\tmax_delta_cache_size = git_config_int(k, v);\n+\t\tmax_delta_cache_size = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.deltacachelimit\")) {\n-\t\tcache_max_small_delta_size = git_config_int(k, v);\n+\t\tcache_max_small_delta_size = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.writebitmaphashcache\")) {\n@@ -3181,7 +3181,7 @@ static int git_pack_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.threads\")) {\n-\t\tdelta_search_threads = git_config_int(k, v);\n+\t\tdelta_search_threads = git_config_int(k, v, ctx->kvi);\n \t\tif (delta_search_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d)\"),\n \t\t\t    delta_search_threads);\n@@ -3192,7 +3192,7 @@ static int git_pack_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.indexversion\")) {\n-\t\tpack_idx_opts.version = git_config_int(k, v);\n+\t\tpack_idx_opts.version = git_config_int(k, v, ctx->kvi);\n \t\tif (pack_idx_opts.version > 2)\n \t\t\tdie(_(\"bad pack.indexVersion=%\"PRIu32),\n \t\t\t    pack_idx_opts.version);\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 94d9898aff7..98f6f0038f0 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -158,12 +158,12 @@ static int receive_pack_config(const char *var, const char *value,\n \t}\n \n \tif (strcmp(var, \"receive.unpacklimit\") == 0) {\n-\t\treceive_unpack_limit = git_config_int(var, value);\n+\t\treceive_unpack_limit = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (strcmp(var, \"transfer.unpacklimit\") == 0) {\n-\t\ttransfer_unpack_limit = git_config_int(var, value);\n+\t\ttransfer_unpack_limit = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n@@ -231,7 +231,7 @@ static int receive_pack_config(const char *var, const char *value,\n \t\treturn git_config_string(&cert_nonce_seed, var, value);\n \n \tif (strcmp(var, \"receive.certnonceslop\") == 0) {\n-\t\tnonce_stamp_slop_limit = git_config_ulong(var, value);\n+\t\tnonce_stamp_slop_limit = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n@@ -246,12 +246,12 @@ static int receive_pack_config(const char *var, const char *value,\n \t}\n \n \tif (strcmp(var, \"receive.keepalive\") == 0) {\n-\t\tkeepalive_in_sec = git_config_int(var, value);\n+\t\tkeepalive_in_sec = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (strcmp(var, \"receive.maxinputsize\") == 0) {\n-\t\tmax_input_size = git_config_int64(var, value);\n+\t\tmax_input_size = git_config_int64(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 3f8f3692937..9e78e5f7a9d 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2189,13 +2189,13 @@ static int update_clone_task_finished(int result,\n }\n \n static int git_update_clone_config(const char *var, const char *value,\n-\t\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t\t   const struct config_context *ctx,\n \t\t\t\t   void *cb)\n {\n \tint *max_jobs = cb;\n \n \tif (!strcmp(var, \"submodule.fetchjobs\"))\n-\t\t*max_jobs = parse_submodule_fetchjobs(var, value);\n+\t\t*max_jobs = parse_submodule_fetchjobs(var, value, ctx->kvi);\n \treturn 0;\n }\n \ndiff --git a/config.c b/config.c\nindex f519656ebcf..a2887b856aa 100644\n--- a/config.c\n+++ b/config.c\n@@ -73,18 +73,8 @@ struct config_reader {\n \t *\n \t * The \"source\" variable will be non-NULL only when we are actually\n \t * parsing a real config source (file, blob, cmdline, etc).\n-\t *\n-\t * The \"config_kvi\" variable will be non-NULL only when we are feeding\n-\t * cached config from a configset into a callback.\n-\t *\n-\t * They cannot be non-NULL at the same time. If they are both NULL, then\n-\t * we aren't parsing anything (and depending on the function looking at\n-\t * the variables, it's either a bug for it to be called in the first\n-\t * place, or it's a function which can be reused for non-config\n-\t * purposes, and should fall back to some sane behavior).\n \t */\n \tstruct config_source *source;\n-\tstruct key_value_info *config_kvi;\n };\n /*\n  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n@@ -96,8 +86,6 @@ static struct config_reader the_reader;\n static inline void config_reader_push_source(struct config_reader *reader,\n \t\t\t\t\t     struct config_source *top)\n {\n-\tif (reader->config_kvi)\n-\t\tBUG(\"source should not be set while iterating a config set\");\n \ttop->prev = reader->source;\n \treader->source = top;\n }\n@@ -112,12 +100,6 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\n \treturn ret;\n }\n \n-static inline void config_reader_set_kvi(struct config_reader *reader,\n-\t\t\t\t\t struct key_value_info *kvi)\n-{\n-\treader->config_kvi = kvi;\n-}\n-\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -1344,80 +1326,78 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)\n \treturn 1;\n }\n \n-static int reader_config_name(struct config_reader *reader, const char **out);\n-static int reader_origin_type(struct config_reader *reader,\n-\t\t\t      enum config_origin_type *type);\n NORETURN\n-static void die_bad_number(struct config_reader *reader, const char *name,\n-\t\t\t   const char *value)\n+static void die_bad_number(const char *name, const char *value,\n+\t\t\t   const struct key_value_info *kvi)\n {\n \tconst char *error_type = (errno == ERANGE) ?\n \t\tN_(\"out of range\") : N_(\"invalid unit\");\n \tconst char *bad_numeric = N_(\"bad numeric config value '%s' for '%s': %s\");\n-\tconst char *config_name = NULL;\n-\tenum config_origin_type config_origin = CONFIG_ORIGIN_UNKNOWN;\n+\n+\tif (!kvi)\n+\t\tBUG(\"kvi should not be NULL\");\n \n \tif (!value)\n \t\tvalue = \"\";\n \n-\t/* Ignoring the return value is okay since we handle missing values. */\n-\treader_config_name(reader, &config_name);\n-\treader_origin_type(reader, &config_origin);\n-\n-\tif (!config_name)\n+\tif (!kvi->filename)\n \t\tdie(_(bad_numeric), value, name, _(error_type));\n \n-\tswitch (config_origin) {\n+\tswitch (kvi->origin_type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in blob %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_FILE:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in file %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_STDIN:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in standard input: %s\"),\n \t\t    value, name, _(error_type));\n \tcase CONFIG_ORIGIN_SUBMODULE_BLOB:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in submodule-blob %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_CMDLINE:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in command line %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tdefault:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \t}\n }\n \n-int git_config_int(const char *name, const char *value)\n+int git_config_int(const char *name, const char *value,\n+\t\t   const struct key_value_info *kvi)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-int64_t git_config_int64(const char *name, const char *value)\n+int64_t git_config_int64(const char *name, const char *value,\n+\t\t\t const struct key_value_info *kvi)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-unsigned long git_config_ulong(const char *name, const char *value)\n+unsigned long git_config_ulong(const char *name, const char *value,\n+\t\t\t       const struct key_value_info *kvi)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-ssize_t git_config_ssize_t(const char *name, const char *value)\n+ssize_t git_config_ssize_t(const char *name, const char *value,\n+\t\t\t   const struct key_value_info *kvi)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n@@ -1522,7 +1502,8 @@ int git_parse_maybe_bool(const char *value)\n \treturn -1;\n }\n \n-int git_config_bool_or_int(const char *name, const char *value, int *is_bool)\n+int git_config_bool_or_int(const char *name, const char *value,\n+\t\t\t   const struct key_value_info *kvi, int *is_bool)\n {\n \tint v = git_parse_maybe_bool_text(value);\n \tif (0 <= v) {\n@@ -1530,7 +1511,7 @@ int git_config_bool_or_int(const char *name, const char *value, int *is_bool)\n \t\treturn v;\n \t}\n \t*is_bool = 0;\n-\treturn git_config_int(name, value);\n+\treturn git_config_int(name, value, kvi);\n }\n \n int git_config_bool(const char *name, const char *value)\n@@ -1656,7 +1637,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t\telse if (!git_parse_maybe_bool_text(value))\n \t\t\tdefault_abbrev = the_hash_algo->hexsz;\n \t\telse {\n-\t\t\tint abbrev = git_config_int(var, value);\n+\t\t\tint abbrev = git_config_int(var, value, ctx->kvi);\n \t\t\tif (abbrev < minimum_abbrev || abbrev > the_hash_algo->hexsz)\n \t\t\t\treturn error(_(\"abbrev length out of range: %d\"), abbrev);\n \t\t\tdefault_abbrev = abbrev;\n@@ -1668,7 +1649,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t\treturn set_disambiguate_hint_config(var, value);\n \n \tif (!strcmp(var, \"core.loosecompression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, ctx->kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -1679,7 +1660,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.compression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, ctx->kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -1693,7 +1674,7 @@ static int git_default_core_config(const char *var, const char *value,\n \n \tif (!strcmp(var, \"core.packedgitwindowsize\")) {\n \t\tint pgsz_x2 = getpagesize() * 2;\n-\t\tpacked_git_window_size = git_config_ulong(var, value);\n+\t\tpacked_git_window_size = git_config_ulong(var, value, ctx->kvi);\n \n \t\t/* This value must be multiple of (pagesize * 2) */\n \t\tpacked_git_window_size /= pgsz_x2;\n@@ -1704,17 +1685,17 @@ static int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.bigfilethreshold\")) {\n-\t\tbig_file_threshold = git_config_ulong(var, value);\n+\t\tbig_file_threshold = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"core.packedgitlimit\")) {\n-\t\tpacked_git_limit = git_config_ulong(var, value);\n+\t\tpacked_git_limit = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"core.deltabasecachelimit\")) {\n-\t\tdelta_base_cache_limit = git_config_ulong(var, value);\n+\t\tdelta_base_cache_limit = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n@@ -1998,12 +1979,12 @@ int git_default_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"pack.packsizelimit\")) {\n-\t\tpack_size_limit_cfg = git_config_ulong(var, value);\n+\t\tpack_size_limit_cfg = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"pack.compression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, ctx->kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -2336,13 +2317,11 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \t\tvalue_index = list->items[i].value_index;\n \t\tvalues = &entry->value_list;\n \n-\t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n \t\tctx.kvi = values->items[value_index].util;\n \t\tif (fn(entry->key, values->items[value_index].string, &ctx, data) < 0)\n \t\t\tgit_die_config_linenr(entry->key,\n \t\t\t\t\t      ctx.kvi->filename,\n \t\t\t\t\t      ctx.kvi->linenr);\n-\t\tconfig_reader_set_kvi(reader, NULL);\n \t}\n }\n \n@@ -2526,11 +2505,12 @@ int git_configset_add_file(struct config_set *set, const char *filename)\n \treturn git_config_from_file(config_set_callback, filename, &data);\n }\n \n-int git_configset_get_value(struct config_set *set, const char *key, const char **value)\n+int git_configset_get_value(struct config_set *set, const char *key,\n+\t\t\t    const char **value, struct key_value_info *kvi)\n {\n \tconst struct string_list *values = NULL;\n \tint ret;\n-\n+\tstruct string_list_item item;\n \t/*\n \t * Follows \"last one wins\" semantic, i.e., if there are multiple matches for the\n \t * queried key in the files of the configset, the value returned will be the last\n@@ -2540,7 +2520,10 @@ int git_configset_get_value(struct config_set *set, const char *key, const char\n \t\treturn ret;\n \n \tassert(values->nr > 0);\n-\t*value = values->items[values->nr - 1].string;\n+\titem = values->items[values->nr - 1];\n+\t*value = item.string;\n+\tif (kvi)\n+\t\t*kvi = *((struct key_value_info *)item.util);\n \treturn 0;\n }\n \n@@ -2593,7 +2576,7 @@ int git_configset_get(struct config_set *set, const char *key)\n int git_configset_get_string(struct config_set *set, const char *key, char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value))\n+\tif (!git_configset_get_value(set, key, &value, NULL))\n \t\treturn git_config_string((const char **)dest, key, value);\n \telse\n \t\treturn 1;\n@@ -2603,7 +2586,7 @@ static int git_configset_get_string_tmp(struct config_set *set, const char *key,\n \t\t\t\t\tconst char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(key);\n \t\t*dest = value;\n@@ -2616,8 +2599,10 @@ static int git_configset_get_string_tmp(struct config_set *set, const char *key,\n int git_configset_get_int(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_int(key, value);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_int(key, value, &kvi);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2626,8 +2611,10 @@ int git_configset_get_int(struct config_set *set, const char *key, int *dest)\n int git_configset_get_ulong(struct config_set *set, const char *key, unsigned long *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_ulong(key, value);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_ulong(key, value, &kvi);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2636,7 +2623,7 @@ int git_configset_get_ulong(struct config_set *set, const char *key, unsigned lo\n int git_configset_get_bool(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\t*dest = git_config_bool(key, value);\n \t\treturn 0;\n \t} else\n@@ -2647,8 +2634,10 @@ int git_configset_get_bool_or_int(struct config_set *set, const char *key,\n \t\t\t\tint *is_bool, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_bool_or_int(key, value, is_bool);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_bool_or_int(key, value, &kvi, is_bool);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2657,7 +2646,7 @@ int git_configset_get_bool_or_int(struct config_set *set, const char *key,\n int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\t*dest = git_parse_maybe_bool(value);\n \t\tif (*dest == -1)\n \t\t\treturn -1;\n@@ -2669,7 +2658,7 @@ int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *d\n int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value))\n+\tif (!git_configset_get_value(set, key, &value, NULL))\n \t\treturn git_config_pathname(dest, key, value);\n \telse\n \t\treturn 1;\n@@ -2739,7 +2728,7 @@ int repo_config_get_value(struct repository *repo,\n \t\t\t  const char *key, const char **value)\n {\n \tgit_config_check_init(repo);\n-\treturn git_configset_get_value(repo->config, key, value);\n+\treturn git_configset_get_value(repo->config, key, value, NULL);\n }\n \n int repo_config_get_value_multi(struct repository *repo, const char *key,\n@@ -3977,18 +3966,6 @@ int parse_config_key(const char *var,\n \treturn 0;\n }\n \n-static int reader_origin_type(struct config_reader *reader,\n-\t\t\t      enum config_origin_type *type)\n-{\n-\tif (the_reader.config_kvi)\n-\t\t*type = reader->config_kvi->origin_type;\n-\telse if(the_reader.source)\n-\t\t*type = reader->source->origin_type;\n-\telse\n-\t\treturn 1;\n-\treturn 0;\n-}\n-\n const char *config_origin_type_name(enum config_origin_type type)\n {\n \tswitch (type) {\n@@ -4027,17 +4004,6 @@ const char *config_scope_name(enum config_scope scope)\n \t}\n }\n \n-static int reader_config_name(struct config_reader *reader, const char **out)\n-{\n-\tif (the_reader.config_kvi)\n-\t\t*out = reader->config_kvi->filename;\n-\telse if (the_reader.source)\n-\t\t*out = reader->source->name;\n-\telse\n-\t\treturn 1;\n-\treturn 0;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex 092db17d2e4..a8f41c59e6c 100644\n--- a/config.h\n+++ b/config.h\n@@ -249,22 +249,26 @@ int git_parse_maybe_bool(const char *);\n  * Parse the string to an integer, including unit factors. Dies on error;\n  * otherwise, returns the parsed result.\n  */\n-int git_config_int(const char *, const char *);\n+int git_config_int(const char *, const char *, const struct key_value_info *);\n \n-int64_t git_config_int64(const char *, const char *);\n+int64_t git_config_int64(const char *, const char *,\n+\t\t\t const struct key_value_info *);\n \n /**\n  * Identical to `git_config_int`, but for unsigned longs.\n  */\n-unsigned long git_config_ulong(const char *, const char *);\n+unsigned long git_config_ulong(const char *, const char *,\n+\t\t\t       const struct key_value_info *);\n \n-ssize_t git_config_ssize_t(const char *, const char *);\n+ssize_t git_config_ssize_t(const char *, const char *,\n+\t\t\t   const struct key_value_info *);\n \n /**\n  * Same as `git_config_bool`, except that integers are returned as-is, and\n  * an `is_bool` flag is unset.\n  */\n-int git_config_bool_or_int(const char *, const char *, int *);\n+int git_config_bool_or_int(const char *, const char *,\n+\t\t\t   const struct key_value_info *, int *);\n \n /**\n  * Parse a string into a boolean value, respecting keywords like \"true\" and\n@@ -529,7 +533,8 @@ int git_configset_get(struct config_set *cs, const char *key);\n  * touching `value`. The caller should not free or modify `value`, as it\n  * is owned by the cache.\n  */\n-int git_configset_get_value(struct config_set *cs, const char *key, const char **dest);\n+int git_configset_get_value(struct config_set *cs, const char *key,\n+\t\t\t    const char **dest, struct key_value_info *kvi);\n \n int git_configset_get_string(struct config_set *cs, const char *key, char **dest);\n int git_configset_get_int(struct config_set *cs, const char *key, int *dest);\ndiff --git a/contrib/coccinelle/git_config_number.cocci b/contrib/coccinelle/git_config_number.cocci\nnew file mode 100644\nindex 00000000000..7b57dceefe6\n--- /dev/null\n+++ b/contrib/coccinelle/git_config_number.cocci\n@@ -0,0 +1,27 @@\n+@@\n+identifier C1, C2, C3;\n+@@\n+(\n+(\n+git_config_int\n+|\n+git_config_int64\n+|\n+git_config_ulong\n+|\n+git_config_ssize_t\n+)\n+  (C1, C2\n++ , ctx->kvi\n+  )\n+|\n+(\n+git_configset_get_value\n+|\n+git_config_bool_or_int\n+)\n+  (C1, C2\n++ , ctx->kvi\n+ , C3\n+  )\n+)\ndiff --git a/diff.c b/diff.c\nindex 07be3bee137..8775323e39c 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -379,13 +379,14 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.context\")) {\n-\t\tdiff_context_default = git_config_int(var, value);\n+\t\tdiff_context_default = git_config_int(var, value, ctx->kvi);\n \t\tif (diff_context_default < 0)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.interhunkcontext\")) {\n-\t\tdiff_interhunk_context_default = git_config_int(var, value);\n+\t\tdiff_interhunk_context_default = git_config_int(var, value,\n+\t\t\t\t\t\t\t\tctx->kvi);\n \t\tif (diff_interhunk_context_default < 0)\n \t\t\treturn -1;\n \t\treturn 0;\n@@ -411,7 +412,7 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.statgraphwidth\")) {\n-\t\tdiff_stat_graph_width = git_config_int(var, value);\n+\t\tdiff_stat_graph_width = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.external\"))\n@@ -450,7 +451,7 @@ int git_diff_basic_config(const char *var, const char *value,\n \tconst char *name;\n \n \tif (!strcmp(var, \"diff.renamelimit\")) {\n-\t\tdiff_rename_limit_default = git_config_int(var, value);\n+\t\tdiff_rename_limit_default = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 10137444321..3ae59bde2f2 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -25,7 +25,7 @@ int fmt_merge_msg_config(const char *key, const char *value,\n {\n \tif (!strcmp(key, \"merge.log\") || !strcmp(key, \"merge.summary\")) {\n \t\tint is_bool;\n-\t\tmerge_log_config = git_config_bool_or_int(key, value, &is_bool);\n+\t\tmerge_log_config = git_config_bool_or_int(key, value, ctx->kvi, &is_bool);\n \t\tif (!is_bool && merge_log_config < 0)\n \t\t\treturn error(\"%s: negative length %s\", key, value);\n \t\tif (is_bool && merge_log_config)\ndiff --git a/help.c b/help.c\nindex ac0ae5ac0dc..389382b1482 100644\n--- a/help.c\n+++ b/help.c\n@@ -545,7 +545,7 @@ static struct cmdnames aliases;\n #define AUTOCORRECT_IMMEDIATELY (-1)\n \n static int git_unknown_cmd_config(const char *var, const char *value,\n-\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t  const struct config_context *ctx,\n \t\t\t\t  void *cb UNUSED)\n {\n \tconst char *p;\n@@ -560,7 +560,7 @@ static int git_unknown_cmd_config(const char *var, const char *value,\n \t\t} else if (!strcmp(value, \"prompt\")) {\n \t\t\tautocorrect = AUTOCORRECT_PROMPT;\n \t\t} else {\n-\t\t\tint v = git_config_int(var, value);\n+\t\t\tint v = git_config_int(var, value, ctx->kvi);\n \t\t\tautocorrect = (v < 0)\n \t\t\t\t? AUTOCORRECT_IMMEDIATELY : v;\n \t\t}\ndiff --git a/http.c b/http.c\nindex 762502828c9..b12b234bde1 100644\n--- a/http.c\n+++ b/http.c\n@@ -414,21 +414,21 @@ static int http_options(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(\"http.minsessions\", var)) {\n-\t\tmin_curl_sessions = git_config_int(var, value);\n+\t\tmin_curl_sessions = git_config_int(var, value, ctx->kvi);\n \t\tif (min_curl_sessions > 1)\n \t\t\tmin_curl_sessions = 1;\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.maxrequests\", var)) {\n-\t\tmax_requests = git_config_int(var, value);\n+\t\tmax_requests = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.lowspeedlimit\", var)) {\n-\t\tcurl_low_speed_limit = (long)git_config_int(var, value);\n+\t\tcurl_low_speed_limit = (long)git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.lowspeedtime\", var)) {\n-\t\tcurl_low_speed_time = (long)git_config_int(var, value);\n+\t\tcurl_low_speed_time = (long)git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n@@ -464,7 +464,7 @@ static int http_options(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(\"http.postbuffer\", var)) {\n-\t\thttp_post_buffer = git_config_ssize_t(var, value);\n+\t\thttp_post_buffer = git_config_ssize_t(var, value, ctx->kvi);\n \t\tif (http_post_buffer < 0)\n \t\t\twarning(_(\"negative value for http.postBuffer; defaulting to %d\"), LARGE_PACKET_MAX);\n \t\tif (http_post_buffer < LARGE_PACKET_MAX)\ndiff --git a/imap-send.c b/imap-send.c\nindex 47777e76861..3518a4ace6c 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1342,7 +1342,7 @@ static int git_imap_config(const char *var, const char *val,\n \telse if (!strcmp(\"imap.authmethod\", var))\n \t\treturn git_config_string(&server.auth_method, var, val);\n \telse if (!strcmp(\"imap.port\", var))\n-\t\tserver.port = git_config_int(var, val);\n+\t\tserver.port = git_config_int(var, val, ctx->kvi);\n \telse if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\tgit_die_config(\"imap.host\", \"Missing value for 'imap.host'\");\ndiff --git a/sequencer.c b/sequencer.c\nindex 34754d17596..a0aaee3056d 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2883,7 +2883,7 @@ static int git_config_string_dup(char **dest,\n }\n \n static int populate_opts_cb(const char *key, const char *value,\n-\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    const struct config_context *ctx,\n \t\t\t    void *data)\n {\n \tstruct replay_opts *opts = data;\n@@ -2892,26 +2892,26 @@ static int populate_opts_cb(const char *key, const char *value,\n \tif (!value)\n \t\terror_flag = 0;\n \telse if (!strcmp(key, \"options.no-commit\"))\n-\t\topts->no_commit = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->no_commit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.edit\"))\n-\t\topts->edit = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->edit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-empty\"))\n \t\topts->allow_empty =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-empty-message\"))\n \t\topts->allow_empty_message =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.keep-redundant-commits\"))\n \t\topts->keep_redundant_commits =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.signoff\"))\n-\t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->signoff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.record-origin\"))\n-\t\topts->record_origin = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->record_origin = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-ff\"))\n-\t\topts->allow_ff = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->allow_ff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.mainline\"))\n-\t\topts->mainline = git_config_int(key, value);\n+\t\topts->mainline = git_config_int(key, value, ctx->kvi);\n \telse if (!strcmp(key, \"options.strategy\"))\n \t\tgit_config_string_dup(&opts->strategy, key, value);\n \telse if (!strcmp(key, \"options.gpg-sign\"))\n@@ -2920,7 +2920,7 @@ static int populate_opts_cb(const char *key, const char *value,\n \t\tstrvec_push(&opts->xopts, value);\n \t} else if (!strcmp(key, \"options.allow-rerere-auto\"))\n \t\topts->allow_rerere_auto =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag) ?\n+\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag) ?\n \t\t\t\tRERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;\n \telse if (!strcmp(key, \"options.default-msg-cleanup\")) {\n \t\topts->explicit_cleanup = 1;\ndiff --git a/setup.c b/setup.c\nindex e7f81151ef0..260ad92df6e 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -597,7 +597,7 @@ static int check_repo_format(const char *var, const char *value,\n \tconst char *ext;\n \n \tif (strcmp(var, \"core.repositoryformatversion\") == 0)\n-\t\tdata->version = git_config_int(var, value);\n+\t\tdata->version = git_config_int(var, value, ctx->kvi);\n \telse if (skip_prefix(var, \"extensions.\", &ext)) {\n \t\tswitch (handle_extension_v0(var, value, ext, data)) {\n \t\tcase EXTENSION_ERROR:\ndiff --git a/submodule-config.c b/submodule-config.c\nindex e19ab593aa4..0cda0f659f2 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -304,9 +304,10 @@ static int parse_fetch_recurse(const char *opt, const char *arg,\n \t}\n }\n \n-int parse_submodule_fetchjobs(const char *var, const char *value)\n+int parse_submodule_fetchjobs(const char *var, const char *value,\n+\t\t\t      const struct key_value_info *kvi)\n {\n-\tint fetchjobs = git_config_int(var, value);\n+\tint fetchjobs = git_config_int(var, value, kvi);\n \tif (fetchjobs < 0)\n \t\tdie(_(\"negative values not allowed for submodule.fetchJobs\"));\n \tif (!fetchjobs)\n@@ -850,14 +851,14 @@ struct fetch_config {\n };\n \n static int gitmodules_fetch_config(const char *var, const char *value,\n-\t\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t\t   const struct config_context *ctx,\n \t\t\t\t   void *cb)\n {\n \tstruct fetch_config *config = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\")) {\n \t\tif (config->max_children)\n \t\t\t*(config->max_children) =\n-\t\t\t\tparse_submodule_fetchjobs(var, value);\n+\t\t\t\tparse_submodule_fetchjobs(var, value, ctx->kvi);\n \t\treturn 0;\n \t} else if (!strcmp(var, \"fetch.recursesubmodules\")) {\n \t\tif (config->recurse_submodules)\n@@ -879,12 +880,12 @@ void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)\n }\n \n static int gitmodules_update_clone_config(const char *var, const char *value,\n-\t\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t\t  const struct config_context *ctx,\n \t\t\t\t\t  void *cb)\n {\n \tint *max_jobs = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\"))\n-\t\t*max_jobs = parse_submodule_fetchjobs(var, value);\n+\t\t*max_jobs = parse_submodule_fetchjobs(var, value, ctx->kvi);\n \treturn 0;\n }\n \ndiff --git a/submodule-config.h b/submodule-config.h\nindex c2045875bbb..2a37689cc27 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -50,7 +50,8 @@ struct repository;\n \n void submodule_cache_free(struct submodule_cache *cache);\n \n-int parse_submodule_fetchjobs(const char *var, const char *value);\n+int parse_submodule_fetchjobs(const char *var, const char *value,\n+\t\t\t      const struct key_value_info *kvi);\n int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);\n struct option;\n int option_fetch_parse_recurse_submodules(const struct option *opt,\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 3f4c3678318..ed444ca4c25 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -63,12 +63,12 @@ static int iterate_cb(const char *var, const char *value,\n }\n \n static int parse_int_cb(const char *var, const char *value,\n-\t\t\tconst struct config_context *ctx UNUSED, void *data)\n+\t\t\tconst struct config_context *ctx, void *data)\n {\n \tconst char *key_to_match = data;\n \n \tif (!strcmp(key_to_match, var)) {\n-\t\tint parsed = git_config_int(value, value);\n+\t\tint parsed = git_config_int(value, value, ctx->kvi);\n \t\tprintf(\"%d\\n\", parsed);\n \t}\n \treturn 0;\n@@ -182,7 +182,7 @@ int cmd__config(int argc, const char **argv)\n \t\t\t\tgoto exit2;\n \t\t\t}\n \t\t}\n-\t\tif (!git_configset_get_value(&cs, argv[2], &v)) {\n+\t\tif (!git_configset_get_value(&cs, argv[2], &v, NULL)) {\n \t\t\tif (!v)\n \t\t\t\tprintf(\"(NULL)\\n\");\n \t\t\telse\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 00d546a27cd..733d9d2872a 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -100,7 +100,7 @@ struct tr2_cfg_data {\n  * See if the given config key matches any of our patterns of interest.\n  */\n static int tr2_cfg_cb(const char *key, const char *value,\n-\t\t      const struct config_context *ctx UNUSED, void *d)\n+\t\t      const struct config_context *ctx, void *d)\n {\n \tstruct strbuf **s;\n \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 951fd1f9c25..c03415a5460 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1275,7 +1275,8 @@ static int find_symref(const char *refname,\n }\n \n static int parse_object_filter_config(const char *var, const char *value,\n-\t\t\t\t       struct upload_pack_data *data)\n+\t\t\t\t      const struct key_value_info *kvi,\n+\t\t\t\t      struct upload_pack_data *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *sub, *key;\n@@ -1302,7 +1303,8 @@ static int parse_object_filter_config(const char *var, const char *value,\n \t\t}\n \t\tstring_list_insert(&data->allowed_filters, buf.buf)->util =\n \t\t\t(void *)(intptr_t)1;\n-\t\tdata->tree_filter_max_depth = git_config_ulong(var, value);\n+\t\tdata->tree_filter_max_depth = git_config_ulong(var, value,\n+\t\t\t\t\t\t\t       kvi);\n \t}\n \n \tstrbuf_release(&buf);\n@@ -1310,7 +1312,7 @@ static int parse_object_filter_config(const char *var, const char *value,\n }\n \n static int upload_pack_config(const char *var, const char *value,\n-\t\t\t      const struct config_context *ctx UNUSED,\n+\t\t\t      const struct config_context *ctx,\n \t\t\t      void *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n@@ -1331,7 +1333,7 @@ static int upload_pack_config(const char *var, const char *value,\n \t\telse\n \t\t\tdata->allow_uor &= ~ALLOW_ANY_SHA1;\n \t} else if (!strcmp(\"uploadpack.keepalive\", var)) {\n-\t\tdata->keepalive = git_config_int(var, value);\n+\t\tdata->keepalive = git_config_int(var, value, ctx->kvi);\n \t\tif (!data->keepalive)\n \t\t\tdata->keepalive = -1;\n \t} else if (!strcmp(\"uploadpack.allowfilter\", var)) {\n@@ -1346,7 +1348,7 @@ static int upload_pack_config(const char *var, const char *value,\n \t\tdata->advertise_sid = git_config_bool(var, value);\n \t}\n \n-\tif (parse_object_filter_config(var, value, data) < 0)\n+\tif (parse_object_filter_config(var, value, ctx->kvi, data) < 0)\n \t\treturn -1;\n \n \treturn parse_hide_refs_config(var, value, \"uploadpack\", &data->hidden_refs);\ndiff --git a/worktree.c b/worktree.c\nindex b5ee71c5ebd..1fbdbd745fb 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -835,7 +835,7 @@ int init_worktree_config(struct repository *r)\n \t * Relocate that value to avoid breaking all worktrees with this\n \t * upgrade to worktree config.\n \t */\n-\tif (!git_configset_get_value(&cs, \"core.worktree\", &core_worktree)) {\n+\tif (!git_configset_get_value(&cs, \"core.worktree\", &core_worktree, NULL)) {\n \t\tif ((res = move_config_setting(\"core.worktree\", core_worktree,\n \t\t\t\t\t       common_config_file,\n \t\t\t\t\t       main_worktree_file)))\n-- \ngitgitgadget\n\n"},{"id":"478598","messageId":"xmqqjzvxlufa.fsf@gitster.g","threadId":"59629","inReplyTo":"26109b65142d2a325201940262a6c0bf183ec4bd.1687290232.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 01/12] config: inline git_color_default_config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-20T21:01:45Z","receivedAt":"2023-06-20T21:01:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Glen Choo <chooglen@google.com>\n>\n> git_color_default_config() is a shorthand for calling two other config\n> callbacks. There are no other non-static functions that do this and it\n> will complicate our refactoring of config_fn_t so inline it instead.\n>\n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n\nWe would need to deal with git_default_config() that is called from\neverywhere in exactly the same way, so I am not sure if the presence\nof git_color_default_config() will complicate so much to require\nthis step while git_default_config() can be dealt with without\ninlining into its callers, but this change does not hurt anybody\neither.\n\nOK.  \n\n>  builtin/branch.c      | 5 ++++-\n>  builtin/clean.c       | 6 ++++--\n>  builtin/grep.c        | 5 ++++-\n>  builtin/show-branch.c | 5 ++++-\n>  builtin/tag.c         | 6 +++++-\n>  color.c               | 8 --------\n>  color.h               | 6 +-----\n>  7 files changed, 22 insertions(+), 19 deletions(-)\n>\n> diff --git a/builtin/branch.c b/builtin/branch.c\n> index e6c2655af68..df99e38847b 100644\n> --- a/builtin/branch.c\n> +++ b/builtin/branch.c\n> @@ -117,7 +117,10 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n>  \t\treturn 0;\n>  \t}\n>  \n> -\treturn git_color_default_config(var, value, cb);\n> +\tif (git_color_config(var, value, cb) < 0)\n> +\t\treturn -1;\n> +\n> +\treturn git_default_config(var, value, cb);\n>  }\n>  \n>  static const char *branch_get_color(enum color_branch ix)\n> diff --git a/builtin/clean.c b/builtin/clean.c\n> index 78852d28cec..57e7f7cac64 100644\n> --- a/builtin/clean.c\n> +++ b/builtin/clean.c\n> @@ -130,8 +130,10 @@ static int git_clean_config(const char *var, const char *value, void *cb)\n>  \t\treturn 0;\n>  \t}\n>  \n> -\t/* inspect the color.ui config variable and others */\n> -\treturn git_color_default_config(var, value, cb);\n> +\tif (git_color_config(var, value, cb) < 0)\n> +\t\treturn -1;\n> +\n> +\treturn git_default_config(var, value, cb);\n>  }\n>  \n>  static const char *clean_get_color(enum color_clean ix)\n> diff --git a/builtin/grep.c b/builtin/grep.c\n> index b86c754defb..76cf999d310 100644\n> --- a/builtin/grep.c\n> +++ b/builtin/grep.c\n> @@ -293,7 +293,10 @@ static int wait_all(void)\n>  static int grep_cmd_config(const char *var, const char *value, void *cb)\n>  {\n>  \tint st = grep_config(var, value, cb);\n> -\tif (git_color_default_config(var, value, NULL) < 0)\n> +\n> +\tif (git_color_config(var, value, cb) < 0)\n> +\t\tst = -1;\n> +\telse if (git_default_config(var, value, cb) < 0)\n>  \t\tst = -1;\n>  \n>  \tif (!strcmp(var, \"grep.threads\")) {\n> diff --git a/builtin/show-branch.c b/builtin/show-branch.c\n> index 7ef4a642c17..a2461270d4b 100644\n> --- a/builtin/show-branch.c\n> +++ b/builtin/show-branch.c\n> @@ -579,7 +579,10 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)\n>  \t\treturn 0;\n>  \t}\n>  \n> -\treturn git_color_default_config(var, value, cb);\n> +\tif (git_color_config(var, value, cb) < 0)\n> +\t\treturn -1;\n> +\n> +\treturn git_default_config(var, value, cb);\n>  }\n>  \n>  static int omit_in_dense(struct commit *commit, struct commit **rev, int n)\n> diff --git a/builtin/tag.c b/builtin/tag.c\n> index 49b64c7a288..1acf5f7a59f 100644\n> --- a/builtin/tag.c\n> +++ b/builtin/tag.c\n> @@ -209,7 +209,11 @@ static int git_tag_config(const char *var, const char *value, void *cb)\n>  \n>  \tif (starts_with(var, \"column.\"))\n>  \t\treturn git_column_config(var, value, \"tag\", &colopts);\n> -\treturn git_color_default_config(var, value, cb);\n> +\n> +\tif (git_color_config(var, value, cb) < 0)\n> +\t\treturn -1;\n> +\n> +\treturn git_default_config(var, value, cb);\n>  }\n>  \n>  static void write_tag_body(int fd, const struct object_id *oid)\n> diff --git a/color.c b/color.c\n> index 83abb11eda0..b24b19566b9 100644\n> --- a/color.c\n> +++ b/color.c\n> @@ -430,14 +430,6 @@ int git_color_config(const char *var, const char *value, void *cb UNUSED)\n>  \treturn 0;\n>  }\n>  \n> -int git_color_default_config(const char *var, const char *value, void *cb)\n> -{\n> -\tif (git_color_config(var, value, cb) < 0)\n> -\t\treturn -1;\n> -\n> -\treturn git_default_config(var, value, cb);\n> -}\n> -\n>  void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb)\n>  {\n>  \tif (*color)\n> diff --git a/color.h b/color.h\n> index cfc8f841b23..bb28343be21 100644\n> --- a/color.h\n> +++ b/color.h\n> @@ -88,12 +88,8 @@ extern const int column_colors_ansi_max;\n>   */\n>  extern int color_stdout_is_tty;\n>  \n> -/*\n> - * Use the first one if you need only color config; the second is a convenience\n> - * if you are just going to change to git_default_config, too.\n> - */\n> +/* Parse color config. */\n>  int git_color_config(const char *var, const char *value, void *cb);\n> -int git_color_default_config(const char *var, const char *value, void *cb);\n>  \n>  /*\n>   * Parse a config option, which can be a boolean or one of\n"},{"id":"478599","messageId":"xmqqfs6llue4.fsf@gitster.g","threadId":"59629","inReplyTo":"1aeffcb13956988dfb7b925f89bbe9c8503cb143.1687290232.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 02/12] urlmatch.h: use config_fn_t type","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-20T21:02:27Z","receivedAt":"2023-06-20T21:02:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Glen Choo <chooglen@google.com>\n>\n> These are actually used as config callbacks, so use the typedef-ed type\n> and make future refactors easier.\n>\n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n>  urlmatch.h | 5 +++--\n>  1 file changed, 3 insertions(+), 2 deletions(-)\n>\n> diff --git a/urlmatch.h b/urlmatch.h\n> index 9f40b00bfb8..bee374a642c 100644\n> --- a/urlmatch.h\n> +++ b/urlmatch.h\n> @@ -2,6 +2,7 @@\n>  #define URL_MATCH_H\n>  \n>  #include \"string-list.h\"\n> +#include \"config.h\"\n>  \n>  struct url_info {\n>  \t/* normalized url on success, must be freed, otherwise NULL */\n> @@ -48,8 +49,8 @@ struct urlmatch_config {\n>  \tconst char *key;\n>  \n>  \tvoid *cb;\n> -\tint (*collect_fn)(const char *var, const char *value, void *cb);\n> -\tint (*cascade_fn)(const char *var, const char *value, void *cb);\n> +\tconfig_fn_t collect_fn;\n> +\tconfig_fn_t cascade_fn;\n\nMakes sense.\n\n>  \t/*\n>  \t * Compare the two matches, the one just discovered and the existing\n>  \t * best match and return a negative value if the found item is to be\n"},{"id":"478600","messageId":"xmqqa5wtltlm.fsf@gitster.g","threadId":"59629","inReplyTo":"c5051ddc10dfa9665fbcbed30875369c4e972d17.1687290233.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 04/12] config.c: pass ctx in configsets","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-20T21:19:33Z","receivedAt":"2023-06-20T21:19:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n>  static int config_read_push_default(const char *key, const char *value,\n> -\tconst struct config_context *ctx UNUSED, void *cb)\n> +\tconst struct config_context *ctx, void *cb)\n>  {\n> +\tconst struct key_value_info *kvi = ctx->kvi;\n> +\n>  \tstruct push_default_info* info = cb;\n>  \tif (strcmp(key, \"remote.pushdefault\") ||\n>  \t    !value || strcmp(value, info->old_name))\n>  \t\treturn 0;\n>  \n> -\tinfo->scope = current_config_scope();\n> +\tinfo->scope = kvi->scope;\n>  \tstrbuf_reset(&info->origin);\n> -\tstrbuf_addstr(&info->origin, current_config_name());\n> -\tinfo->linenr = current_config_line();\n> +\tstrbuf_addstr(&info->origin, config_origin_type_name(kvi->origin_type));\n> +\tinfo->linenr = kvi->linenr;\n\nYay!\n\nIt is very pleasing to see these current_*() functions made\nunnecessary.  This step only allows us to remove the _line() but not\nyet _scope(), but as long as we lose them at the end, going one step\nat a time is perfectly fine and readable.\n\n> +const char *current_config_origin_type(void)\n> +{\n> +\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n> +\n> +\tif (reader_origin_type(&the_reader, &type))\n> +\t\tBUG(\"current_config_origin_type called outside config callback\");\n> +\n> +\treturn config_origin_type_name(type);\n> +}\n\nWe still rely on the fact that the_reader is a global singleton, but\nthat is of course OK in this early part of the series.\n\nLooking nice.\n"},{"id":"478601","messageId":"xmqq4jn1lsuv.fsf@gitster.g","threadId":"59629","inReplyTo":"a2a891a069f46d3d77cafe61f64402c93cffaae4.1687290233.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 06/12] builtin/config.c: test misuse of format_config()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-20T21:35:36Z","receivedAt":"2023-06-20T21:35:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Glen Choo <chooglen@google.com>\n>\n> current_config_*() functions aren't meant to be called outside of\n> config callbacks because they read state that is only set when iterating\n> through config.\n\nInteresting.  And config_context is about expressing the state of\nthe iteration, so presumably some of these callers that do not have\nconfig_context to pass (because they happen outside iterations) will\nnow (can|have to) pass NULL or something to say \"I am asking format\nbut from outside any iteration\" or something?\n\n> +test_expect_success 'urlmatch with --show-scope' '\n> +\tcat >.git/config <<-\\EOF &&\n> +\t[http \"https://weak.example.com\"]\n> +\t\tsslVerify = false\n> +\t\tcookieFile = /tmp/cookie.txt\n> +\tEOF\n> +\n> +\tcat >expect <<-EOF &&\n> +\tunknown\thttp.cookiefile /tmp/cookie.txt\n> +\tunknown\thttp.sslverify false\n> +\tEOF\n> +\tgit config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_expect_success 'urlmatch favors more specific URLs' '\n>  \tcat >.git/config <<-\\EOF &&\n>  \t[http \"https://example.com/\"]\n> @@ -2055,6 +2070,10 @@ test_expect_success '--show-origin blob ref' '\n>  \ttest_cmp expect output\n>  '\n>  \n> +test_expect_success '--show-origin with --default' '\n> +\ttest_must_fail git config --show-origin --default foo some.key\n> +'\n> +\n>  test_expect_success '--show-scope with --list' '\n>  \tcat >expect <<-EOF &&\n>  \tglobal\tuser.global=true\n> @@ -2123,6 +2142,12 @@ test_expect_success '--show-scope with --show-origin' '\n>  \ttest_cmp expect output\n>  '\n>  \n> +test_expect_success '--show-scope with --default' '\n> +\tgit config --show-scope --default foo some.key >actual &&\n> +\techo \"unknown\tfoo\" >expect &&\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_expect_success 'override global and system config' '\n>  \ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n>  \tcat >\"$HOME\"/.gitconfig <<-EOF &&\n"},{"id":"478603","messageId":"xmqqwmzxkdrp.fsf@gitster.g","threadId":"59629","inReplyTo":"484d553cc7da360b134e7cfcc02e587073b1f9fa.1687290233.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 12/12] config: pass source to config_parser_event_fn_t","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-20T21:46:50Z","receivedAt":"2023-06-20T21:46:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Glen Choo <chooglen@google.com>\n>\n> ..so that the callback can use a \"struct config_source\" parameter\n> instead of \"config_reader.source\". \"struct config_source\" is internal to\n> config.c, so we are adding a pointer to a struct defined in config.c\n> into a public function signature defined in config.h, but this is okay\n> because this function has only ever been (and probably ever will be)\n> used internally by config.c.\n>\n> As a result, the_reader isn't used anywhere, so \"struct config_reader\"\n> is obsolete (it was only intended to be used with the_reader). Remove\n> them.\n>\n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n>  config.c | 77 ++++++++++----------------------------------------------\n>  config.h |  6 +++++\n>  2 files changed, 19 insertions(+), 64 deletions(-)\n\nTogether with the removal of current_* functions in earlier steps,\ngetting rid of the_reader interface makes the config API more easily\nreused in other contexts, I guess.  Nicely done.\n"},{"id":"478606","messageId":"kl6l1qi5spgz.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59629","inReplyTo":"xmqq4jn1lsuv.fsf@gitster.g","subject":"Re: [PATCH v3 06/12] builtin/config.c: test misuse of format_config()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-20T23:06:52Z","receivedAt":"2023-06-20T23:06:59Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> current_config_*() functions aren't meant to be called outside of\n>> config callbacks because they read state that is only set when iterating\n>> through config.\n>\n> Interesting.  And config_context is about expressing the state of\n> the iteration, so presumably some of these callers that do not have\n> config_context to pass (because they happen outside iterations) will\n> now (can|have to) pass NULL or something to say \"I am asking format\n> but from outside any iteration\" or something?\n\nYup, they now have to pass a default value for config_context. For .kvi,\nit should be what's given by KVI_INIT (the next patch has an example).\n"},{"id":"478667","messageId":"xmqqr0q4fpzb.fsf@gitster.g","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 00/12] config: remove global state from config iteration","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-21T21:46:32Z","receivedAt":"2023-06-21T21:46:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Junio: I rebased this onto a newer \"master\". I assume this is a noop for\n> you - I noticed that the RFC versions weren't applied anyway (good).\n\nIt would be a noop only if I do not queue this iteration.\n\nI've reviewed them in its current shape, but it seems to cause too\nmany conflicts even when merged to 'next', let alone 'seen', with\ninteractions with topics in flight:\n\n * ds/add-i-color-configuration-fix (easy)\n * ps/fetch-cleanups (easy but messy)\n * vd/worktree-config-is-per-repository (moderately messy)\n\nsome of which may have graduated to 'master' in the meantime, so it\nmight not be a bad idea to rebase on a more recent 'master' after\nyou collect and adjust for review comments on v3.\n\nThanks.\n\n"},{"id":"478671","messageId":"kl6lh6r0qut8.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59629","inReplyTo":"xmqqr0q4fpzb.fsf@gitster.g","subject":"Re: [PATCH v3 00/12] config: remove global state from config iteration","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-21T23:06:43Z","receivedAt":"2023-06-21T23:07:02Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> I've reviewed them in its current shape, but it seems to cause too\n> many conflicts even when merged to 'next', let alone 'seen', with\n> interactions with topics in flight:\n>\n>  * ds/add-i-color-configuration-fix (easy)\n>  * ps/fetch-cleanups (easy but messy)\n>  * vd/worktree-config-is-per-repository (moderately messy)\n\nAh, sorry. I ran some trial merges against these before I sent out v3,\nbut I forgot as I sent this out. Not queueing this version sounds fine.\n\n> some of which may have graduated to 'master' in the meantime, so it\n> might not be a bad idea to rebase on a more recent 'master' after\n> you collect and adjust for review comments on v3.\n\nSounds good. I suppose it would also be worthwhile to base it on\nconflicting topics queued for 'next'.\n"},{"id":"478742","messageId":"20230623203219.3255267-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"a2a891a069f46d3d77cafe61f64402c93cffaae4.1687290233.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 06/12] builtin/config.c: test misuse of format_config()","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-23T20:32:19Z","receivedAt":"2023-06-23T20:32:37Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> +test_expect_success '--show-origin with --default' '\n> +\ttest_must_fail git config --show-origin --default foo some.key\n> +'\n\nOn my machine, this fails with\n\n  BUG: config.c:4035: current_config_origin_type called outside config callback\n  /usr/local/google/home/jonathantanmy/git/t/test-lib-functions.sh: line 1067: 3255109 Aborted                 \"$@\" 2>&7\n  test_must_fail: died by signal 6: git config --show-origin --default foo some.key\n\n(So it indeed fails, as expected, but test_must_fail seems to not like\nthe exit code.)\n\nNot sure if we need to be more precise with the exit code we're testing\nfor.\n"},{"id":"478743","messageId":"20230623203544.3255750-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"1fb1708bbd967ed841fcb3d96ea0d1f6b880c60e.1687290233.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 07/12] config.c: pass ctx with CLI config","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-23T20:35:44Z","receivedAt":"2023-06-23T20:36:29Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> @@ -2465,16 +2488,10 @@ static int configset_add_value(struct config_reader *reader,\n>  \tl_item->e = e;\n>  \tl_item->value_index = e->value_list.nr - 1;\n>  \n> -\tif (!reader->source)\n> -\t\tBUG(\"configset_add_value has no source\");\n>  \tif (reader->source->name) {\n> -\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n> +\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n>  \t} else {\n> -\t\t/* for values read from `git_config_from_parameters()` */\n> -\t\tkv_info->filename = NULL;\n> -\t\tkv_info->linenr = -1;\n> -\t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n> -\t\tkv_info->scope = reader->parsing_scope;\n> +\t\tkvi_from_param(kv_info);\n>  \t}\n>  \tsi->util = kv_info;\n>  \n\nAny reason to remove the \"if (!reader->source)\" guard? I don't think\nthis patch does anything to ensure that reader is present. We can\nprobably remove this once \"reader\" is removed.\n\nThe rest of the patch looks fine (and all patches prior look fine too.\n \n"},{"id":"478744","messageId":"20230623204002.3256359-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"66572df7beb220b8f7d5ce2eb724fda55c128b94.1687290233.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 08/12] trace2: plumb config kvi","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-23T20:40:02Z","receivedAt":"2023-06-23T20:41:17Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> @@ -125,19 +115,9 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\n>  static inline void config_reader_set_kvi(struct config_reader *reader,\n>  \t\t\t\t\t struct key_value_info *kvi)\n>  {\n> -\tif (kvi && (reader->source || reader->parsing_scope))\n> -\t\tBUG(\"kvi should not be set while parsing a config source\");\n>  \treader->config_kvi = kvi;\n>  }\n>  \n\nWe're removing this check now because reader->parsing_scope is\nremoved in this patch. Makes sense (no reason to just remove the\nreader->parsing_scope first and then remove the whole check later).\n\nRest of the patch looks good too.\n \n"},{"id":"478745","messageId":"20230623205729.3258822-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"8ec24b018e9a8a0767b1811aeb604eb97647bb4e.1687290233.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 10/12] config.c: remove config_reader from configsets","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-23T20:57:29Z","receivedAt":"2023-06-23T20:57:35Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> @@ -2429,11 +2427,7 @@ static int configset_add_value(const struct key_value_info *kvi_p,\n>  \tl_item->e = e;\n>  \tl_item->value_index = e->value_list.nr - 1;\n>  \n> -\tif (reader->source->name) {\n> -\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n> -\t} else {\n> -\t\tkvi_from_param(kv_info);\n> -\t}\n> +\tmemcpy(kv_info, kvi_p, sizeof(struct key_value_info));\n>  \tsi->util = kv_info;\n>  \n>  \treturn 0;\n\nAh, I remember seeing this memcpy from the previous round, but forgot to\ncomment on it (I only commented on another instance, [1]).\n\nOther than that, up to here looks good.\n\n[1] https://lore.kernel.org/git/20230601233550.429921-1-jonathantanmy@google.com/\n \n"},{"id":"478747","messageId":"20230623210217.3259337-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"kl6lh6r0qut8.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v3 00/12] config: remove global state from config iteration","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-23T21:02:17Z","receivedAt":"2023-06-23T21:02:23Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > I've reviewed them in its current shape, but it seems to cause too\n> > many conflicts even when merged to 'next', let alone 'seen', with\n> > interactions with topics in flight:\n> >\n> >  * ds/add-i-color-configuration-fix (easy)\n> >  * ps/fetch-cleanups (easy but messy)\n> >  * vd/worktree-config-is-per-repository (moderately messy)\n> \n> Ah, sorry. I ran some trial merges against these before I sent out v3,\n> but I forgot as I sent this out. Not queueing this version sounds fine.\n> \n> > some of which may have graduated to 'master' in the meantime, so it\n> > might not be a bad idea to rebase on a more recent 'master' after\n> > you collect and adjust for review comments on v3.\n> \n> Sounds good. I suppose it would also be worthwhile to base it on\n> conflicting topics queued for 'next'.\n\nGlen, if you can, rebase the patches *before* updating them based on my\ncomments so that it's easier for reviewers to see if my comments have\nbeen addressed. (My comments are minor, so they should still be relevant\neven after rebasing.)\n\nOverall the patch set looks good aside from some minor comments that\nI've given as replies to the patches.\n"},{"id":"478749","messageId":"xmqqjzvtampe.fsf@gitster.g","threadId":"59629","inReplyTo":"20230623205729.3258822-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 10/12] config.c: remove config_reader from configsets","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-23T21:33:01Z","receivedAt":"2023-06-23T21:33:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> \"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> @@ -2429,11 +2427,7 @@ static int configset_add_value(const struct key_value_info *kvi_p,\n>>  \tl_item->e = e;\n>>  \tl_item->value_index = e->value_list.nr - 1;\n>>  \n>> -\tif (reader->source->name) {\n>> -\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n>> -\t} else {\n>> -\t\tkvi_from_param(kv_info);\n>> -\t}\n>> +\tmemcpy(kv_info, kvi_p, sizeof(struct key_value_info));\n>>  \tsi->util = kv_info;\n>>  \n>>  \treturn 0;\n>\n> Ah, I remember seeing this memcpy from the previous round, but forgot to\n> comment on it (I only commented on another instance, [1]).\n\nYeah, I noticed this and recalled seeing a comment on structure\nassignment.  As both are pointers,\n\n    *kv_info = *kvi_p;\n\nwould sufficiently stand out to notify us that we are doing a\nnot-trivial assignment here, and would avoid (slight) risks of typos\nby type checking.\n\n> Other than that, up to here looks good.\n>\n> [1] https://lore.kernel.org/git/20230601233550.429921-1-jonathantanmy@google.com/\n>  \n"},{"id":"478750","messageId":"xmqqfs6hamoe.fsf@gitster.g","threadId":"59629","inReplyTo":"20230623210217.3259337-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 00/12] config: remove global state from config iteration","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-23T21:33:37Z","receivedAt":"2023-06-23T21:33:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Glen Choo <chooglen@google.com> writes:\n>> Junio C Hamano <gitster@pobox.com> writes:\n>> \n>> > I've reviewed them in its current shape, but it seems to cause too\n>> > many conflicts even when merged to 'next', let alone 'seen', with\n>> > interactions with topics in flight:\n>> >\n>> >  * ds/add-i-color-configuration-fix (easy)\n>> >  * ps/fetch-cleanups (easy but messy)\n>> >  * vd/worktree-config-is-per-repository (moderately messy)\n>> \n>> Ah, sorry. I ran some trial merges against these before I sent out v3,\n>> but I forgot as I sent this out. Not queueing this version sounds fine.\n>> \n>> > some of which may have graduated to 'master' in the meantime, so it\n>> > might not be a bad idea to rebase on a more recent 'master' after\n>> > you collect and adjust for review comments on v3.\n>> \n>> Sounds good. I suppose it would also be worthwhile to base it on\n>> conflicting topics queued for 'next'.\n>\n> Glen, if you can, rebase the patches *before* updating them based on my\n> comments so that it's easier for reviewers to see if my comments have\n> been addressed. (My comments are minor, so they should still be relevant\n> even after rebasing.)\n>\n> Overall the patch set looks good aside from some minor comments that\n> I've given as replies to the patches.\n\nThanks for a quick review (and thank you, Glen, for working on it).\n\n"},{"id":"478751","messageId":"kl6ledm1svoi.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59629","inReplyTo":"20230623203544.3255750-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 07/12] config.c: pass ctx with CLI config","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-23T21:41:49Z","receivedAt":"2023-06-23T21:41:55Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> \"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> @@ -2465,16 +2488,10 @@ static int configset_add_value(struct config_reader *reader,\n>>  \tl_item->e = e;\n>>  \tl_item->value_index = e->value_list.nr - 1;\n>>  \n>> -\tif (!reader->source)\n>> -\t\tBUG(\"configset_add_value has no source\");\n>>  \tif (reader->source->name) {\n>> -\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n>> +\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n>>  \t} else {\n>> -\t\t/* for values read from `git_config_from_parameters()` */\n>> -\t\tkv_info->filename = NULL;\n>> -\t\tkv_info->linenr = -1;\n>> -\t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n>> -\t\tkv_info->scope = reader->parsing_scope;\n>> +\t\tkvi_from_param(kv_info);\n>>  \t}\n>>  \tsi->util = kv_info;\n>>  \n>\n> Any reason to remove the \"if (!reader->source)\" guard? I don't think\n> this patch does anything to ensure that reader is present. We can\n> probably remove this once \"reader\" is removed.\n\nHm you're right, this shouldn't be removed here. I think this a leftover\nfrom an ancient version where I dropped references to \"reader\".\n"},{"id":"478753","messageId":"kl6lbkh5svi3.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59629","inReplyTo":"20230623210217.3259337-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 00/12] config: remove global state from config iteration","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-23T21:45:40Z","receivedAt":"2023-06-23T21:45:45Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n>> Sounds good. I suppose it would also be worthwhile to base it on\n>> conflicting topics queued for 'next'.\n>\n> Glen, if you can, rebase the patches *before* updating them based on my\n> comments so that it's easier for reviewers to see if my comments have\n> been addressed. (My comments are minor, so they should still be relevant\n> even after rebasing.)\n\nGood idea. Fortunately, I already have that prepared, so there's not\nmuch extra work for me.\n"},{"id":"478762","messageId":"20230624013104.GE95358@coredump.intra.peff.net","threadId":"59629","inReplyTo":"20230623203219.3255267-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 06/12] builtin/config.c: test misuse of format_config()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2023-06-24T01:31:04Z","receivedAt":"2023-06-24T01:31:10Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Jun 23, 2023 at 01:32:19PM -0700, Jonathan Tan wrote:\n\n> \"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> > +test_expect_success '--show-origin with --default' '\n> > +\ttest_must_fail git config --show-origin --default foo some.key\n> > +'\n> \n> On my machine, this fails with\n> \n>   BUG: config.c:4035: current_config_origin_type called outside config callback\n>   /usr/local/google/home/jonathantanmy/git/t/test-lib-functions.sh: line 1067: 3255109 Aborted                 \"$@\" 2>&7\n>   test_must_fail: died by signal 6: git config --show-origin --default foo some.key\n> \n> (So it indeed fails, as expected, but test_must_fail seems to not like\n> the exit code.)\n> \n> Not sure if we need to be more precise with the exit code we're testing\n> for.\n\nThat is test_must_fail operating as intended. We should _never_ hit a\nBUG() call, no matter what garbage we get from the user or the disk. The\nresolution is generally one of:\n\n  - there really is a bug, so we should fix it\n\n  - the invocation is garbage and expected to fail, but we should catch\n    it sooner and give a nice error message, rather than getting as far\n    as a BUG() call\n\nBut I didn't look at the patches to know which case this is.\n\n-Peff\n"},{"id":"478834","messageId":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v3.git.git.1687290231.gitgitgadget@gmail.com","subject":"[PATCH v4 00/12] config: remove global state from config iteration","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T18:11:11Z","receivedAt":"2023-06-26T18:11:35Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Thanks for the careful reread of the previous round and the helpful suggestions.\nThis version is _mostly_ just the previous one rebased onto a newer 'master'\nwith the conflicting topics applied, per Jonathan's and Junio's suggestions, and\nI plan to address Jonathan's comments in the next version.\n\nJunio: There is now a minor conflict with the bugfix I sent in [1] (the\nresolution is to just pick this series). It seemed small enough that I didn't\nthink it was worth basing this series on top of that, but let me know if you\nfeel otherwise. If both series happen to go through the same release cycle, we\ncould also drop that one-patch.\n\nI also tested these patches on top of 'seen', and the only conflict I found was\nwith en/header-split-cache-h-part-3, where ll-merge.c got renamed to merge-ll.c,\nso we need to apply the config refactor there (should work with .cocci).\n\n= Changes since v3\n\n- Rebase onto newer 'master'\n- Move the 'remove UNUSED from tr2_cfg_cb' hunk from 9/12 -> 8/12. It should\n  have been there all along; v3 8/12 didn't build at all.\n\n[1] https://lore.kernel.org/git/pull.1535.git.git.1687801297404.gitgitgadget@gmail.com/\n\n\nGlen Choo (12):\n  config: inline git_color_default_config\n  urlmatch.h: use config_fn_t type\n  config: add ctx arg to config_fn_t\n  config.c: pass ctx in configsets\n  config: pass ctx with config files\n  builtin/config.c: test misuse of format_config()\n  config.c: pass ctx with CLI config\n  trace2: plumb config kvi\n  config: pass kvi to die_bad_number()\n  config.c: remove config_reader from configsets\n  config: add kvi.path, use it to evaluate includes\n  config: pass source to config_parser_event_fn_t\n\n alias.c                                       |   3 +-\n archive-tar.c                                 |   5 +-\n archive-zip.c                                 |   1 +\n builtin/add.c                                 |   8 +-\n builtin/blame.c                               |   5 +-\n builtin/branch.c                              |   8 +-\n builtin/cat-file.c                            |   5 +-\n builtin/checkout.c                            |  12 +-\n builtin/clean.c                               |   9 +-\n builtin/clone.c                               |  11 +-\n builtin/column.c                              |   3 +-\n builtin/commit-graph.c                        |   3 +-\n builtin/commit.c                              |  20 +-\n builtin/config.c                              |  72 ++-\n builtin/difftool.c                            |   5 +-\n builtin/fetch.c                               |  13 +-\n builtin/fsmonitor--daemon.c                   |  11 +-\n builtin/grep.c                                |  12 +-\n builtin/help.c                                |   5 +-\n builtin/index-pack.c                          |   9 +-\n builtin/log.c                                 |  12 +-\n builtin/merge.c                               |   7 +-\n builtin/multi-pack-index.c                    |   1 +\n builtin/pack-objects.c                        |  19 +-\n builtin/patch-id.c                            |   5 +-\n builtin/pull.c                                |   5 +-\n builtin/push.c                                |   5 +-\n builtin/read-tree.c                           |   5 +-\n builtin/rebase.c                              |   5 +-\n builtin/receive-pack.c                        |  15 +-\n builtin/reflog.c                              |   7 +-\n builtin/remote.c                              |  15 +-\n builtin/repack.c                              |   5 +-\n builtin/reset.c                               |   5 +-\n builtin/send-pack.c                           |   5 +-\n builtin/show-branch.c                         |   8 +-\n builtin/stash.c                               |   5 +-\n builtin/submodule--helper.c                   |   3 +-\n builtin/tag.c                                 |   9 +-\n builtin/var.c                                 |   5 +-\n builtin/worktree.c                            |   5 +-\n bundle-uri.c                                  |   9 +-\n color.c                                       |   8 -\n color.h                                       |   6 +-\n compat/mingw.c                                |   3 +-\n compat/mingw.h                                |   4 +-\n config.c                                      | 552 +++++++-----------\n config.h                                      |  80 ++-\n connect.c                                     |   4 +-\n .../coccinelle/config_fn_ctx.pending.cocci    | 144 +++++\n contrib/coccinelle/git_config_number.cocci    |  27 +\n convert.c                                     |   4 +-\n credential.c                                  |   1 +\n delta-islands.c                               |   4 +-\n diff.c                                        |  19 +-\n diff.h                                        |   7 +-\n fetch-pack.c                                  |   5 +-\n fmt-merge-msg.c                               |   7 +-\n fmt-merge-msg.h                               |   3 +-\n fsck.c                                        |  12 +-\n fsck.h                                        |   4 +-\n git-compat-util.h                             |   2 +\n gpg-interface.c                               |   7 +-\n grep.c                                        |   7 +-\n grep.h                                        |   4 +-\n help.c                                        |   9 +-\n http.c                                        |  15 +-\n ident.c                                       |   4 +-\n ident.h                                       |   4 +-\n imap-send.c                                   |   7 +-\n ll-merge.c                                    |   1 +\n ls-refs.c                                     |   1 +\n mailinfo.c                                    |   5 +-\n notes-utils.c                                 |   4 +-\n notes.c                                       |   4 +-\n pager.c                                       |   5 +-\n pretty.c                                      |   1 +\n promisor-remote.c                             |   4 +-\n remote.c                                      |   8 +-\n revision.c                                    |   4 +-\n scalar.c                                      |   4 +-\n sequencer.c                                   |  29 +-\n setup.c                                       |  18 +-\n submodule-config.c                            |  31 +-\n submodule-config.h                            |   3 +-\n t/helper/test-config.c                        |  24 +-\n t/helper/test-userdiff.c                      |   4 +-\n t/t1300-config.sh                             |  27 +\n trace2.c                                      |   4 +-\n trace2.h                                      |   3 +-\n trace2/tr2_cfg.c                              |  16 +-\n trace2/tr2_sysenv.c                           |   3 +-\n trace2/tr2_tgt.h                              |   4 +-\n trace2/tr2_tgt_event.c                        |   4 +-\n trace2/tr2_tgt_normal.c                       |   4 +-\n trace2/tr2_tgt_perf.c                         |   4 +-\n trailer.c                                     |   2 +\n upload-pack.c                                 |  18 +-\n urlmatch.c                                    |   7 +-\n urlmatch.h                                    |   8 +-\n worktree.c                                    |   2 +-\n xdiff-interface.c                             |   5 +-\n xdiff-interface.h                             |   4 +-\n 103 files changed, 960 insertions(+), 638 deletions(-)\n create mode 100644 contrib/coccinelle/config_fn_ctx.pending.cocci\n create mode 100644 contrib/coccinelle/git_config_number.cocci\n\n\nbase-commit: 6ff334181cfb6485d3ba50843038209a2a253907\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1497%2Fchooglen%2Fconfig%2Fno-global-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1497/chooglen/config/no-global-v4\nPull-Request: https://github.com/git/git/pull/1497\n\nRange-diff vs v3:\n\n  1:  26109b65142 !  1:  7bfffb454c5 config: inline git_color_default_config\n     @@ Commit message\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n     + ## builtin/add.c ##\n     +@@ builtin/add.c: static int add_config(const char *var, const char *value, void *cb)\n     + \t\treturn 0;\n     + \t}\n     + \n     +-\treturn git_color_default_config(var, value, cb);\n     ++\tif (git_color_config(var, value, cb) < 0)\n     ++\t\treturn -1;\n     ++\n     ++\treturn git_default_config(var, value, cb);\n     + }\n     + \n     + static const char embedded_advice[] = N_(\n     +\n       ## builtin/branch.c ##\n      @@ builtin/branch.c: static int git_branch_config(const char *var, const char *value, void *cb)\n       \t\treturn 0;\n  2:  1aeffcb1395 =  2:  739c519ce62 urlmatch.h: use config_fn_t type\n  3:  d3eb439aad2 !  3:  a9a0a50f32a config: add ctx arg to config_fn_t\n     @@ builtin/add.c: static struct option builtin_add_options[] = {\n       \tif (!strcmp(var, \"add.ignoreerrors\") ||\n       \t    !strcmp(var, \"add.ignore-errors\")) {\n      @@ builtin/add.c: static int add_config(const char *var, const char *value, void *cb)\n     - \t\treturn 0;\n     - \t}\n     + \tif (git_color_config(var, value, cb) < 0)\n     + \t\treturn -1;\n       \n      -\treturn git_default_config(var, value, cb);\n      +\treturn git_default_config(var, value, ctx, cb);\n     @@ builtin/difftool.c: static const char *const builtin_difftool_usage[] = {\n      \n       ## builtin/fetch.c ##\n      @@ builtin/fetch.c: struct fetch_config {\n     - \tenum display_format display_format;\n     + \tint submodule_fetch_jobs;\n       };\n       \n      -static int git_fetch_config(const char *k, const char *v, void *cb)\n  4:  c5051ddc10d =  4:  39b2e291f86 config.c: pass ctx in configsets\n  5:  595e7d2e163 !  5:  bfc6d2833c5 config: pass ctx with config files\n     @@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n      +\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n       \n       \tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n     - \tif (!opts->ignore_worktree && repository_format_worktree_config) {\n     - \t\tchar *path = git_pathdup(\"config.worktree\");\n     - \t\tif (!access_or_die(path, R_OK, 0))\n     --\t\t\tret += git_config_from_file(fn, path, data);\n     -+\t\t\tret += git_config_from_file_with_options(fn, path, data,\n     + \tif (!opts->ignore_worktree && worktree_config &&\n     + \t    repo && repo->repository_format_worktree_config &&\n     + \t    !access_or_die(worktree_config, R_OK, 0)) {\n     +-\t\tret += git_config_from_file(fn, worktree_config, data);\n     ++\t\t\tret += git_config_from_file_with_options(fn, worktree_config, data,\n      +\t\t\t\t\t\t\t\t CONFIG_SCOPE_WORKTREE,\n      +\t\t\t\t\t\t\t\t NULL);\n     - \t\tfree(path);\n       \t}\n       \n     + \tconfig_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);\n      @@ config.c: int config_with_options(config_fn_t fn, void *data,\n       \t * regular lookup sequence.\n       \t */\n     @@ config.c: int config_with_options(config_fn_t fn, void *data,\n      +\t\t\t\t\t\t\tdata, config_source->scope,\n      +\t\t\t\t\t\t\tNULL);\n       \t} else if (config_source && config_source->blob) {\n     - \t\tstruct repository *repo = config_source->repo ?\n     - \t\t\tconfig_source->repo : the_repository;\n       \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n      -\t\t\t\t\t\tdata);\n      +\t\t\t\t\t       data, config_source->scope);\n       \t} else {\n     - \t\tret = do_git_config_sequence(&the_reader, opts, fn, data);\n     + \t\tret = do_git_config_sequence(&the_reader, opts, repo, fn, data);\n       \t}\n      @@ config.c: static int configset_add_value(struct config_reader *reader,\n       \tif (!reader->source)\n  6:  a2a891a069f =  6:  897bdc759b5 builtin/config.c: test misuse of format_config()\n  7:  1fb1708bbd9 !  7:  33e4437737d config.c: pass ctx with CLI config\n     @@ builtin/config.c: static int collect_config(const char *key_, const char *value_\n       \n       static int get_value(const char *key_, const char *regex_, unsigned flags)\n      @@ builtin/config.c: static int get_value(const char *key_, const char *regex_, unsigned flags)\n     - \t\t\t    &given_config_source, &config_options);\n     + \t\t\t    &config_options);\n       \n       \tif (!values.nr && default_value) {\n      +\t\tstruct key_value_info kvi = KVI_INIT;\n  8:  66572df7beb !  8:  9bd5f60282c trace2: plumb config kvi\n     @@ config.c: static void populate_remote_urls(struct config_include_data *inc)\n      -\n       \tinc->remote_urls = xmalloc(sizeof(*inc->remote_urls));\n       \tstring_list_init_dup(inc->remote_urls);\n     - \tconfig_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);\n     + \tconfig_with_options(add_remote_url, inc->remote_urls,\n     + \t\t\t    inc->config_source, inc->repo, &opts);\n      -\n      -\tconfig_reader_set_scope(inc->config_reader, store_scope);\n       }\n       \n       static int forbid_remote_url(const char *var, const char *value UNUSED,\n      @@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n     - \tchar *xdg_config = NULL;\n       \tchar *user_config = NULL;\n       \tchar *repo_config;\n     + \tchar *worktree_config;\n      -\tenum config_scope prev_parsing_scope = reader->parsing_scope;\n       \n     - \tif (opts->commondir)\n     - \t\trepo_config = mkpathdup(\"%s/config\", opts->commondir);\n     + \t/*\n     + \t * Ensure that either:\n      @@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n     - \telse\n     - \t\trepo_config = NULL;\n     + \t\tworktree_config = NULL;\n     + \t}\n       \n      -\tconfig_reader_set_scope(reader, CONFIG_SCOPE_SYSTEM);\n       \tif (git_config_system() && system_config &&\n     @@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n       \t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n       \n      -\tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n     - \tif (!opts->ignore_worktree && repository_format_worktree_config) {\n     - \t\tchar *path = git_pathdup(\"config.worktree\");\n     - \t\tif (!access_or_die(path, R_OK, 0))\n     + \tif (!opts->ignore_worktree && worktree_config &&\n     + \t    repo && repo->repository_format_worktree_config &&\n     + \t    !access_or_die(worktree_config, R_OK, 0)) {\n      @@ config.c: static int do_git_config_sequence(struct config_reader *reader,\n     - \t\tfree(path);\n     + \t\t\t\t\t\t\t\t NULL);\n       \t}\n       \n      -\tconfig_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);\n     @@ trace2.h: void trace2_thread_exit_fl(const char *file, int line);\n       \ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\n      \n       ## trace2/tr2_cfg.c ##\n     +@@ trace2/tr2_cfg.c: struct tr2_cfg_data {\n     +  * See if the given config key matches any of our patterns of interest.\n     +  */\n     + static int tr2_cfg_cb(const char *key, const char *value,\n     +-\t\t      const struct config_context *ctx UNUSED, void *d)\n     ++\t\t      const struct config_context *ctx, void *d)\n     + {\n     + \tstruct strbuf **s;\n     + \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n      @@ trace2/tr2_cfg.c: static int tr2_cfg_cb(const char *key, const char *value,\n       \t\tstruct strbuf *buf = *s;\n       \t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n  9:  123e19dda4a !  9:  114723ee4a7 config: pass kvi to die_bad_number()\n     @@ builtin/fetch.c: static int git_fetch_config(const char *k, const char *v,\n       \t}\n       \n       \tif (!strcmp(k, \"submodule.fetchjobs\")) {\n     --\t\tsubmodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v);\n     -+\t\tsubmodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v, ctx->kvi);\n     +-\t\tfetch_config->submodule_fetch_jobs = parse_submodule_fetchjobs(k, v);\n     ++\t\tfetch_config->submodule_fetch_jobs = parse_submodule_fetchjobs(k, v, ctx->kvi);\n       \t\treturn 0;\n       \t} else if (!strcmp(k, \"fetch.recursesubmodules\")) {\n     - \t\trecurse_submodules = parse_fetch_recurse_submodules_arg(k, v);\n     + \t\tfetch_config->recurse_submodules = parse_fetch_recurse_submodules_arg(k, v);\n      @@ builtin/fetch.c: static int git_fetch_config(const char *k, const char *v,\n       \t}\n       \n       \tif (!strcmp(k, \"fetch.parallel\")) {\n     --\t\tfetch_parallel_config = git_config_int(k, v);\n     -+\t\tfetch_parallel_config = git_config_int(k, v, ctx->kvi);\n     - \t\tif (fetch_parallel_config < 0)\n     +-\t\tfetch_config->parallel = git_config_int(k, v);\n     ++\t\tfetch_config->parallel = git_config_int(k, v, ctx->kvi);\n     + \t\tif (fetch_config->parallel < 0)\n       \t\t\tdie(_(\"fetch.parallel cannot be negative\"));\n     - \t\tif (!fetch_parallel_config)\n     + \t\tif (!fetch_config->parallel)\n      \n       ## builtin/fsmonitor--daemon.c ##\n      @@ builtin/fsmonitor--daemon.c: static int fsmonitor_config(const char *var, const char *value,\n     @@ t/helper/test-config.c: int cmd__config(int argc, const char **argv)\n       \t\t\t\tprintf(\"(NULL)\\n\");\n       \t\t\telse\n      \n     - ## trace2/tr2_cfg.c ##\n     -@@ trace2/tr2_cfg.c: struct tr2_cfg_data {\n     -  * See if the given config key matches any of our patterns of interest.\n     -  */\n     - static int tr2_cfg_cb(const char *key, const char *value,\n     --\t\t      const struct config_context *ctx UNUSED, void *d)\n     -+\t\t      const struct config_context *ctx, void *d)\n     - {\n     - \tstruct strbuf **s;\n     - \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n     -\n       ## upload-pack.c ##\n      @@ upload-pack.c: static int find_symref(const char *refname,\n       }\n 10:  8ec24b018e9 ! 10:  807057b6d7f config.c: remove config_reader from configsets\n     @@ config.c: static void repo_read_config(struct repository *repo)\n      -\tdata.config_set = repo->config;\n      -\tdata.config_reader = &the_reader;\n      -\n     --\tif (config_with_options(config_set_callback, &data, NULL, &opts) < 0)\n     +-\tif (config_with_options(config_set_callback, &data, NULL, repo, &opts) < 0)\n      +\tif (config_with_options(config_set_callback, repo->config, NULL,\n     -+\t\t\t\t&opts) < 0)\n     ++\t\t\t\trepo, &opts) < 0)\n       \t\t/*\n       \t\t * config_with_options() normally returns only\n       \t\t * zero, as most errors are fatal, and\n     @@ config.c: static void read_protected_config(void)\n       \tgit_configset_init(&protected_config);\n      -\tdata.config_set = &protected_config;\n      -\tdata.config_reader = &the_reader;\n     --\tconfig_with_options(config_set_callback, &data, NULL, &opts);\n     -+\tconfig_with_options(config_set_callback, &protected_config, NULL, &opts);\n     +-\tconfig_with_options(config_set_callback, &data, NULL, NULL, &opts);\n     ++\tconfig_with_options(config_set_callback, &protected_config, NULL,\n     ++\t\t\t    NULL, &opts);\n       }\n       \n       void git_protected_config(config_fn_t fn, void *data)\n 11:  8ae115cff88 ! 11:  3f0f84df972 config: add kvi.path, use it to evaluate includes\n     @@ Commit message\n      \n       ## config.c ##\n      @@ config.c: struct config_include_data {\n     - \tvoid *data;\n       \tconst struct config_options *opts;\n       \tstruct git_config_source *config_source;\n     + \tstruct repository *repo;\n      -\tstruct config_reader *config_reader;\n       \n       \t/*\n     @@ config.c: static void kvi_from_source(struct config_source *cs,\n       \n       static int git_parse_source(struct config_source *cs, config_fn_t fn,\n      @@ config.c: int config_with_options(config_fn_t fn, void *data,\n     - \t\tinc.data = data;\n       \t\tinc.opts = opts;\n     + \t\tinc.repo = repo;\n       \t\tinc.config_source = config_source;\n      -\t\tinc.config_reader = &the_reader;\n       \t\tfn = git_config_include;\n 12:  484d553cc7d ! 12:  fe2f154fe8b config: pass source to config_parser_event_fn_t\n     @@ config.c: int git_config_system(void)\n      -static int do_git_config_sequence(struct config_reader *reader,\n      -\t\t\t\t  const struct config_options *opts,\n      +static int do_git_config_sequence(const struct config_options *opts,\n     + \t\t\t\t  const struct repository *repo,\n       \t\t\t\t  config_fn_t fn, void *data)\n       {\n     - \tint ret = 0;\n      @@ config.c: int config_with_options(config_fn_t fn, void *data,\n       \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n       \t\t\t\t\t       data, config_source->scope);\n       \t} else {\n     --\t\tret = do_git_config_sequence(&the_reader, opts, fn, data);\n     -+\t\tret = do_git_config_sequence(opts, fn, data);\n     +-\t\tret = do_git_config_sequence(&the_reader, opts, repo, fn, data);\n     ++\t\tret = do_git_config_sequence(opts, repo, fn, data);\n       \t}\n       \n       \tif (inc.remote_urls) {\n\n-- \ngitgitgadget\n"},{"id":"478835","messageId":"7bfffb454c5d837077fe08087e43abb194efd1f4.1687803085.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"[PATCH v4 01/12] config: inline git_color_default_config","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T18:11:12Z","receivedAt":"2023-06-26T18:11:39Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\ngit_color_default_config() is a shorthand for calling two other config\ncallbacks. There are no other non-static functions that do this and it\nwill complicate our refactoring of config_fn_t so inline it instead.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/add.c         | 5 ++++-\n builtin/branch.c      | 5 ++++-\n builtin/clean.c       | 6 ++++--\n builtin/grep.c        | 5 ++++-\n builtin/show-branch.c | 5 ++++-\n builtin/tag.c         | 6 +++++-\n color.c               | 8 --------\n color.h               | 6 +-----\n 8 files changed, 26 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/add.c b/builtin/add.c\nindex 6137e7b4ad7..e01efdfc50d 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -365,7 +365,10 @@ static int add_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static const char embedded_advice[] = N_(\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 075e580d224..8337b9e71bb 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -117,7 +117,10 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static const char *branch_get_color(enum color_branch ix)\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 78852d28cec..57e7f7cac64 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -130,8 +130,10 @@ static int git_clean_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\t/* inspect the color.ui config variable and others */\n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static const char *clean_get_color(enum color_clean ix)\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex b86c754defb..76cf999d310 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -293,7 +293,10 @@ static int wait_all(void)\n static int grep_cmd_config(const char *var, const char *value, void *cb)\n {\n \tint st = grep_config(var, value, cb);\n-\tif (git_color_default_config(var, value, NULL) < 0)\n+\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\tst = -1;\n+\telse if (git_default_config(var, value, cb) < 0)\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex 7ef4a642c17..a2461270d4b 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -579,7 +579,10 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static int omit_in_dense(struct commit *commit, struct commit **rev, int n)\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 49b64c7a288..1acf5f7a59f 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -209,7 +209,11 @@ static int git_tag_config(const char *var, const char *value, void *cb)\n \n \tif (starts_with(var, \"column.\"))\n \t\treturn git_column_config(var, value, \"tag\", &colopts);\n-\treturn git_color_default_config(var, value, cb);\n+\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static void write_tag_body(int fd, const struct object_id *oid)\ndiff --git a/color.c b/color.c\nindex 83abb11eda0..b24b19566b9 100644\n--- a/color.c\n+++ b/color.c\n@@ -430,14 +430,6 @@ int git_color_config(const char *var, const char *value, void *cb UNUSED)\n \treturn 0;\n }\n \n-int git_color_default_config(const char *var, const char *value, void *cb)\n-{\n-\tif (git_color_config(var, value, cb) < 0)\n-\t\treturn -1;\n-\n-\treturn git_default_config(var, value, cb);\n-}\n-\n void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb)\n {\n \tif (*color)\ndiff --git a/color.h b/color.h\nindex cfc8f841b23..bb28343be21 100644\n--- a/color.h\n+++ b/color.h\n@@ -88,12 +88,8 @@ extern const int column_colors_ansi_max;\n  */\n extern int color_stdout_is_tty;\n \n-/*\n- * Use the first one if you need only color config; the second is a convenience\n- * if you are just going to change to git_default_config, too.\n- */\n+/* Parse color config. */\n int git_color_config(const char *var, const char *value, void *cb);\n-int git_color_default_config(const char *var, const char *value, void *cb);\n \n /*\n  * Parse a config option, which can be a boolean or one of\n-- \ngitgitgadget\n\n"},{"id":"478836","messageId":"739c519ce625caa0fe5d0758b707ec2828c37ac3.1687803085.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"[PATCH v4 02/12] urlmatch.h: use config_fn_t type","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T18:11:13Z","receivedAt":"2023-06-26T18:11:41Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThese are actually used as config callbacks, so use the typedef-ed type\nand make future refactors easier.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n urlmatch.h | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/urlmatch.h b/urlmatch.h\nindex 9f40b00bfb8..bee374a642c 100644\n--- a/urlmatch.h\n+++ b/urlmatch.h\n@@ -2,6 +2,7 @@\n #define URL_MATCH_H\n \n #include \"string-list.h\"\n+#include \"config.h\"\n \n struct url_info {\n \t/* normalized url on success, must be freed, otherwise NULL */\n@@ -48,8 +49,8 @@ struct urlmatch_config {\n \tconst char *key;\n \n \tvoid *cb;\n-\tint (*collect_fn)(const char *var, const char *value, void *cb);\n-\tint (*cascade_fn)(const char *var, const char *value, void *cb);\n+\tconfig_fn_t collect_fn;\n+\tconfig_fn_t cascade_fn;\n \t/*\n \t * Compare the two matches, the one just discovered and the existing\n \t * best match and return a negative value if the found item is to be\n-- \ngitgitgadget\n\n"},{"id":"478837","messageId":"39b2e291f8669e1d77725619d6b3c2b34b2acecc.1687803085.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"[PATCH v4 04/12] config.c: pass ctx in configsets","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T18:11:15Z","receivedAt":"2023-06-26T18:11:43Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPass config_context to config callbacks in configset_iter(), trivially\nsetting the .kvi member to the cached key_value_info. Then, in config\ncallbacks that are only used with configsets, use the .kvi member to\nreplace calls to current_config_*(), and delete current_config_line()\nbecause it has no remaining callers.\n\nThis leaves builtin/config.c and config.c as the only remaining users of\ncurrent_config_*().\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/remote.c       | 10 ++++++----\n config.c               | 35 ++++++++++++++++-------------------\n config.h               |  2 +-\n remote.c               |  7 ++++---\n t/helper/test-config.c | 11 ++++++-----\n 5 files changed, 33 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 87de81105e2..d47f9ee21cf 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -646,17 +646,19 @@ struct push_default_info\n };\n \n static int config_read_push_default(const char *key, const char *value,\n-\tconst struct config_context *ctx UNUSED, void *cb)\n+\tconst struct config_context *ctx, void *cb)\n {\n+\tconst struct key_value_info *kvi = ctx->kvi;\n+\n \tstruct push_default_info* info = cb;\n \tif (strcmp(key, \"remote.pushdefault\") ||\n \t    !value || strcmp(value, info->old_name))\n \t\treturn 0;\n \n-\tinfo->scope = current_config_scope();\n+\tinfo->scope = kvi->scope;\n \tstrbuf_reset(&info->origin);\n-\tstrbuf_addstr(&info->origin, current_config_name());\n-\tinfo->linenr = current_config_line();\n+\tstrbuf_addstr(&info->origin, config_origin_type_name(kvi->origin_type));\n+\tinfo->linenr = kvi->linenr;\n \n \treturn 0;\n }\ndiff --git a/config.c b/config.c\nindex 850e432e301..662d406ac1e 100644\n--- a/config.c\n+++ b/config.c\n@@ -2317,6 +2317,7 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \tstruct string_list *values;\n \tstruct config_set_element *entry;\n \tstruct configset_list *list = &set->list;\n+\tstruct config_context ctx = CONFIG_CONTEXT_INIT;\n \n \tfor (i = 0; i < list->nr; i++) {\n \t\tentry = list->items[i].e;\n@@ -2324,12 +2325,11 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \t\tvalues = &entry->value_list;\n \n \t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n-\n-\t\tif (fn(entry->key, values->items[value_index].string, NULL, data) < 0)\n+\t\tctx.kvi = values->items[value_index].util;\n+\t\tif (fn(entry->key, values->items[value_index].string, &ctx, data) < 0)\n \t\t\tgit_die_config_linenr(entry->key,\n-\t\t\t\t\t      reader->config_kvi->filename,\n-\t\t\t\t\t      reader->config_kvi->linenr);\n-\n+\t\t\t\t\t      ctx.kvi->filename,\n+\t\t\t\t\t      ctx.kvi->linenr);\n \t\tconfig_reader_set_kvi(reader, NULL);\n \t}\n }\n@@ -3984,13 +3984,8 @@ static int reader_origin_type(struct config_reader *reader,\n \treturn 0;\n }\n \n-const char *current_config_origin_type(void)\n+const char *config_origin_type_name(enum config_origin_type type)\n {\n-\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n-\n-\tif (reader_origin_type(&the_reader, &type))\n-\t\tBUG(\"current_config_origin_type called outside config callback\");\n-\n \tswitch (type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\treturn \"blob\";\n@@ -4007,6 +4002,16 @@ const char *current_config_origin_type(void)\n \t}\n }\n \n+const char *current_config_origin_type(void)\n+{\n+\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n+\n+\tif (reader_origin_type(&the_reader, &type))\n+\t\tBUG(\"current_config_origin_type called outside config callback\");\n+\n+\treturn config_origin_type_name(type);\n+}\n+\n const char *config_scope_name(enum config_scope scope)\n {\n \tswitch (scope) {\n@@ -4054,14 +4059,6 @@ enum config_scope current_config_scope(void)\n \t\treturn the_reader.parsing_scope;\n }\n \n-int current_config_line(void)\n-{\n-\tif (the_reader.config_kvi)\n-\t\treturn the_reader.config_kvi->linenr;\n-\telse\n-\t\treturn the_reader.source->linenr;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex cd30125a8a4..ddf147bb2d1 100644\n--- a/config.h\n+++ b/config.h\n@@ -387,7 +387,7 @@ int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n enum config_scope current_config_scope(void);\n const char *current_config_origin_type(void);\n const char *current_config_name(void);\n-int current_config_line(void);\n+const char *config_origin_type_name(enum config_origin_type type);\n \n /*\n  * Match and parse a config key of the form:\ndiff --git a/remote.c b/remote.c\nindex 241999c2842..1dab860141b 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -350,7 +350,7 @@ static void read_branches_file(struct remote_state *remote_state,\n }\n \n static int handle_config(const char *key, const char *value,\n-\t\t\t const struct config_context *ctx UNUSED, void *cb)\n+\t\t\t const struct config_context *ctx, void *cb)\n {\n \tconst char *name;\n \tsize_t namelen;\n@@ -358,6 +358,7 @@ static int handle_config(const char *key, const char *value,\n \tstruct remote *remote;\n \tstruct branch *branch;\n \tstruct remote_state *remote_state = cb;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \n \tif (parse_config_key(key, \"branch\", &name, &namelen, &subkey) >= 0) {\n \t\t/* There is no subsection. */\n@@ -415,8 +416,8 @@ static int handle_config(const char *key, const char *value,\n \t}\n \tremote = make_remote(remote_state, name, namelen);\n \tremote->origin = REMOTE_CONFIG;\n-\tif (current_config_scope() == CONFIG_SCOPE_LOCAL ||\n-\t    current_config_scope() == CONFIG_SCOPE_WORKTREE)\n+\tif (kvi->scope == CONFIG_SCOPE_LOCAL ||\n+\t    kvi->scope == CONFIG_SCOPE_WORKTREE)\n \t\tremote->configured_in_repo = 1;\n \tif (!strcmp(subkey, \"mirror\"))\n \t\tremote->mirror = git_config_bool(key, value);\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 85ad815358e..3f4c3678318 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -43,9 +43,10 @@\n  */\n \n static int iterate_cb(const char *var, const char *value,\n-\t\t      const struct config_context *ctx UNUSED,\n+\t\t      const struct config_context *ctx,\n \t\t      void *data UNUSED)\n {\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \tstatic int nr;\n \n \tif (nr++)\n@@ -53,10 +54,10 @@ static int iterate_cb(const char *var, const char *value,\n \n \tprintf(\"key=%s\\n\", var);\n \tprintf(\"value=%s\\n\", value ? value : \"(null)\");\n-\tprintf(\"origin=%s\\n\", current_config_origin_type());\n-\tprintf(\"name=%s\\n\", current_config_name());\n-\tprintf(\"lno=%d\\n\", current_config_line());\n-\tprintf(\"scope=%s\\n\", config_scope_name(current_config_scope()));\n+\tprintf(\"origin=%s\\n\", config_origin_type_name(kvi->origin_type));\n+\tprintf(\"name=%s\\n\", kvi->filename ? kvi->filename : \"\");\n+\tprintf(\"lno=%d\\n\", kvi->linenr);\n+\tprintf(\"scope=%s\\n\", config_scope_name(kvi->scope));\n \n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"478838","messageId":"897bdc759b5a59ff65cf2d555f6007671be22f64.1687803085.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"[PATCH v4 06/12] builtin/config.c: test misuse of format_config()","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T18:11:17Z","receivedAt":"2023-06-26T18:11:51Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\ncurrent_config_*() functions aren't meant to be called outside of\nconfig callbacks because they read state that is only set when iterating\nthrough config. However, several sites in builtin/config.c are\nindirectly calling these functions outside of config callbacks thanks to\nthe format_config() helper. Show the current, bad behavior via tests\nso that the fixes in a subsequent commit will be clearer.\n\nThe misbehaving cases are:\n\n* \"git config --get-urlmatch --show-scope\" results in an \"unknown\"\n   scope, where it arguably should show the config file's scope. It's\n   clear that this wasn't intended, though: we knew that\n   \"--get-urlmatch\" couldn't show config source metadata, which is why\n   \"--show-origin\" was marked incompatible with \"--get-urlmatch\" when\n   it was introduced [1]. It was most likely a mistake that we allowed\n   \"--show-scope\" to sneak through.\n\n* Similarly, \"git config --default\" doesn't set config source metadata ,\n  so \"--show-scope\" also results in \"unknown\", and \"--show-origin\"\n  results in a BUG().\n\n[1] https://lore.kernel.org/git/20160205112001.GA13397@sigill.intra.peff.net/\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n t/t1300-config.sh | 25 +++++++++++++++++++++++++\n 1 file changed, 25 insertions(+)\n\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 86bfbc2b364..fa6a8df2521 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1668,6 +1668,21 @@ test_expect_success 'urlmatch' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'urlmatch with --show-scope' '\n+\tcat >.git/config <<-\\EOF &&\n+\t[http \"https://weak.example.com\"]\n+\t\tsslVerify = false\n+\t\tcookieFile = /tmp/cookie.txt\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tunknown\thttp.cookiefile /tmp/cookie.txt\n+\tunknown\thttp.sslverify false\n+\tEOF\n+\tgit config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'urlmatch favors more specific URLs' '\n \tcat >.git/config <<-\\EOF &&\n \t[http \"https://example.com/\"]\n@@ -2055,6 +2070,10 @@ test_expect_success '--show-origin blob ref' '\n \ttest_cmp expect output\n '\n \n+test_expect_success '--show-origin with --default' '\n+\ttest_must_fail git config --show-origin --default foo some.key\n+'\n+\n test_expect_success '--show-scope with --list' '\n \tcat >expect <<-EOF &&\n \tglobal\tuser.global=true\n@@ -2123,6 +2142,12 @@ test_expect_success '--show-scope with --show-origin' '\n \ttest_cmp expect output\n '\n \n+test_expect_success '--show-scope with --default' '\n+\tgit config --show-scope --default foo some.key >actual &&\n+\techo \"unknown\tfoo\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'override global and system config' '\n \ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n \tcat >\"$HOME\"/.gitconfig <<-EOF &&\n-- \ngitgitgadget\n\n"},{"id":"478839","messageId":"bfc6d2833c5fb904149452246021a432917d2776.1687803085.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"[PATCH v4 05/12] config: pass ctx with config files","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T18:11:16Z","receivedAt":"2023-06-26T18:11:55Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPass config_context to config_callbacks when parsing config files. To\nprovide the .kvi member, refactor out the configset logic that caches\n\"struct config_source\" and \"enum config_scope\" as a \"struct\nkey_value_info\". Make the \"enum config_scope\" available to the config\nfile machinery by plumbing an additional arg through\ngit_config_from_file_with_options().\n\nWe do not exercise ctx yet because the remaining current_config_*()\ncallers may be used with config_with_options(), which may read config\nfrom parameters, but parameters don't pass ctx yet.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n bundle-uri.c       |   1 +\n config.c           | 105 ++++++++++++++++++++++++++++++---------------\n config.h           |   8 ++--\n fsck.c             |   3 +-\n submodule-config.c |   5 ++-\n 5 files changed, 81 insertions(+), 41 deletions(-)\n\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex 0d5acc3dc51..64f32387745 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -255,6 +255,7 @@ int bundle_uri_parse_config_format(const char *uri,\n \t}\n \tresult = git_config_from_file_with_options(config_to_bundle_list,\n \t\t\t\t\t\t   filename, list,\n+\t\t\t\t\t\t   CONFIG_SCOPE_UNKNOWN,\n \t\t\t\t\t\t   &opts);\n \n \tif (!result && list->mode == BUNDLE_MODE_NONE) {\ndiff --git a/config.c b/config.c\nindex 662d406ac1e..31718711827 100644\n--- a/config.c\n+++ b/config.c\n@@ -259,7 +259,9 @@ static int handle_path_include(struct config_source *cs, const char *path,\n \t\t\t    !cs ? \"<unknown>\" :\n \t\t\t    cs->name ? cs->name :\n \t\t\t    \"the command line\");\n-\t\tret = git_config_from_file(git_config_include, path, inc);\n+\t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n+\t\t\t\t\t\t\tcurrent_config_scope(),\n+\t\t\t\t\t\t\tNULL);\n \t\tinc->depth--;\n \t}\n cleanup:\n@@ -503,7 +505,7 @@ static int git_config_include(const char *var, const char *value,\n \t * Pass along all values, including \"include\" directives; this makes it\n \t * possible to query information on the includes themselves.\n \t */\n-\tret = inc->fn(var, value, NULL, inc->data);\n+\tret = inc->fn(var, value, ctx, inc->data);\n \tif (ret < 0)\n \t\treturn ret;\n \n@@ -939,12 +941,15 @@ static char *parse_value(struct config_source *cs)\n \t}\n }\n \n-static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n-\t\t     struct strbuf *name)\n+static int get_value(struct config_source *cs, struct key_value_info *kvi,\n+\t\t     config_fn_t fn, void *data, struct strbuf *name)\n {\n \tint c;\n \tchar *value;\n \tint ret;\n+\tstruct config_context ctx = {\n+\t\t.kvi = kvi,\n+\t};\n \n \t/* Get the full name */\n \tfor (;;) {\n@@ -973,7 +978,8 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \t * accurate line number in error messages.\n \t */\n \tcs->linenr--;\n-\tret = fn(name->buf, value, NULL, data);\n+\tkvi->linenr = cs->linenr;\n+\tret = fn(name->buf, value, &ctx, data);\n \tif (ret >= 0)\n \t\tcs->linenr++;\n \treturn ret;\n@@ -1072,8 +1078,19 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \treturn 0;\n }\n \n+static void kvi_from_source(struct config_source *cs,\n+\t\t\t    enum config_scope scope,\n+\t\t\t    struct key_value_info *out)\n+{\n+\tout->filename = strintern(cs->name);\n+\tout->origin_type = cs->origin_type;\n+\tout->linenr = cs->linenr;\n+\tout->scope = scope;\n+}\n+\n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n-\t\t\t    void *data, const struct config_options *opts)\n+\t\t\t    struct key_value_info *kvi, void *data,\n+\t\t\t    const struct config_options *opts)\n {\n \tint comment = 0;\n \tsize_t baselen = 0;\n@@ -1157,7 +1174,7 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t */\n \t\tstrbuf_setlen(var, baselen);\n \t\tstrbuf_addch(var, tolower(c));\n-\t\tif (get_value(cs, fn, data, var) < 0)\n+\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n \t\t\tbreak;\n \t}\n \n@@ -2010,9 +2027,11 @@ int git_default_config(const char *var, const char *value,\n  * this function.\n  */\n static int do_config_from(struct config_reader *reader,\n-\t\t\t  struct config_source *top, config_fn_t fn, void *data,\n+\t\t\t  struct config_source *top, config_fn_t fn,\n+\t\t\t  void *data, enum config_scope scope,\n \t\t\t  const struct config_options *opts)\n {\n+\tstruct key_value_info kvi = KVI_INIT;\n \tint ret;\n \n \t/* push config-file parsing state stack */\n@@ -2022,8 +2041,9 @@ static int do_config_from(struct config_reader *reader,\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n \tconfig_reader_push_source(reader, top);\n+\tkvi_from_source(top, scope, &kvi);\n \n-\tret = git_parse_source(top, fn, data, opts);\n+\tret = git_parse_source(top, fn, &kvi, data, opts);\n \n \t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n@@ -2037,7 +2057,8 @@ static int do_config_from_file(struct config_reader *reader,\n \t\t\t       config_fn_t fn,\n \t\t\t       const enum config_origin_type origin_type,\n \t\t\t       const char *name, const char *path, FILE *f,\n-\t\t\t       void *data, const struct config_options *opts)\n+\t\t\t       void *data, enum config_scope scope,\n+\t\t\t       const struct config_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n@@ -2052,19 +2073,20 @@ static int do_config_from_file(struct config_reader *reader,\n \ttop.do_ftell = config_file_ftell;\n \n \tflockfile(f);\n-\tret = do_config_from(reader, &top, fn, data, opts);\n+\tret = do_config_from(reader, &top, fn, data, scope, opts);\n \tfunlockfile(f);\n \treturn ret;\n }\n \n-static int git_config_from_stdin(config_fn_t fn, void *data)\n+static int git_config_from_stdin(config_fn_t fn, void *data,\n+\t\t\t\t enum config_scope scope)\n {\n \treturn do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, \"\",\n-\t\t\t\t   NULL, stdin, data, NULL);\n+\t\t\t\t   NULL, stdin, data, scope, NULL);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n-\t\t\t\t      void *data,\n+\t\t\t\t      void *data, enum config_scope scope,\n \t\t\t\t      const struct config_options *opts)\n {\n \tint ret = -1;\n@@ -2075,7 +2097,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \tf = fopen_or_warn(filename, \"r\");\n \tif (f) {\n \t\tret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,\n-\t\t\t\t\t  filename, filename, f, data, opts);\n+\t\t\t\t\t  filename, filename, f, data, scope,\n+\t\t\t\t\t  opts);\n \t\tfclose(f);\n \t}\n \treturn ret;\n@@ -2083,13 +2106,15 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \n int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n {\n-\treturn git_config_from_file_with_options(fn, filename, data, NULL);\n+\treturn git_config_from_file_with_options(fn, filename, data,\n+\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, NULL);\n }\n \n int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type origin_type,\n \t\t\tconst char *name, const char *buf, size_t len,\n-\t\t\tvoid *data, const struct config_options *opts)\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \n@@ -2104,14 +2129,15 @@ int git_config_from_mem(config_fn_t fn,\n \ttop.do_ungetc = config_buf_ungetc;\n \ttop.do_ftell = config_buf_ftell;\n \n-\treturn do_config_from(&the_reader, &top, fn, data, opts);\n+\treturn do_config_from(&the_reader, &top, fn, data, scope, opts);\n }\n \n int git_config_from_blob_oid(config_fn_t fn,\n \t\t\t      const char *name,\n \t\t\t      struct repository *repo,\n \t\t\t      const struct object_id *oid,\n-\t\t\t      void *data)\n+\t\t\t      void *data,\n+\t\t\t      enum config_scope scope)\n {\n \tenum object_type type;\n \tchar *buf;\n@@ -2127,7 +2153,7 @@ int git_config_from_blob_oid(config_fn_t fn,\n \t}\n \n \tret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size,\n-\t\t\t\t  data, NULL);\n+\t\t\t\t  data, scope, NULL);\n \tfree(buf);\n \n \treturn ret;\n@@ -2136,13 +2162,14 @@ int git_config_from_blob_oid(config_fn_t fn,\n static int git_config_from_blob_ref(config_fn_t fn,\n \t\t\t\t    struct repository *repo,\n \t\t\t\t    const char *name,\n-\t\t\t\t    void *data)\n+\t\t\t\t    void *data,\n+\t\t\t\t    enum config_scope scope)\n {\n \tstruct object_id oid;\n \n \tif (repo_get_oid(repo, name, &oid) < 0)\n \t\treturn error(_(\"unable to resolve config blob '%s'\"), name);\n-\treturn git_config_from_blob_oid(fn, name, repo, &oid, data);\n+\treturn git_config_from_blob_oid(fn, name, repo, &oid, data, scope);\n }\n \n char *git_system_config(void)\n@@ -2228,27 +2255,34 @@ static int do_git_config_sequence(struct config_reader *reader,\n \tif (git_config_system() && system_config &&\n \t    !access_or_die(system_config, R_OK,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n-\t\tret += git_config_from_file(fn, system_config, data);\n+\t\tret += git_config_from_file_with_options(fn, system_config,\n+\t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n+\t\t\t\t\t\t\t NULL);\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);\n \tgit_global_config(&user_config, &xdg_config);\n \n \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file(fn, xdg_config, data);\n+\t\tret += git_config_from_file_with_options(fn, xdg_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n \tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file(fn, user_config, data);\n+\t\tret += git_config_from_file_with_options(fn, user_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);\n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n-\t\tret += git_config_from_file(fn, repo_config, data);\n+\t\tret += git_config_from_file_with_options(fn, repo_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n \tif (!opts->ignore_worktree && worktree_config &&\n \t    repo && repo->repository_format_worktree_config &&\n \t    !access_or_die(worktree_config, R_OK, 0)) {\n-\t\tret += git_config_from_file(fn, worktree_config, data);\n+\t\t\tret += git_config_from_file_with_options(fn, worktree_config, data,\n+\t\t\t\t\t\t\t\t CONFIG_SCOPE_WORKTREE,\n+\t\t\t\t\t\t\t\t NULL);\n \t}\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);\n@@ -2292,12 +2326,14 @@ int config_with_options(config_fn_t fn, void *data,\n \t * regular lookup sequence.\n \t */\n \tif (config_source && config_source->use_stdin) {\n-\t\tret = git_config_from_stdin(fn, data);\n+\t\tret = git_config_from_stdin(fn, data, config_source->scope);\n \t} else if (config_source && config_source->file) {\n-\t\tret = git_config_from_file(fn, config_source->file, data);\n+\t\tret = git_config_from_file_with_options(fn, config_source->file,\n+\t\t\t\t\t\t\tdata, config_source->scope,\n+\t\t\t\t\t\t\tNULL);\n \t} else if (config_source && config_source->blob) {\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n-\t\t\t\t\t\tdata);\n+\t\t\t\t\t       data, config_source->scope);\n \t} else {\n \t\tret = do_git_config_sequence(&the_reader, opts, repo, fn, data);\n \t}\n@@ -2440,16 +2476,14 @@ static int configset_add_value(struct config_reader *reader,\n \tif (!reader->source)\n \t\tBUG(\"configset_add_value has no source\");\n \tif (reader->source->name) {\n-\t\tkv_info->filename = strintern(reader->source->name);\n-\t\tkv_info->linenr = reader->source->linenr;\n-\t\tkv_info->origin_type = reader->source->origin_type;\n+\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n \t} else {\n \t\t/* for values read from `git_config_from_parameters()` */\n \t\tkv_info->filename = NULL;\n \t\tkv_info->linenr = -1;\n \t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n+\t\tkv_info->scope = reader->parsing_scope;\n \t}\n-\tkv_info->scope = reader->parsing_scope;\n \tsi->util = kv_info;\n \n \treturn 0;\n@@ -3490,7 +3524,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \t\t */\n \t\tif (git_config_from_file_with_options(store_aux,\n \t\t\t\t\t\t      config_filename,\n-\t\t\t\t\t\t      &store, &opts)) {\n+\t\t\t\t\t\t      &store, CONFIG_SCOPE_UNKNOWN,\n+\t\t\t\t\t\t      &opts)) {\n \t\t\terror(_(\"invalid config file %s\"), config_filename);\n \t\t\tret = CONFIG_INVALID_FILE;\n \t\t\tgoto out_free;\ndiff --git a/config.h b/config.h\nindex ddf147bb2d1..206bf1f175a 100644\n--- a/config.h\n+++ b/config.h\n@@ -169,16 +169,18 @@ int git_default_config(const char *, const char *,\n int git_config_from_file(config_fn_t fn, const char *, void *);\n \n int git_config_from_file_with_options(config_fn_t fn, const char *,\n-\t\t\t\t      void *,\n+\t\t\t\t      void *, enum config_scope,\n \t\t\t\t      const struct config_options *);\n int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type,\n \t\t\tconst char *name,\n \t\t\tconst char *buf, size_t len,\n-\t\t\tvoid *data, const struct config_options *opts);\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_options *opts);\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     struct repository *repo,\n-\t\t\t     const struct object_id *oid, void *data);\n+\t\t\t     const struct object_id *oid, void *data,\n+\t\t\t     enum config_scope scope);\n void git_config_push_parameter(const char *text);\n void git_config_push_env(const char *spec);\n int git_config_from_parameters(config_fn_t fn, void *data);\ndiff --git a/fsck.c b/fsck.c\nindex 55b6a694853..f92c216fb5c 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1238,7 +1238,8 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\tdata.ret = 0;\n \t\tconfig_opts.error_action = CONFIG_ERROR_SILENT;\n \t\tif (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB,\n-\t\t\t\t\t\".gitmodules\", buf, size, &data, &config_opts))\n+\t\t\t\t\t\".gitmodules\", buf, size, &data,\n+\t\t\t\t\tCONFIG_SCOPE_UNKNOWN, &config_opts))\n \t\t\tdata.ret |= report(options, oid, OBJ_BLOB,\n \t\t\t\t\tFSCK_MSG_GITMODULES_PARSE,\n \t\t\t\t\t\"could not parse gitmodules blob\");\ndiff --git a/submodule-config.c b/submodule-config.c\nindex a38d4d49731..3f25bd13674 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -606,7 +606,7 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \tparameter.gitmodules_oid = &oid;\n \tparameter.overwrite = 0;\n \tgit_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf,\n-\t\t\tconfig, config_size, &parameter, NULL);\n+\t\t\t    config, config_size, &parameter, CONFIG_SCOPE_UNKNOWN, NULL);\n \tstrbuf_release(&rev);\n \tfree(config);\n \n@@ -714,7 +714,8 @@ void gitmodules_config_oid(const struct object_id *commit_oid)\n \n \tif (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) {\n \t\tgit_config_from_blob_oid(gitmodules_cb, rev.buf,\n-\t\t\t\t\t the_repository, &oid, the_repository);\n+\t\t\t\t\t the_repository, &oid, the_repository,\n+\t\t\t\t\t CONFIG_SCOPE_UNKNOWN);\n \t}\n \tstrbuf_release(&rev);\n \n-- \ngitgitgadget\n\n"},{"id":"478840","messageId":"33e4437737dfe04c4666c4c56f1b26276a80f0f4.1687803085.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"[PATCH v4 07/12] config.c: pass ctx with CLI config","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T18:11:18Z","receivedAt":"2023-06-26T18:11:56Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPass config_context when parsing CLI config. To provide the .kvi member,\nrefactor out kvi_from_param() from the logic that caches CLI config in\nconfigsets. Now that config_context and config_context.kvi is always\npresent when config machinery calls config callbacks, plumb \"kvi\" so\nthat we can remove all calls of current_config_scope() except for\ntrace2/*.c (which will be handled in a later commit), and remove all\nother current_config_*() (the functions themselves and their calls).\nNote that this results in .kvi containing a different, more complete\nset of information than the mocked up \"struct config_source\" in\ngit_config_from_parameters().\n\nPlumbing \"kvi\" reveals a few places where we've been doing the wrong\nthing:\n\n* git_config_parse_parameter() hasn't been setting config source\n  information, so plumb \"kvi\" there too.\n\n* \"git config --get-urlmatch --show-scope\" iterates config to collect\n  values, but then attempts to display the scope after config iteration.\n  Fix this by copying the \"kvi\" value in the collection phase so that it\n  can be read back later. This means that we can now support \"git config\n  --get-urlmatch --show-origin\" (we don't allow this combination of args\n  because of this bug), but that is left unchanged for now.\n\n* \"git config --default\" doesn't have config source metadata when\n  displaying the default value. Fix this by treating the default value\n  as if it came from the command line (e.g. like we do with \"git -c\" or\n  \"git config --file\"), using kvi_from_param().\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/config.c  | 47 +++++++++++++++++----------\n config.c          | 82 +++++++++++++++++++++++------------------------\n config.h          |  3 +-\n t/t1300-config.sh | 10 +++---\n 4 files changed, 78 insertions(+), 64 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex f4fccf99cb8..9b9f5527311 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -194,38 +194,42 @@ static void check_argc(int argc, int min, int max)\n \tusage_builtin_config();\n }\n \n-static void show_config_origin(struct strbuf *buf)\n+static void show_config_origin(const struct key_value_info *kvi,\n+\t\t\t       struct strbuf *buf)\n {\n \tconst char term = end_nul ? '\\0' : '\\t';\n \n-\tstrbuf_addstr(buf, current_config_origin_type());\n+\tstrbuf_addstr(buf, config_origin_type_name(kvi->origin_type));\n \tstrbuf_addch(buf, ':');\n \tif (end_nul)\n-\t\tstrbuf_addstr(buf, current_config_name());\n+\t\tstrbuf_addstr(buf, kvi->filename ? kvi->filename : \"\");\n \telse\n-\t\tquote_c_style(current_config_name(), buf, NULL, 0);\n+\t\tquote_c_style(kvi->filename ? kvi->filename : \"\", buf, NULL, 0);\n \tstrbuf_addch(buf, term);\n }\n \n-static void show_config_scope(struct strbuf *buf)\n+static void show_config_scope(const struct key_value_info *kvi,\n+\t\t\t      struct strbuf *buf)\n {\n \tconst char term = end_nul ? '\\0' : '\\t';\n-\tconst char *scope = config_scope_name(current_config_scope());\n+\tconst char *scope = config_scope_name(kvi->scope);\n \n \tstrbuf_addstr(buf, N_(scope));\n \tstrbuf_addch(buf, term);\n }\n \n static int show_all_config(const char *key_, const char *value_,\n-\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t   const struct config_context *ctx,\n \t\t\t   void *cb UNUSED)\n {\n+\tconst struct key_value_info *kvi = ctx->kvi;\n+\n \tif (show_origin || show_scope) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \t\tif (show_scope)\n-\t\t\tshow_config_scope(&buf);\n+\t\t\tshow_config_scope(kvi, &buf);\n \t\tif (show_origin)\n-\t\t\tshow_config_origin(&buf);\n+\t\t\tshow_config_origin(kvi, &buf);\n \t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n \t\tfwrite(buf.buf, 1, buf.len, stdout);\n \t\tstrbuf_release(&buf);\n@@ -243,12 +247,13 @@ struct strbuf_list {\n \tint alloc;\n };\n \n-static int format_config(struct strbuf *buf, const char *key_, const char *value_)\n+static int format_config(struct strbuf *buf, const char *key_,\n+\t\t\t const char *value_, const struct key_value_info *kvi)\n {\n \tif (show_scope)\n-\t\tshow_config_scope(buf);\n+\t\tshow_config_scope(kvi, buf);\n \tif (show_origin)\n-\t\tshow_config_origin(buf);\n+\t\tshow_config_origin(kvi, buf);\n \tif (show_keys)\n \t\tstrbuf_addstr(buf, key_);\n \tif (!omit_values) {\n@@ -303,9 +308,10 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value\n }\n \n static int collect_config(const char *key_, const char *value_,\n-\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tstruct strbuf_list *values = cb;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \n \tif (!use_key_regexp && strcmp(key_, key))\n \t\treturn 0;\n@@ -320,7 +326,7 @@ static int collect_config(const char *key_, const char *value_,\n \tALLOC_GROW(values->items, values->nr + 1, values->alloc);\n \tstrbuf_init(&values->items[values->nr], 0);\n \n-\treturn format_config(&values->items[values->nr++], key_, value_);\n+\treturn format_config(&values->items[values->nr++], key_, value_, kvi);\n }\n \n static int get_value(const char *key_, const char *regex_, unsigned flags)\n@@ -382,11 +388,14 @@ static int get_value(const char *key_, const char *regex_, unsigned flags)\n \t\t\t    &config_options);\n \n \tif (!values.nr && default_value) {\n+\t\tstruct key_value_info kvi = KVI_INIT;\n \t\tstruct strbuf *item;\n+\n+\t\tkvi_from_param(&kvi);\n \t\tALLOC_GROW(values.items, values.nr + 1, values.alloc);\n \t\titem = &values.items[values.nr++];\n \t\tstrbuf_init(item, 0);\n-\t\tif (format_config(item, key_, default_value) < 0)\n+\t\tif (format_config(item, key_, default_value, &kvi) < 0)\n \t\t\tdie(_(\"failed to format default config value: %s\"),\n \t\t\t\tdefault_value);\n \t}\n@@ -563,15 +572,17 @@ static void check_write(void)\n struct urlmatch_current_candidate_value {\n \tchar value_is_null;\n \tstruct strbuf value;\n+\tstruct key_value_info kvi;\n };\n \n static int urlmatch_collect_fn(const char *var, const char *value,\n-\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       const struct config_context *ctx,\n \t\t\t       void *cb)\n {\n \tstruct string_list *values = cb;\n \tstruct string_list_item *item = string_list_insert(values, var);\n \tstruct urlmatch_current_candidate_value *matched = item->util;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \n \tif (!matched) {\n \t\tmatched = xmalloc(sizeof(*matched));\n@@ -580,6 +591,7 @@ static int urlmatch_collect_fn(const char *var, const char *value,\n \t} else {\n \t\tstrbuf_reset(&matched->value);\n \t}\n+\tmatched->kvi = *kvi;\n \n \tif (value) {\n \t\tstrbuf_addstr(&matched->value, value);\n@@ -627,7 +639,8 @@ static int get_urlmatch(const char *var, const char *url)\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n \t\tformat_config(&buf, item->string,\n-\t\t\t      matched->value_is_null ? NULL : matched->value.buf);\n+\t\t\t      matched->value_is_null ? NULL : matched->value.buf,\n+\t\t\t      &matched->kvi);\n \t\tfwrite(buf.buf, 1, buf.len, stdout);\n \t\tstrbuf_release(&buf);\n \ndiff --git a/config.c b/config.c\nindex 31718711827..2e24e6ea515 100644\n--- a/config.c\n+++ b/config.c\n@@ -219,7 +219,9 @@ static const char include_depth_advice[] = N_(\n \"from\\n\"\n \"\t%s\\n\"\n \"This might be due to circular includes.\");\n-static int handle_path_include(struct config_source *cs, const char *path,\n+static int handle_path_include(struct config_source *cs,\n+\t\t\t       const struct key_value_info *kvi,\n+\t\t\t       const char *path,\n \t\t\t       struct config_include_data *inc)\n {\n \tint ret = 0;\n@@ -260,8 +262,7 @@ static int handle_path_include(struct config_source *cs, const char *path,\n \t\t\t    cs->name ? cs->name :\n \t\t\t    \"the command line\");\n \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n-\t\t\t\t\t\t\tcurrent_config_scope(),\n-\t\t\t\t\t\t\tNULL);\n+\t\t\t\t\t\t\tkvi->scope, NULL);\n \t\tinc->depth--;\n \t}\n cleanup:\n@@ -510,7 +511,7 @@ static int git_config_include(const char *var, const char *value,\n \t\treturn ret;\n \n \tif (!strcmp(var, \"include.path\"))\n-\t\tret = handle_path_include(cs, value, inc);\n+\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n \t    cond && include_condition_is_true(cs, inc, cond, cond_len) &&\n@@ -519,7 +520,7 @@ static int git_config_include(const char *var, const char *value,\n \n \t\tif (inc->opts->unconditional_remote_url)\n \t\t\tinc->fn = forbid_remote_url;\n-\t\tret = handle_path_include(cs, value, inc);\n+\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -677,27 +678,44 @@ out_free_ret_1:\n }\n \n static int config_parse_pair(const char *key, const char *value,\n-\t\t\t  config_fn_t fn, void *data)\n+\t\t\t     struct key_value_info *kvi,\n+\t\t\t     config_fn_t fn, void *data)\n {\n \tchar *canonical_name;\n \tint ret;\n+\tstruct config_context ctx = {\n+\t\t.kvi = kvi,\n+\t};\n \n \tif (!strlen(key))\n \t\treturn error(_(\"empty config key\"));\n \tif (git_config_parse_key(key, &canonical_name, NULL))\n \t\treturn -1;\n \n-\tret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;\n+\tret = (fn(canonical_name, value, &ctx, data) < 0) ? -1 : 0;\n \tfree(canonical_name);\n \treturn ret;\n }\n \n+\n+/* for values read from `git_config_from_parameters()` */\n+void kvi_from_param(struct key_value_info *out)\n+{\n+\tout->filename = NULL;\n+\tout->linenr = -1;\n+\tout->origin_type = CONFIG_ORIGIN_CMDLINE;\n+\tout->scope = CONFIG_SCOPE_COMMAND;\n+}\n+\n int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n \tconst char *value;\n \tstruct strbuf **pair;\n \tint ret;\n+\tstruct key_value_info kvi = KVI_INIT;\n+\n+\tkvi_from_param(&kvi);\n \n \tpair = strbuf_split_str(text, '=', 2);\n \tif (!pair[0])\n@@ -716,12 +734,13 @@ int git_config_parse_parameter(const char *text,\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \t}\n \n-\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n+\tret = config_parse_pair(pair[0]->buf, value, &kvi, fn, data);\n \tstrbuf_list_free(pair);\n \treturn ret;\n }\n \n-static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n+static int parse_config_env_list(char *env, struct key_value_info *kvi,\n+\t\t\t\t config_fn_t fn, void *data)\n {\n \tchar *cur = env;\n \twhile (cur && *cur) {\n@@ -755,7 +774,7 @@ static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n \t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n \t\t\t}\n \n-\t\t\tif (config_parse_pair(key, value, fn, data) < 0)\n+\t\t\tif (config_parse_pair(key, value, kvi, fn, data) < 0)\n \t\t\t\treturn -1;\n \t\t}\n \t\telse {\n@@ -780,10 +799,13 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tint ret = 0;\n \tchar *envw = NULL;\n \tstruct config_source source = CONFIG_SOURCE_INIT;\n+\tstruct key_value_info kvi = KVI_INIT;\n \n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n \tconfig_reader_push_source(&the_reader, &source);\n \n+\tkvi_from_param(&kvi);\n+\n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n \t\tunsigned long count;\n@@ -819,7 +841,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \t\t\t}\n \t\t\tstrbuf_reset(&envvar);\n \n-\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n+\t\t\tif (config_parse_pair(key, value, &kvi, fn, data) < 0) {\n \t\t\t\tret = -1;\n \t\t\t\tgoto out;\n \t\t\t}\n@@ -830,7 +852,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tif (env) {\n \t\t/* sq_dequote will write over it */\n \t\tenvw = xstrdup(env);\n-\t\tif (parse_config_env_list(envw, fn, data) < 0) {\n+\t\tif (parse_config_env_list(envw, &kvi, fn, data) < 0) {\n \t\t\tret = -1;\n \t\t\tgoto out;\n \t\t}\n@@ -2442,7 +2464,8 @@ static int configset_find_element(struct config_set *set, const char *key,\n \treturn 0;\n }\n \n-static int configset_add_value(struct config_reader *reader,\n+static int configset_add_value(const struct key_value_info *kvi_p,\n+\t\t\t       struct config_reader *reader,\n \t\t\t       struct config_set *set, const char *key,\n \t\t\t       const char *value)\n {\n@@ -2473,16 +2496,10 @@ static int configset_add_value(struct config_reader *reader,\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (!reader->source)\n-\t\tBUG(\"configset_add_value has no source\");\n \tif (reader->source->name) {\n-\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n+\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n \t} else {\n-\t\t/* for values read from `git_config_from_parameters()` */\n-\t\tkv_info->filename = NULL;\n-\t\tkv_info->linenr = -1;\n-\t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n-\t\tkv_info->scope = reader->parsing_scope;\n+\t\tkvi_from_param(kv_info);\n \t}\n \tsi->util = kv_info;\n \n@@ -2538,11 +2555,12 @@ struct configset_add_data {\n #define CONFIGSET_ADD_INIT { 0 }\n \n static int config_set_callback(const char *key, const char *value,\n-\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       const struct config_context *ctx,\n \t\t\t       void *cb)\n {\n \tstruct configset_add_data *data = cb;\n-\tconfigset_add_value(data->config_reader, data->config_set, key, value);\n+\tconfigset_add_value(ctx->kvi, data->config_reader, data->config_set,\n+\t\t\t    key, value);\n \treturn 0;\n }\n \n@@ -4037,16 +4055,6 @@ const char *config_origin_type_name(enum config_origin_type type)\n \t}\n }\n \n-const char *current_config_origin_type(void)\n-{\n-\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n-\n-\tif (reader_origin_type(&the_reader, &type))\n-\t\tBUG(\"current_config_origin_type called outside config callback\");\n-\n-\treturn config_origin_type_name(type);\n-}\n-\n const char *config_scope_name(enum config_scope scope)\n {\n \tswitch (scope) {\n@@ -4078,14 +4086,6 @@ static int reader_config_name(struct config_reader *reader, const char **out)\n \treturn 0;\n }\n \n-const char *current_config_name(void)\n-{\n-\tconst char *name;\n-\tif (reader_config_name(&the_reader, &name))\n-\t\tBUG(\"current_config_name called outside config callback\");\n-\treturn name ? name : \"\";\n-}\n-\n enum config_scope current_config_scope(void)\n {\n \tif (the_reader.config_kvi)\ndiff --git a/config.h b/config.h\nindex 206bf1f175a..ea92392400e 100644\n--- a/config.h\n+++ b/config.h\n@@ -387,9 +387,8 @@ void git_global_config(char **user, char **xdg);\n int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n \n enum config_scope current_config_scope(void);\n-const char *current_config_origin_type(void);\n-const char *current_config_name(void);\n const char *config_origin_type_name(enum config_origin_type type);\n+void kvi_from_param(struct key_value_info *out);\n \n /*\n  * Match and parse a config key of the form:\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex fa6a8df2521..387d336c91f 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1676,8 +1676,8 @@ test_expect_success 'urlmatch with --show-scope' '\n \tEOF\n \n \tcat >expect <<-EOF &&\n-\tunknown\thttp.cookiefile /tmp/cookie.txt\n-\tunknown\thttp.sslverify false\n+\tlocal\thttp.cookiefile /tmp/cookie.txt\n+\tlocal\thttp.sslverify false\n \tEOF\n \tgit config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&\n \ttest_cmp expect actual\n@@ -2071,7 +2071,9 @@ test_expect_success '--show-origin blob ref' '\n '\n \n test_expect_success '--show-origin with --default' '\n-\ttest_must_fail git config --show-origin --default foo some.key\n+\tgit config --show-origin --default foo some.key >actual &&\n+\techo \"command line:\tfoo\" >expect &&\n+\ttest_cmp expect actual\n '\n \n test_expect_success '--show-scope with --list' '\n@@ -2144,7 +2146,7 @@ test_expect_success '--show-scope with --show-origin' '\n \n test_expect_success '--show-scope with --default' '\n \tgit config --show-scope --default foo some.key >actual &&\n-\techo \"unknown\tfoo\" >expect &&\n+\techo \"command\tfoo\" >expect &&\n \ttest_cmp expect actual\n '\n \n-- \ngitgitgadget\n\n"},{"id":"478841","messageId":"9bd5f60282c5308e9f8575adca717019617341db.1687803086.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"[PATCH v4 08/12] trace2: plumb config kvi","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T18:11:19Z","receivedAt":"2023-06-26T18:11:58Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThere is a code path starting from trace2_def_param_fl() that eventually\ncalls current_config_scope(), and thus it needs to have \"kvi\" plumbed\nthrough it. Additional plumbing is also needed to get \"kvi\" to\ntrace2_def_param_fl(), which gets called by two code paths:\n\n- Through tr2_cfg_cb(), which is a config callback, so it trivially\n  receives \"kvi\" via the \"struct config_context ctx\" parameter.\n\n- Through tr2_list_env_vars_fl(), which is a high level function that\n  lists environment variables for tracing. This has been secretly\n  behaving like git_config_from_parameters() (in that it parses config\n  from environment variables/the CLI), but does not set config source\n  information.\n\n  Teach tr2_list_env_vars_fl() to be well-behaved by using\n  kvi_from_param(), which is used elsewhere for CLI/environment\n  variable-based config.\n\nAs a result, current_config_scope() has no more callers, so remove it.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c                | 46 -----------------------------------------\n config.h                |  1 -\n trace2.c                |  4 ++--\n trace2.h                |  3 ++-\n trace2/tr2_cfg.c        |  9 +++++---\n trace2/tr2_tgt.h        |  4 +++-\n trace2/tr2_tgt_event.c  |  4 ++--\n trace2/tr2_tgt_normal.c |  4 ++--\n trace2/tr2_tgt_perf.c   |  4 ++--\n 9 files changed, 19 insertions(+), 60 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 2e24e6ea515..8546fea370e 100644\n--- a/config.c\n+++ b/config.c\n@@ -85,16 +85,6 @@ struct config_reader {\n \t */\n \tstruct config_source *source;\n \tstruct key_value_info *config_kvi;\n-\t/*\n-\t * The \"scope\" of the current config source being parsed (repo, global,\n-\t * etc). Like \"source\", this is only set when parsing a config source.\n-\t * It's not part of \"source\" because it transcends a single file (i.e.,\n-\t * a file included from .git/config is still in \"repo\" scope).\n-\t *\n-\t * When iterating through a configset, the equivalent value is\n-\t * \"config_kvi.scope\" (see above).\n-\t */\n-\tenum config_scope parsing_scope;\n };\n /*\n  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n@@ -125,19 +115,9 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\n static inline void config_reader_set_kvi(struct config_reader *reader,\n \t\t\t\t\t struct key_value_info *kvi)\n {\n-\tif (kvi && (reader->source || reader->parsing_scope))\n-\t\tBUG(\"kvi should not be set while parsing a config source\");\n \treader->config_kvi = kvi;\n }\n \n-static inline void config_reader_set_scope(struct config_reader *reader,\n-\t\t\t\t\t   enum config_scope scope)\n-{\n-\tif (scope && reader->config_kvi)\n-\t\tBUG(\"scope should only be set when iterating through a config source\");\n-\treader->parsing_scope = scope;\n-}\n-\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -412,19 +392,13 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tenum config_scope store_scope = inc->config_reader->parsing_scope;\n-\n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tconfig_reader_set_scope(inc->config_reader, 0);\n-\n \tinc->remote_urls = xmalloc(sizeof(*inc->remote_urls));\n \tstring_list_init_dup(inc->remote_urls);\n \tconfig_with_options(add_remote_url, inc->remote_urls,\n \t\t\t    inc->config_source, inc->repo, &opts);\n-\n-\tconfig_reader_set_scope(inc->config_reader, store_scope);\n }\n \n static int forbid_remote_url(const char *var, const char *value UNUSED,\n@@ -2255,7 +2229,6 @@ static int do_git_config_sequence(struct config_reader *reader,\n \tchar *user_config = NULL;\n \tchar *repo_config;\n \tchar *worktree_config;\n-\tenum config_scope prev_parsing_scope = reader->parsing_scope;\n \n \t/*\n \t * Ensure that either:\n@@ -2273,7 +2246,6 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\tworktree_config = NULL;\n \t}\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_SYSTEM);\n \tif (git_config_system() && system_config &&\n \t    !access_or_die(system_config, R_OK,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n@@ -2281,7 +2253,6 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n \t\t\t\t\t\t\t NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);\n \tgit_global_config(&user_config, &xdg_config);\n \n \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n@@ -2292,13 +2263,11 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\tret += git_config_from_file_with_options(fn, user_config, data,\n \t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);\n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n \t\tret += git_config_from_file_with_options(fn, repo_config, data,\n \t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n \tif (!opts->ignore_worktree && worktree_config &&\n \t    repo && repo->repository_format_worktree_config &&\n \t    !access_or_die(worktree_config, R_OK, 0)) {\n@@ -2307,11 +2276,9 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\t\t\t\t\t\t\t NULL);\n \t}\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);\n \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n \t\tdie(_(\"unable to parse command-line config\"));\n \n-\tconfig_reader_set_scope(reader, prev_parsing_scope);\n \tfree(system_config);\n \tfree(xdg_config);\n \tfree(user_config);\n@@ -2326,7 +2293,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\t\tconst struct config_options *opts)\n {\n \tstruct config_include_data inc = CONFIG_INCLUDE_INIT;\n-\tenum config_scope prev_scope = the_reader.parsing_scope;\n \tint ret;\n \n \tif (opts->respect_includes) {\n@@ -2340,9 +2306,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tdata = &inc;\n \t}\n \n-\tif (config_source)\n-\t\tconfig_reader_set_scope(&the_reader, config_source->scope);\n-\n \t/*\n \t * If we have a specific filename, use it. Otherwise, follow the\n \t * regular lookup sequence.\n@@ -2364,7 +2327,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tstring_list_clear(inc.remote_urls, 0);\n \t\tFREE_AND_NULL(inc.remote_urls);\n \t}\n-\tconfig_reader_set_scope(&the_reader, prev_scope);\n \treturn ret;\n }\n \n@@ -4086,14 +4048,6 @@ static int reader_config_name(struct config_reader *reader, const char **out)\n \treturn 0;\n }\n \n-enum config_scope current_config_scope(void)\n-{\n-\tif (the_reader.config_kvi)\n-\t\treturn the_reader.config_kvi->scope;\n-\telse\n-\t\treturn the_reader.parsing_scope;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex ea92392400e..82eeba94e71 100644\n--- a/config.h\n+++ b/config.h\n@@ -386,7 +386,6 @@ void git_global_config(char **user, char **xdg);\n \n int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n \n-enum config_scope current_config_scope(void);\n const char *config_origin_type_name(enum config_origin_type type);\n void kvi_from_param(struct key_value_info *out);\n \ndiff --git a/trace2.c b/trace2.c\nindex 0efc4e7b958..49c23bfd05a 100644\n--- a/trace2.c\n+++ b/trace2.c\n@@ -634,7 +634,7 @@ void trace2_thread_exit_fl(const char *file, int line)\n }\n \n void trace2_def_param_fl(const char *file, int line, const char *param,\n-\t\t\t const char *value)\n+\t\t\t const char *value, const struct key_value_info *kvi)\n {\n \tstruct tr2_tgt *tgt_j;\n \tint j;\n@@ -644,7 +644,7 @@ void trace2_def_param_fl(const char *file, int line, const char *param,\n \n \tfor_each_wanted_builtin (j, tgt_j)\n \t\tif (tgt_j->pfn_param_fl)\n-\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n+\t\t\ttgt_j->pfn_param_fl(file, line, param, value, kvi);\n }\n \n void trace2_def_repo_fl(const char *file, int line, struct repository *repo)\ndiff --git a/trace2.h b/trace2.h\nindex 4ced30c0db3..f5c5a9e6bac 100644\n--- a/trace2.h\n+++ b/trace2.h\n@@ -325,6 +325,7 @@ void trace2_thread_exit_fl(const char *file, int line);\n \n #define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)\n \n+struct key_value_info;\n /*\n  * Emits a \"def_param\" message containing a key/value pair.\n  *\n@@ -334,7 +335,7 @@ void trace2_thread_exit_fl(const char *file, int line);\n  * `core.abbrev`, `status.showUntrackedFiles`, or `--no-ahead-behind`.\n  */\n void trace2_def_param_fl(const char *file, int line, const char *param,\n-\t\t\t const char *value);\n+\t\t\t const char *value, const struct key_value_info *kvi);\n \n #define trace2_def_param(param, value) \\\n \ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 83bc4fd109c..733d9d2872a 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -100,7 +100,7 @@ struct tr2_cfg_data {\n  * See if the given config key matches any of our patterns of interest.\n  */\n static int tr2_cfg_cb(const char *key, const char *value,\n-\t\t      const struct config_context *ctx UNUSED, void *d)\n+\t\t      const struct config_context *ctx, void *d)\n {\n \tstruct strbuf **s;\n \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n@@ -109,7 +109,8 @@ static int tr2_cfg_cb(const char *key, const char *value,\n \t\tstruct strbuf *buf = *s;\n \t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n \t\tif (wm == WM_MATCH) {\n-\t\t\ttrace2_def_param_fl(data->file, data->line, key, value);\n+\t\t\ttrace2_def_param_fl(data->file, data->line, key, value,\n+\t\t\t\t\t    ctx->kvi);\n \t\t\treturn 0;\n \t\t}\n \t}\n@@ -127,8 +128,10 @@ void tr2_cfg_list_config_fl(const char *file, int line)\n \n void tr2_list_env_vars_fl(const char *file, int line)\n {\n+\tstruct key_value_info kvi = KVI_INIT;\n \tstruct strbuf **s;\n \n+\tkvi_from_param(&kvi);\n \tif (tr2_load_env_vars() <= 0)\n \t\treturn;\n \n@@ -136,7 +139,7 @@ void tr2_list_env_vars_fl(const char *file, int line)\n \t\tstruct strbuf *buf = *s;\n \t\tconst char *val = getenv(buf->buf);\n \t\tif (val && *val)\n-\t\t\ttrace2_def_param_fl(file, line, buf->buf, val);\n+\t\t\ttrace2_def_param_fl(file, line, buf->buf, val, &kvi);\n \t}\n }\n \ndiff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h\nindex bf8745c4f05..1f626cffea0 100644\n--- a/trace2/tr2_tgt.h\n+++ b/trace2/tr2_tgt.h\n@@ -69,8 +69,10 @@ typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, int line,\n \t\t\t\t\t   uint64_t us_elapsed_absolute,\n \t\t\t\t\t   int exec_id, int code);\n \n+struct key_value_info;\n typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,\n-\t\t\t\t     const char *param, const char *value);\n+\t\t\t\t     const char *param, const char *value,\n+\t\t\t\t     const struct key_value_info *kvi);\n \n typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,\n \t\t\t\t    const struct repository *repo);\ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nindex 2af53e5d4de..53091781eca 100644\n--- a/trace2/tr2_tgt_event.c\n+++ b/trace2/tr2_tgt_event.c\n@@ -477,11 +477,11 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, const struct key_value_info *kvi)\n {\n \tconst char *event_name = \"def_param\";\n \tstruct json_writer jw = JSON_WRITER_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tjw_object_begin(&jw, 0);\ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nindex 1ebfb464d54..d25ea131643 100644\n--- a/trace2/tr2_tgt_normal.c\n+++ b/trace2/tr2_tgt_normal.c\n@@ -297,10 +297,10 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, const struct key_value_info *kvi)\n {\n \tstruct strbuf buf_payload = STRBUF_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tstrbuf_addf(&buf_payload, \"def_param scope:%s %s=%s\", scope_name, param,\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nindex 328e483a05e..a6f9a8a193e 100644\n--- a/trace2/tr2_tgt_perf.c\n+++ b/trace2/tr2_tgt_perf.c\n@@ -439,12 +439,12 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, const struct key_value_info *kvi)\n {\n \tconst char *event_name = \"def_param\";\n \tstruct strbuf buf_payload = STRBUF_INIT;\n \tstruct strbuf scope_payload = STRBUF_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tstrbuf_addf(&buf_payload, \"%s:%s\", param, value);\n-- \ngitgitgadget\n\n"},{"id":"478843","messageId":"a9a0a50f32a6d01e2d5cd411f97588734aea2fd1.1687803085.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"[PATCH v4 03/12] config: add ctx arg to config_fn_t","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T18:11:14Z","receivedAt":"2023-06-26T18:12:01Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nAdd a new \"const struct config_context *ctx\" arg to config_fn_t to hold\nadditional information about the config iteration operation.\nconfig_context has a \"struct key_value_info kvi\" member that holds\nmetadata about the config source being read (e.g. what kind of config\nsource it is, the filename, etc). In this series, we're only interested\nin .kvi, so we could have just used \"struct key_value_info\" as an arg,\nbut config_context makes it possible to add/adjust members in the future\nwithout changing the config_fn_t signature. We could also consider other\nways of organizing the args (e.g. moving the config name and value into\nconfig_context or key_value_info), but in my experiments, the\nincremental benefit doesn't justify the added complexity (e.g. a\nconfig_fn_t will sometimes invoke another config_fn_t but with a\ndifferent config value).\n\nIn subsequent commits, the .kvi member will replace the global \"struct\nconfig_reader\" in config.c, making config iteration a global-free\noperation. It requires much more work for the machinery to provide\nmeaningful values of .kvi, so for now, merely change the signature and\ncall sites, pass NULL as a placeholder value, and don't rely on the arg\nin any meaningful way.\n\nMost of the changes are performed by\ncontrib/coccinelle/config_fn_ctx.pending.cocci, which, for every\nconfig_fn_t:\n\n- Modifies the signature to accept \"const struct config_context *ctx\"\n- Passes \"ctx\" to any inner config_fn_t, if needed\n- Adds UNUSED attributes to \"ctx\", if needed\n\nMost config_fn_t instances are easily identified by seeing if they are\ncalled by the various config functions. Most of the remaining ones are\nmanually named in the .cocci patch. Manual cleanups are still needed,\nbut the majority of it is trivial; it's either adjusting config_fn_t\nthat the .cocci patch didn't catch, or adding forward declarations of\n\"struct config_context ctx\" to make the signatures make sense.\n\nThe non-trivial changes are in cases where we are invoking a config_fn_t\noutside of config machinery, and we now need to decide what value of\n\"ctx\" to pass. These cases are:\n\n- trace2/tr2_cfg.c:tr2_cfg_set_fl()\n\n  This is indirectly called by git_config_set() so that the trace2\n  machinery can notice the new config values and update its settings\n  using the tr2 config parsing function, i.e. tr2_cfg_cb().\n\n- builtin/checkout.c:checkout_main()\n\n  This calls git_xmerge_config() as a shorthand for parsing a CLI arg.\n  This might be worth refactoring away in the future, since\n  git_xmerge_config() can call git_default_config(), which can do much\n  more than just parsing.\n\nHandle them by creating a KVI_INIT macro that initializes \"struct\nkey_value_info\" to a reasonable default, and use that to construct the\n\"ctx\" arg.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n alias.c                                       |   3 +-\n archive-tar.c                                 |   3 +-\n archive-zip.c                                 |   1 +\n builtin/add.c                                 |   5 +-\n builtin/blame.c                               |   5 +-\n builtin/branch.c                              |   5 +-\n builtin/cat-file.c                            |   5 +-\n builtin/checkout.c                            |  12 +-\n builtin/clean.c                               |   5 +-\n builtin/clone.c                               |  11 +-\n builtin/column.c                              |   3 +-\n builtin/commit-graph.c                        |   1 +\n builtin/commit.c                              |  10 +-\n builtin/config.c                              |  10 +-\n builtin/difftool.c                            |   5 +-\n builtin/fetch.c                               |   9 +-\n builtin/fsmonitor--daemon.c                   |   5 +-\n builtin/grep.c                                |   7 +-\n builtin/help.c                                |   5 +-\n builtin/index-pack.c                          |   5 +-\n builtin/log.c                                 |  10 +-\n builtin/merge.c                               |   7 +-\n builtin/multi-pack-index.c                    |   1 +\n builtin/pack-objects.c                        |   5 +-\n builtin/patch-id.c                            |   5 +-\n builtin/pull.c                                |   5 +-\n builtin/push.c                                |   5 +-\n builtin/read-tree.c                           |   5 +-\n builtin/rebase.c                              |   5 +-\n builtin/receive-pack.c                        |   5 +-\n builtin/reflog.c                              |   7 +-\n builtin/remote.c                              |   7 +-\n builtin/repack.c                              |   5 +-\n builtin/reset.c                               |   5 +-\n builtin/send-pack.c                           |   5 +-\n builtin/show-branch.c                         |   5 +-\n builtin/stash.c                               |   5 +-\n builtin/submodule--helper.c                   |   1 +\n builtin/tag.c                                 |   5 +-\n builtin/var.c                                 |   5 +-\n builtin/worktree.c                            |   5 +-\n bundle-uri.c                                  |   8 +-\n compat/mingw.c                                |   3 +-\n compat/mingw.h                                |   4 +-\n config.c                                      |  38 +++--\n config.h                                      |  41 +++--\n connect.c                                     |   4 +-\n .../coccinelle/config_fn_ctx.pending.cocci    | 144 ++++++++++++++++++\n convert.c                                     |   4 +-\n credential.c                                  |   1 +\n delta-islands.c                               |   4 +-\n diff.c                                        |  10 +-\n diff.h                                        |   7 +-\n fetch-pack.c                                  |   5 +-\n fmt-merge-msg.c                               |   5 +-\n fmt-merge-msg.h                               |   3 +-\n fsck.c                                        |   9 +-\n fsck.h                                        |   4 +-\n git-compat-util.h                             |   2 +\n gpg-interface.c                               |   7 +-\n grep.c                                        |   7 +-\n grep.h                                        |   4 +-\n help.c                                        |   7 +-\n http.c                                        |   5 +-\n ident.c                                       |   4 +-\n ident.h                                       |   4 +-\n imap-send.c                                   |   5 +-\n ll-merge.c                                    |   1 +\n ls-refs.c                                     |   1 +\n mailinfo.c                                    |   5 +-\n notes-utils.c                                 |   4 +-\n notes.c                                       |   4 +-\n pager.c                                       |   5 +-\n pretty.c                                      |   1 +\n promisor-remote.c                             |   4 +-\n remote.c                                      |   3 +-\n revision.c                                    |   4 +-\n scalar.c                                      |   4 +-\n sequencer.c                                   |   9 +-\n setup.c                                       |  16 +-\n submodule-config.c                            |  17 ++-\n t/helper/test-config.c                        |  11 +-\n t/helper/test-userdiff.c                      |   4 +-\n trace2/tr2_cfg.c                              |   9 +-\n trace2/tr2_sysenv.c                           |   3 +-\n trailer.c                                     |   2 +\n upload-pack.c                                 |   8 +-\n urlmatch.c                                    |   7 +-\n urlmatch.h                                    |   3 +-\n xdiff-interface.c                             |   5 +-\n xdiff-interface.h                             |   4 +-\n 91 files changed, 515 insertions(+), 181 deletions(-)\n create mode 100644 contrib/coccinelle/config_fn_ctx.pending.cocci\n\ndiff --git a/alias.c b/alias.c\nindex 54a1a23d2cf..910dd252a01 100644\n--- a/alias.c\n+++ b/alias.c\n@@ -12,7 +12,8 @@ struct config_alias_data {\n \tstruct string_list *list;\n };\n \n-static int config_alias_cb(const char *key, const char *value, void *d)\n+static int config_alias_cb(const char *key, const char *value,\n+\t\t\t   const struct config_context *ctx UNUSED, void *d)\n {\n \tstruct config_alias_data *data = d;\n \tconst char *p;\ndiff --git a/archive-tar.c b/archive-tar.c\nindex 4cd81d8161e..ef06e516b1f 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -411,7 +411,8 @@ static int tar_filter_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-static int git_tar_config(const char *var, const char *value, void *cb)\n+static int git_tar_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n {\n \tif (!strcmp(var, \"tar.umask\")) {\n \t\tif (value && !strcmp(value, \"user\")) {\ndiff --git a/archive-zip.c b/archive-zip.c\nindex d0d065a312e..b6811951955 100644\n--- a/archive-zip.c\n+++ b/archive-zip.c\n@@ -617,6 +617,7 @@ static void dos_time(timestamp_t *timestamp, int *dos_date, int *dos_time)\n }\n \n static int archive_zip_config(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx UNUSED,\n \t\t\t      void *data UNUSED)\n {\n \treturn userdiff_config(var, value);\ndiff --git a/builtin/add.c b/builtin/add.c\nindex e01efdfc50d..1009ae13c13 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -357,7 +357,8 @@ static struct option builtin_add_options[] = {\n \tOPT_END(),\n };\n \n-static int add_config(const char *var, const char *value, void *cb)\n+static int add_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"add.ignoreerrors\") ||\n \t    !strcmp(var, \"add.ignore-errors\")) {\n@@ -368,7 +369,7 @@ static int add_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static const char embedded_advice[] = N_(\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 2df6039a6e0..d0970a1ab13 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -694,7 +694,8 @@ static const char *add_prefix(const char *prefix, const char *path)\n \treturn prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n }\n \n-static int git_blame_config(const char *var, const char *value, void *cb)\n+static int git_blame_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"blame.showroot\")) {\n \t\tshow_root = git_config_bool(var, value);\n@@ -767,7 +768,7 @@ static int git_blame_config(const char *var, const char *value, void *cb)\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int blame_copy_callback(const struct option *option, const char *arg, int unset)\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 8337b9e71bb..af6d2e75fb0 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -83,7 +83,8 @@ static unsigned int colopts;\n \n define_list_config_array(color_branch_slots);\n \n-static int git_branch_config(const char *var, const char *value, void *cb)\n+static int git_branch_config(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -120,7 +121,7 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static const char *branch_get_color(enum color_branch ix)\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 7ff56d5a781..b3c41b54c8d 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -873,12 +873,13 @@ static int batch_objects(struct batch_options *opt)\n \treturn retval;\n }\n \n-static int git_cat_file_config(const char *var, const char *value, void *cb)\n+static int git_cat_file_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int batch_option_callback(const struct option *opt,\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 715eeb5048f..4e1f7dc26c2 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1186,7 +1186,8 @@ static int switch_branches(const struct checkout_opts *opts,\n \treturn ret || writeout_error;\n }\n \n-static int git_checkout_config(const char *var, const char *value, void *cb)\n+static int git_checkout_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tstruct checkout_opts *opts = cb;\n \n@@ -1202,7 +1203,7 @@ static int git_checkout_config(const char *var, const char *value, void *cb)\n \tif (starts_with(var, \"submodule.\"))\n \t\treturn git_default_submodule_config(var, value, NULL);\n \n-\treturn git_xmerge_config(var, value, NULL);\n+\treturn git_xmerge_config(var, value, ctx, NULL);\n }\n \n static void setup_new_branch_info_and_source_tree(\n@@ -1689,8 +1690,13 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t}\n \n \tif (opts->conflict_style) {\n+\t\tstruct key_value_info kvi = KVI_INIT;\n+\t\tstruct config_context ctx = {\n+\t\t\t.kvi = &kvi,\n+\t\t};\n \t\topts->merge = 1; /* implied */\n-\t\tgit_xmerge_config(\"merge.conflictstyle\", opts->conflict_style, NULL);\n+\t\tgit_xmerge_config(\"merge.conflictstyle\", opts->conflict_style,\n+\t\t\t\t  &ctx, NULL);\n \t}\n \tif (opts->force) {\n \t\topts->discard_changes = 1;\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 57e7f7cac64..5eff1b802a7 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -103,7 +103,8 @@ struct menu_stuff {\n \n define_list_config_array(color_interactive_slots);\n \n-static int git_clean_config(const char *var, const char *value, void *cb)\n+static int git_clean_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -133,7 +134,7 @@ static int git_clean_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static const char *clean_get_color(enum color_clean ix)\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 15f9912b4ca..c0f6e067493 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -790,7 +790,8 @@ static int checkout(int submodule_progress, int filter_submodules)\n \treturn err;\n }\n \n-static int git_clone_config(const char *k, const char *v, void *cb)\n+static int git_clone_config(const char *k, const char *v,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(k, \"clone.defaultremotename\")) {\n \t\tfree(remote_name);\n@@ -801,17 +802,19 @@ static int git_clone_config(const char *k, const char *v, void *cb)\n \tif (!strcmp(k, \"clone.filtersubmodules\"))\n \t\tconfig_filter_submodules = git_config_bool(k, v);\n \n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n-static int write_one_config(const char *key, const char *value, void *data)\n+static int write_one_config(const char *key, const char *value,\n+\t\t\t    const struct config_context *ctx,\n+\t\t\t    void *data)\n {\n \t/*\n \t * give git_clone_config a chance to write config values back to the\n \t * environment, since git_config_set_multivar_gently only deals with\n \t * config-file writes\n \t */\n-\tint apply_failed = git_clone_config(key, value, data);\n+\tint apply_failed = git_clone_config(key, value, ctx, data);\n \tif (apply_failed)\n \t\treturn apply_failed;\n \ndiff --git a/builtin/column.c b/builtin/column.c\nindex de623a16c2d..4a6148ca479 100644\n--- a/builtin/column.c\n+++ b/builtin/column.c\n@@ -13,7 +13,8 @@ static const char * const builtin_column_usage[] = {\n };\n static unsigned int colopts;\n \n-static int column_config(const char *var, const char *value, void *cb)\n+static int column_config(const char *var, const char *value,\n+\t\t\t const struct config_context *ctx UNUSED, void *cb)\n {\n \treturn git_column_config(var, value, cb, &colopts);\n }\ndiff --git a/builtin/commit-graph.c b/builtin/commit-graph.c\nindex dd732b35348..1185c49239a 100644\n--- a/builtin/commit-graph.c\n+++ b/builtin/commit-graph.c\n@@ -186,6 +186,7 @@ static int write_option_max_new_filters(const struct option *opt,\n }\n \n static int git_commit_graph_write_config(const char *var, const char *value,\n+\t\t\t\t\t const struct config_context *ctx UNUSED,\n \t\t\t\t\t void *cb UNUSED)\n {\n \tif (!strcmp(var, \"commitgraph.maxnewfilters\"))\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 9ab57ea1aaa..6a2b2503328 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1405,7 +1405,8 @@ static int parse_status_slot(const char *slot)\n \treturn LOOKUP_CONFIG(color_status_slots, slot);\n }\n \n-static int git_status_config(const char *k, const char *v, void *cb)\n+static int git_status_config(const char *k, const char *v,\n+\t\t\t     const struct config_context *ctx, void *cb)\n {\n \tstruct wt_status *s = cb;\n \tconst char *slot_name;\n@@ -1490,7 +1491,7 @@ static int git_status_config(const char *k, const char *v, void *cb)\n \t\ts->detect_rename = git_config_rename(k, v);\n \t\treturn 0;\n \t}\n-\treturn git_diff_ui_config(k, v, NULL);\n+\treturn git_diff_ui_config(k, v, ctx, NULL);\n }\n \n int cmd_status(int argc, const char **argv, const char *prefix)\n@@ -1605,7 +1606,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)\n \treturn 0;\n }\n \n-static int git_commit_config(const char *k, const char *v, void *cb)\n+static int git_commit_config(const char *k, const char *v,\n+\t\t\t     const struct config_context *ctx, void *cb)\n {\n \tstruct wt_status *s = cb;\n \n@@ -1627,7 +1629,7 @@ static int git_commit_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_status_config(k, v, s);\n+\treturn git_status_config(k, v, ctx, s);\n }\n \n int cmd_commit(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/config.c b/builtin/config.c\nindex d40fddb042a..f4fccf99cb8 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -217,6 +217,7 @@ static void show_config_scope(struct strbuf *buf)\n }\n \n static int show_all_config(const char *key_, const char *value_,\n+\t\t\t   const struct config_context *ctx UNUSED,\n \t\t\t   void *cb UNUSED)\n {\n \tif (show_origin || show_scope) {\n@@ -301,7 +302,8 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value\n \treturn 0;\n }\n \n-static int collect_config(const char *key_, const char *value_, void *cb)\n+static int collect_config(const char *key_, const char *value_,\n+\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n {\n \tstruct strbuf_list *values = cb;\n \n@@ -470,6 +472,7 @@ static const char *get_colorbool_slot;\n static char parsed_color[COLOR_MAXLEN];\n \n static int git_get_color_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n \t\t\t\tvoid *cb UNUSED)\n {\n \tif (!strcmp(var, get_color_slot)) {\n@@ -503,6 +506,7 @@ static int get_colorbool_found;\n static int get_diff_color_found;\n static int get_color_ui_found;\n static int git_get_colorbool_config(const char *var, const char *value,\n+\t\t\t\t    const struct config_context *ctx UNUSED,\n \t\t\t\t    void *data UNUSED)\n {\n \tif (!strcmp(var, get_colorbool_slot))\n@@ -561,7 +565,9 @@ struct urlmatch_current_candidate_value {\n \tstruct strbuf value;\n };\n \n-static int urlmatch_collect_fn(const char *var, const char *value, void *cb)\n+static int urlmatch_collect_fn(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       void *cb)\n {\n \tstruct string_list *values = cb;\n \tstruct string_list_item *item = string_list_insert(values, var);\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 0049342f5c0..f289530068b 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -40,14 +40,15 @@ static const char *const builtin_difftool_usage[] = {\n \tNULL\n };\n \n-static int difftool_config(const char *var, const char *value, void *cb)\n+static int difftool_config(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"difftool.trustexitcode\")) {\n \t\ttrust_exit_code = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int print_tool_help(void)\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex e3871048cf6..a4aa0fbb8f5 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -110,7 +110,8 @@ struct fetch_config {\n \tint submodule_fetch_jobs;\n };\n \n-static int git_fetch_config(const char *k, const char *v, void *cb)\n+static int git_fetch_config(const char *k, const char *v,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tstruct fetch_config *fetch_config = cb;\n \n@@ -164,7 +165,7 @@ static int git_fetch_config(const char *k, const char *v, void *cb)\n \t\t\t    \"fetch.output\", v);\n \t}\n \n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n static int parse_refmap_arg(const struct option *opt, const char *arg, int unset)\n@@ -1799,7 +1800,9 @@ struct remote_group_data {\n \tstruct string_list *list;\n };\n \n-static int get_remote_group(const char *key, const char *value, void *priv)\n+static int get_remote_group(const char *key, const char *value,\n+\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    void *priv)\n {\n \tstruct remote_group_data *g = priv;\n \ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex f6dd9a784c1..91a776e2f17 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -37,7 +37,8 @@ static int fsmonitor__start_timeout_sec = 60;\n #define FSMONITOR__ANNOUNCE_STARTUP \"fsmonitor.announcestartup\"\n static int fsmonitor__announce_startup = 0;\n \n-static int fsmonitor_config(const char *var, const char *value, void *cb)\n+static int fsmonitor_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, FSMONITOR__IPC_THREADS)) {\n \t\tint i = git_config_int(var, value);\n@@ -67,7 +68,7 @@ static int fsmonitor_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n /*\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 76cf999d310..757d52b94ec 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -290,13 +290,14 @@ static int wait_all(void)\n \treturn hit;\n }\n \n-static int grep_cmd_config(const char *var, const char *value, void *cb)\n+static int grep_cmd_config(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n-\tint st = grep_config(var, value, cb);\n+\tint st = grep_config(var, value, ctx, cb);\n \n \tif (git_color_config(var, value, cb) < 0)\n \t\tst = -1;\n-\telse if (git_default_config(var, value, cb) < 0)\n+\telse if (git_default_config(var, value, ctx, cb) < 0)\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\ndiff --git a/builtin/help.c b/builtin/help.c\nindex d3cf4af3f6e..c348f201254 100644\n--- a/builtin/help.c\n+++ b/builtin/help.c\n@@ -398,7 +398,8 @@ static int add_man_viewer_info(const char *var, const char *value)\n \treturn 0;\n }\n \n-static int git_help_config(const char *var, const char *value, void *cb)\n+static int git_help_config(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"help.format\")) {\n \t\tif (!value)\n@@ -421,7 +422,7 @@ static int git_help_config(const char *var, const char *value, void *cb)\n \tif (starts_with(var, \"man.\"))\n \t\treturn add_man_viewer_info(var, value);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static struct cmdnames main_cmds, other_cmds;\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex d0d8067510b..de8884ea5c2 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1581,7 +1581,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name,\n \tstrbuf_release(&pack_name);\n }\n \n-static int git_index_pack_config(const char *k, const char *v, void *cb)\n+static int git_index_pack_config(const char *k, const char *v,\n+\t\t\t\t const struct config_context *ctx, void *cb)\n {\n \tstruct pack_idx_option *opts = cb;\n \n@@ -1608,7 +1609,7 @@ static int git_index_pack_config(const char *k, const char *v, void *cb)\n \t\telse\n \t\t\topts->flags &= ~WRITE_REV;\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n static int cmp_uint32(const void *a_, const void *b_)\ndiff --git a/builtin/log.c b/builtin/log.c\nindex c85f13a5d5e..09d6a13075b 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -564,7 +564,8 @@ static int cmd_log_walk(struct rev_info *rev)\n \treturn retval;\n }\n \n-static int git_log_config(const char *var, const char *value, void *cb)\n+static int git_log_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -613,7 +614,7 @@ static int git_log_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_diff_ui_config(var, value, cb);\n+\treturn git_diff_ui_config(var, value, ctx, cb);\n }\n \n int cmd_whatchanged(int argc, const char **argv, const char *prefix)\n@@ -979,7 +980,8 @@ static enum cover_from_description parse_cover_from_description(const char *arg)\n \t\tdie(_(\"%s: invalid cover from description mode\"), arg);\n }\n \n-static int git_format_config(const char *var, const char *value, void *cb)\n+static int git_format_config(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"format.headers\")) {\n \t\tif (!value)\n@@ -1108,7 +1110,7 @@ static int git_format_config(const char *var, const char *value, void *cb)\n \tif (!strcmp(var, \"diff.noprefix\"))\n \t\treturn 0;\n \n-\treturn git_log_config(var, value, cb);\n+\treturn git_log_config(var, value, ctx, cb);\n }\n \n static const char *output_directory = NULL;\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 8da3e46abb0..cad624fb797 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -623,7 +623,8 @@ static void parse_branch_merge_options(char *bmo)\n \tfree(argv);\n }\n \n-static int git_merge_config(const char *k, const char *v, void *cb)\n+static int git_merge_config(const char *k, const char *v,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tint status;\n \tconst char *str;\n@@ -668,10 +669,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\tstatus = fmt_merge_msg_config(k, v, cb);\n+\tstatus = fmt_merge_msg_config(k, v, ctx, cb);\n \tif (status)\n \t\treturn status;\n-\treturn git_diff_ui_config(k, v, cb);\n+\treturn git_diff_ui_config(k, v, ctx, cb);\n }\n \n static int read_tree_trivial(struct object_id *common, struct object_id *head,\ndiff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\nindex 1b5083f8b26..a0a7b82cc69 100644\n--- a/builtin/multi-pack-index.c\n+++ b/builtin/multi-pack-index.c\n@@ -82,6 +82,7 @@ static struct option *add_common_options(struct option *prev)\n }\n \n static int git_multi_pack_index_write_config(const char *var, const char *value,\n+\t\t\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t\t\t     void *cb UNUSED)\n {\n \tif (!strcmp(var, \"pack.writebitmaphashcache\")) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 3af2d84f589..34aa0b483a0 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3135,7 +3135,8 @@ static void prepare_pack(int window, int depth)\n \tfree(delta_list);\n }\n \n-static int git_pack_config(const char *k, const char *v, void *cb)\n+static int git_pack_config(const char *k, const char *v,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(k, \"pack.window\")) {\n \t\twindow = git_config_int(k, v);\n@@ -3227,7 +3228,7 @@ static int git_pack_config(const char *k, const char *v, void *cb)\n \t\tex->uri = xstrdup(pack_end + 1);\n \t\toidmap_put(&configured_exclusions, ex);\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n /* Counters for trace2 output when in --stdin-packs mode. */\ndiff --git a/builtin/patch-id.c b/builtin/patch-id.c\nindex 9d5585d3a72..03eddd0fb82 100644\n--- a/builtin/patch-id.c\n+++ b/builtin/patch-id.c\n@@ -196,7 +196,8 @@ struct patch_id_opts {\n \tint verbatim;\n };\n \n-static int git_patch_id_config(const char *var, const char *value, void *cb)\n+static int git_patch_id_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tstruct patch_id_opts *opts = cb;\n \n@@ -209,7 +210,7 @@ static int git_patch_id_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n int cmd_patch_id(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 0c7bac97b75..83fca5b1d46 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -361,7 +361,8 @@ static enum rebase_type config_get_rebase(int *rebase_unspecified)\n /**\n  * Read config variables.\n  */\n-static int git_pull_config(const char *var, const char *value, void *cb)\n+static int git_pull_config(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"rebase.autostash\")) {\n \t\tconfig_autostash = git_config_bool(var, value);\n@@ -374,7 +375,7 @@ static int git_pull_config(const char *var, const char *value, void *cb)\n \t\tcheck_trust_level = 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n /**\ndiff --git a/builtin/push.c b/builtin/push.c\nindex dbdf609daf3..a2f68f77324 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -510,7 +510,8 @@ static void set_push_cert_flags(int *flags, int v)\n }\n \n \n-static int git_push_config(const char *k, const char *v, void *cb)\n+static int git_push_config(const char *k, const char *v,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tconst char *slot_name;\n \tint *flags = cb;\n@@ -577,7 +578,7 @@ static int git_push_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(k, v, NULL);\n+\treturn git_default_config(k, v, ctx, NULL);\n }\n \n int cmd_push(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/read-tree.c b/builtin/read-tree.c\nindex 440f19b1b87..8877dd6d4b5 100644\n--- a/builtin/read-tree.c\n+++ b/builtin/read-tree.c\n@@ -102,12 +102,13 @@ static int debug_merge(const struct cache_entry * const *stages,\n \treturn 0;\n }\n \n-static int git_read_tree_config(const char *var, const char *value, void *cb)\n+static int git_read_tree_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"submodule.recurse\"))\n \t\treturn git_default_submodule_config(var, value, cb);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ace1d5e8d11..60930e2d8e0 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -772,7 +772,8 @@ static void parse_rebase_merges_value(struct rebase_options *options, const char\n \t\tdie(_(\"Unknown rebase-merges mode: %s\"), value);\n }\n \n-static int rebase_config(const char *var, const char *value, void *data)\n+static int rebase_config(const char *var, const char *value,\n+\t\t\t const struct config_context *ctx, void *data)\n {\n \tstruct rebase_options *opts = data;\n \n@@ -831,7 +832,7 @@ static int rebase_config(const char *var, const char *value, void *data)\n \t\treturn git_config_string(&opts->default_backend, var, value);\n \t}\n \n-\treturn git_default_config(var, value, data);\n+\treturn git_default_config(var, value, ctx, data);\n }\n \n static int checkout_up_to_date(struct rebase_options *options)\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 1a31a583674..94d9898aff7 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -139,7 +139,8 @@ static enum deny_action parse_deny_action(const char *var, const char *value)\n \treturn DENY_IGNORE;\n }\n \n-static int receive_pack_config(const char *var, const char *value, void *cb)\n+static int receive_pack_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tint status = parse_hide_refs_config(var, value, \"receive\", &hidden_refs);\n \n@@ -266,7 +267,7 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static void show_ref(const char *path, const struct object_id *oid)\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex a1fa0c855f4..84251cc9517 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -108,7 +108,8 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)\n #define EXPIRE_TOTAL   01\n #define EXPIRE_UNREACH 02\n \n-static int reflog_expire_config(const char *var, const char *value, void *cb)\n+static int reflog_expire_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx, void *cb)\n {\n \tconst char *pattern, *key;\n \tsize_t pattern_len;\n@@ -117,7 +118,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)\n \tstruct reflog_expire_cfg *ent;\n \n \tif (parse_config_key(var, \"gc\", &pattern, &pattern_len, &key) < 0)\n-\t\treturn git_default_config(var, value, cb);\n+\t\treturn git_default_config(var, value, ctx, cb);\n \n \tif (!strcmp(key, \"reflogexpire\")) {\n \t\tslot = EXPIRE_TOTAL;\n@@ -128,7 +129,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)\n \t\tif (git_config_expiry_date(&expire, var, value))\n \t\t\treturn -1;\n \t} else\n-\t\treturn git_default_config(var, value, cb);\n+\t\treturn git_default_config(var, value, ctx, cb);\n \n \tif (!pattern) {\n \t\tswitch (slot) {\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 1e0b137d977..87de81105e2 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -268,6 +268,7 @@ static const char *abbrev_ref(const char *name, const char *prefix)\n #define abbrev_branch(name) abbrev_ref((name), \"refs/heads/\")\n \n static int config_read_branches(const char *key, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n \t\t\t\tvoid *data UNUSED)\n {\n \tconst char *orig_key = key;\n@@ -645,7 +646,7 @@ struct push_default_info\n };\n \n static int config_read_push_default(const char *key, const char *value,\n-\tvoid *cb)\n+\tconst struct config_context *ctx UNUSED, void *cb)\n {\n \tstruct push_default_info* info = cb;\n \tif (strcmp(key, \"remote.pushdefault\") ||\n@@ -1494,7 +1495,9 @@ static int prune(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n-static int get_remote_default(const char *key, const char *value UNUSED, void *priv)\n+static int get_remote_default(const char *key, const char *value UNUSED,\n+\t\t\t      const struct config_context *ctx UNUSED,\n+\t\t\t      void *priv)\n {\n \tif (strcmp(key, \"remotes.default\") == 0) {\n \t\tint *found = priv;\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 0541c3ce157..6f74570bf94 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -59,7 +59,8 @@ struct pack_objects_args {\n \tint local;\n };\n \n-static int repack_config(const char *var, const char *value, void *cb)\n+static int repack_config(const char *var, const char *value,\n+\t\t\t const struct config_context *ctx, void *cb)\n {\n \tstruct pack_objects_args *cruft_po_args = cb;\n \tif (!strcmp(var, \"repack.usedeltabaseoffset\")) {\n@@ -91,7 +92,7 @@ static int repack_config(const char *var, const char *value, void *cb)\n \t\treturn git_config_string(&cruft_po_args->depth, var, value);\n \tif (!strcmp(var, \"repack.cruftthreads\"))\n \t\treturn git_config_string(&cruft_po_args->threads, var, value);\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n /*\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex f99f32d5802..1ae82f2e89c 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -312,12 +312,13 @@ static int reset_refs(const char *rev, const struct object_id *oid)\n \treturn update_ref_status;\n }\n \n-static int git_reset_config(const char *var, const char *value, void *cb)\n+static int git_reset_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"submodule.recurse\"))\n \t\treturn git_default_submodule_config(var, value, cb);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n int cmd_reset(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 4784143004d..cd6d9e41129 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -131,7 +131,8 @@ static void print_helper_status(struct ref *ref)\n \tstrbuf_release(&buf);\n }\n \n-static int send_pack_config(const char *k, const char *v, void *cb)\n+static int send_pack_config(const char *k, const char *v,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(k, \"push.gpgsign\")) {\n \t\tconst char *value;\n@@ -151,7 +152,7 @@ static int send_pack_config(const char *k, const char *v, void *cb)\n \t\t\t}\n \t\t}\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n int cmd_send_pack(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex a2461270d4b..f2fd245b838 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -559,7 +559,8 @@ static void append_one_rev(const char *av)\n \tdie(\"bad sha1 reference %s\", av);\n }\n \n-static int git_show_branch_config(const char *var, const char *value, void *cb)\n+static int git_show_branch_config(const char *var, const char *value,\n+\t\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"showbranch.default\")) {\n \t\tif (!value)\n@@ -582,7 +583,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int omit_in_dense(struct commit *commit, struct commit **rev, int n)\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex a7e17ffe384..e5c4246d2d4 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -837,7 +837,8 @@ static int show_stat = 1;\n static int show_patch;\n static int show_include_untracked;\n \n-static int git_stash_config(const char *var, const char *value, void *cb)\n+static int git_stash_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"stash.showstat\")) {\n \t\tshow_stat = git_config_bool(var, value);\n@@ -851,7 +852,7 @@ static int git_stash_config(const char *var, const char *value, void *cb)\n \t\tshow_include_untracked = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\treturn git_diff_basic_config(var, value, cb);\n+\treturn git_diff_basic_config(var, value, ctx, cb);\n }\n \n static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 6a16208e8a8..f8e9d85e77a 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2192,6 +2192,7 @@ static int update_clone_task_finished(int result,\n }\n \n static int git_update_clone_config(const char *var, const char *value,\n+\t\t\t\t   const struct config_context *ctx UNUSED,\n \t\t\t\t   void *cb)\n {\n \tint *max_jobs = cb;\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 1acf5f7a59f..b7dfb5e2cad 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -188,7 +188,8 @@ static const char tag_template_nocleanup[] =\n \t\"Lines starting with '%c' will be kept; you may remove them\"\n \t\" yourself if you want to.\\n\");\n \n-static int git_tag_config(const char *var, const char *value, void *cb)\n+static int git_tag_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"tag.gpgsign\")) {\n \t\tconfig_sign_tag = git_config_bool(var, value);\n@@ -213,7 +214,7 @@ static int git_tag_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static void write_tag_body(int fd, const struct object_id *oid)\ndiff --git a/builtin/var.c b/builtin/var.c\nindex 21499989807..ae011bdf409 100644\n--- a/builtin/var.c\n+++ b/builtin/var.c\n@@ -71,13 +71,14 @@ static const struct git_var *get_git_var(const char *var)\n \treturn NULL;\n }\n \n-static int show_config(const char *var, const char *value, void *cb)\n+static int show_config(const char *var, const char *value,\n+\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (value)\n \t\tprintf(\"%s=%s\\n\", var, value);\n \telse\n \t\tprintf(\"%s\\n\", var);\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n int cmd_var(int argc, const char **argv, const char *prefix UNUSED)\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 5a9cf076ad2..a30d37a273f 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -128,14 +128,15 @@ static int verbose;\n static int guess_remote;\n static timestamp_t expire;\n \n-static int git_worktree_config(const char *var, const char *value, void *cb)\n+static int git_worktree_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"worktree.guessremote\")) {\n \t\tguess_remote = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int delete_git_dir(const char *id)\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex 2a2db1a1d39..0d5acc3dc51 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -224,7 +224,9 @@ static int bundle_list_update(const char *key, const char *value,\n \treturn 0;\n }\n \n-static int config_to_bundle_list(const char *key, const char *value, void *data)\n+static int config_to_bundle_list(const char *key, const char *value,\n+\t\t\t\t const struct config_context *ctx UNUSED,\n+\t\t\t\t void *data)\n {\n \tstruct bundle_list *list = data;\n \treturn bundle_list_update(key, value, list);\n@@ -871,7 +873,9 @@ cached:\n \treturn advertise_bundle_uri;\n }\n \n-static int config_to_packet_line(const char *key, const char *value, void *data)\n+static int config_to_packet_line(const char *key, const char *value,\n+\t\t\t\t const struct config_context *ctx UNUSED,\n+\t\t\t\t void *data)\n {\n \tstruct packet_reader *writer = data;\n \ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex d06cdc6254f..4186bc7a417 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -244,7 +244,8 @@ static int core_restrict_inherited_handles = -1;\n static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;\n static char *unset_environment_variables;\n \n-int mingw_core_config(const char *var, const char *value, void *cb)\n+int mingw_core_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"core.hidedotfiles\")) {\n \t\tif (value && !strcasecmp(value, \"dotgitonly\"))\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 209cf7cebad..5e34c873473 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -11,7 +11,9 @@ typedef _sigset_t sigset_t;\n #undef _POSIX_THREAD_SAFE_FUNCTIONS\n #endif\n \n-int mingw_core_config(const char *var, const char *value, void *cb);\n+struct config_context;\n+int mingw_core_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb);\n #define platform_core_config mingw_core_config\n \n /*\ndiff --git a/config.c b/config.c\nindex 2eeb6621421..850e432e301 100644\n--- a/config.c\n+++ b/config.c\n@@ -209,7 +209,8 @@ struct config_include_data {\n };\n #define CONFIG_INCLUDE_INIT { 0 }\n \n-static int git_config_include(const char *var, const char *value, void *data);\n+static int git_config_include(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx, void *data);\n \n #define MAX_INCLUDE_DEPTH 10\n static const char include_depth_advice[] = N_(\n@@ -388,7 +389,8 @@ static int include_by_branch(const char *cond, size_t cond_len)\n \treturn ret;\n }\n \n-static int add_remote_url(const char *var, const char *value, void *data)\n+static int add_remote_url(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx UNUSED, void *data)\n {\n \tstruct string_list *remote_urls = data;\n \tconst char *remote_name;\n@@ -423,6 +425,7 @@ static void populate_remote_urls(struct config_include_data *inc)\n }\n \n static int forbid_remote_url(const char *var, const char *value UNUSED,\n+\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t     void *data UNUSED)\n {\n \tconst char *remote_name;\n@@ -486,7 +489,9 @@ static int include_condition_is_true(struct config_source *cs,\n \treturn 0;\n }\n \n-static int git_config_include(const char *var, const char *value, void *data)\n+static int git_config_include(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx,\n+\t\t\t      void *data)\n {\n \tstruct config_include_data *inc = data;\n \tstruct config_source *cs = inc->config_reader->source;\n@@ -498,7 +503,7 @@ static int git_config_include(const char *var, const char *value, void *data)\n \t * Pass along all values, including \"include\" directives; this makes it\n \t * possible to query information on the includes themselves.\n \t */\n-\tret = inc->fn(var, value, inc->data);\n+\tret = inc->fn(var, value, NULL, inc->data);\n \tif (ret < 0)\n \t\treturn ret;\n \n@@ -680,7 +685,7 @@ static int config_parse_pair(const char *key, const char *value,\n \tif (git_config_parse_key(key, &canonical_name, NULL))\n \t\treturn -1;\n \n-\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n+\tret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;\n \tfree(canonical_name);\n \treturn ret;\n }\n@@ -968,7 +973,7 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \t * accurate line number in error messages.\n \t */\n \tcs->linenr--;\n-\tret = fn(name->buf, value, data);\n+\tret = fn(name->buf, value, NULL, data);\n \tif (ret >= 0)\n \t\tcs->linenr++;\n \treturn ret;\n@@ -1562,7 +1567,8 @@ int git_config_color(char *dest, const char *var, const char *value)\n \treturn 0;\n }\n \n-static int git_default_core_config(const char *var, const char *value, void *cb)\n+static int git_default_core_config(const char *var, const char *value,\n+\t\t\t\t   const struct config_context *ctx, void *cb)\n {\n \t/* This needs a better name */\n \tif (!strcmp(var, \"core.filemode\")) {\n@@ -1842,7 +1848,7 @@ static int git_default_core_config(const char *var, const char *value, void *cb)\n \t}\n \n \t/* Add other config variables here and to Documentation/config.txt. */\n-\treturn platform_core_config(var, value, cb);\n+\treturn platform_core_config(var, value, ctx, cb);\n }\n \n static int git_default_sparse_config(const char *var, const char *value)\n@@ -1944,15 +1950,16 @@ static int git_default_mailmap_config(const char *var, const char *value)\n \treturn 0;\n }\n \n-int git_default_config(const char *var, const char *value, void *cb)\n+int git_default_config(const char *var, const char *value,\n+\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (starts_with(var, \"core.\"))\n-\t\treturn git_default_core_config(var, value, cb);\n+\t\treturn git_default_core_config(var, value, ctx, cb);\n \n \tif (starts_with(var, \"user.\") ||\n \t    starts_with(var, \"author.\") ||\n \t    starts_with(var, \"committer.\"))\n-\t\treturn git_ident_config(var, value, cb);\n+\t\treturn git_ident_config(var, value, ctx, cb);\n \n \tif (starts_with(var, \"i18n.\"))\n \t\treturn git_default_i18n_config(var, value);\n@@ -2318,7 +2325,7 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \n \t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n \n-\t\tif (fn(entry->key, values->items[value_index].string, data) < 0)\n+\t\tif (fn(entry->key, values->items[value_index].string, NULL, data) < 0)\n \t\t\tgit_die_config_linenr(entry->key,\n \t\t\t\t\t      reader->config_kvi->filename,\n \t\t\t\t\t      reader->config_kvi->linenr);\n@@ -2496,7 +2503,9 @@ struct configset_add_data {\n };\n #define CONFIGSET_ADD_INIT { 0 }\n \n-static int config_set_callback(const char *key, const char *value, void *cb)\n+static int config_set_callback(const char *key, const char *value,\n+\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       void *cb)\n {\n \tstruct configset_add_data *data = cb;\n \tconfigset_add_value(data->config_reader, data->config_set, key, value);\n@@ -3106,7 +3115,8 @@ static int store_aux_event(enum config_event_t type,\n \treturn 0;\n }\n \n-static int store_aux(const char *key, const char *value, void *cb)\n+static int store_aux(const char *key, const char *value,\n+\t\t     const struct config_context *ctx UNUSED, void *cb)\n {\n \tstruct config_store_data *store = cb;\n \ndiff --git a/config.h b/config.h\nindex d1c5577589e..cd30125a8a4 100644\n--- a/config.h\n+++ b/config.h\n@@ -110,8 +110,29 @@ struct config_options {\n \t} error_action;\n };\n \n+/* Config source metadata for a given config key-value pair */\n+struct key_value_info {\n+\tconst char *filename;\n+\tint linenr;\n+\tenum config_origin_type origin_type;\n+\tenum config_scope scope;\n+};\n+#define KVI_INIT { \\\n+\t.filename = NULL, \\\n+\t.linenr = -1, \\\n+\t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n+\t.scope = CONFIG_SCOPE_UNKNOWN, \\\n+}\n+\n+/* Captures additional information that a config callback can use. */\n+struct config_context {\n+\t/* Config source metadata for key and value. */\n+\tconst struct key_value_info *kvi;\n+};\n+#define CONFIG_CONTEXT_INIT { 0 }\n+\n /**\n- * A config callback function takes three parameters:\n+ * A config callback function takes four parameters:\n  *\n  * - the name of the parsed variable. This is in canonical \"flat\" form: the\n  *   section, subsection, and variable segments will be separated by dots,\n@@ -122,15 +143,22 @@ struct config_options {\n  *   value specified, the value will be NULL (typically this means it\n  *   should be interpreted as boolean true).\n  *\n+ * - the 'config context', that is, additional information about the config\n+ *   iteration operation provided by the config machinery. For example, this\n+ *   includes information about the config source being parsed (e.g. the\n+ *   filename).\n+ *\n  * - a void pointer passed in by the caller of the config API; this can\n  *   contain callback-specific data\n  *\n  * A config callback should return 0 for success, or -1 if the variable\n  * could not be parsed properly.\n  */\n-typedef int (*config_fn_t)(const char *, const char *, void *);\n+typedef int (*config_fn_t)(const char *, const char *,\n+\t\t\t   const struct config_context *, void *);\n \n-int git_default_config(const char *, const char *, void *);\n+int git_default_config(const char *, const char *,\n+\t\t       const struct config_context *, void *);\n \n /**\n  * Read a specific file in git-config format.\n@@ -667,13 +695,6 @@ int git_config_get_expiry(const char *key, const char **output);\n /* parse either \"this many days\" integer, or \"5.days.ago\" approxidate */\n int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t now);\n \n-struct key_value_info {\n-\tconst char *filename;\n-\tint linenr;\n-\tenum config_origin_type origin_type;\n-\tenum config_scope scope;\n-};\n-\n /**\n  * First prints the error message specified by the caller in `err` and then\n  * dies printing the line number and the file name of the highest priority\ndiff --git a/connect.c b/connect.c\nindex 3a0186280c4..cddac1d96b8 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -964,7 +964,7 @@ static struct child_process *git_tcp_connect(int fd[2], char *host, int flags)\n static char *git_proxy_command;\n \n static int git_proxy_command_options(const char *var, const char *value,\n-\t\tvoid *cb)\n+\t\tconst struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"core.gitproxy\")) {\n \t\tconst char *for_pos;\n@@ -1010,7 +1010,7 @@ static int git_proxy_command_options(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int git_use_proxy(const char *host)\ndiff --git a/contrib/coccinelle/config_fn_ctx.pending.cocci b/contrib/coccinelle/config_fn_ctx.pending.cocci\nnew file mode 100644\nindex 00000000000..6d3d1000a96\n--- /dev/null\n+++ b/contrib/coccinelle/config_fn_ctx.pending.cocci\n@@ -0,0 +1,144 @@\n+@ get_fn @\n+identifier fn, R;\n+@@\n+(\n+(\n+git_config_from_file\n+|\n+git_config_from_file_with_options\n+|\n+git_config_from_mem\n+|\n+git_config_from_blob_oid\n+|\n+read_early_config\n+|\n+read_very_early_config\n+|\n+config_with_options\n+|\n+git_config\n+|\n+git_protected_config\n+|\n+config_from_gitmodules\n+)\n+  (fn, ...)\n+|\n+repo_config(R, fn, ...)\n+)\n+\n+@ extends get_fn @\n+identifier C1, C2, D;\n+@@\n+int fn(const char *C1, const char *C2,\n++ const struct config_context *ctx,\n+  void *D);\n+\n+@ extends get_fn @\n+@@\n+int fn(const char *, const char *,\n++ const struct config_context *,\n+  void *);\n+\n+@ extends get_fn @\n+// Don't change fns that look like callback fns but aren't\n+identifier fn2 != tar_filter_config && != git_diff_heuristic_config &&\n+  != git_default_submodule_config && != git_color_config &&\n+  != bundle_list_update && != parse_object_filter_config;\n+identifier C1, C2, D1, D2, S;\n+attribute name UNUSED;\n+@@\n+int fn(const char *C1, const char *C2,\n++ const struct config_context *ctx,\n+  void *D1) {\n+<+...\n+(\n+fn2(C1, C2\n++ , ctx\n+, D2);\n+|\n+if(fn2(C1, C2\n++ , ctx\n+, D2) < 0) { ... }\n+|\n+return fn2(C1, C2\n++ , ctx\n+, D2);\n+|\n+S = fn2(C1, C2\n++ , ctx\n+, D2);\n+)\n+...+>\n+  }\n+\n+@ extends get_fn@\n+identifier C1, C2, D;\n+attribute name UNUSED;\n+@@\n+int fn(const char *C1, const char *C2,\n++ const struct config_context *ctx UNUSED,\n+  void *D) {...}\n+\n+\n+// The previous rules don't catch all callbacks, especially if they're defined\n+// in a separate file from the git_config() call. Fix these manually.\n+@@\n+identifier C1, C2, D;\n+attribute name UNUSED;\n+@@\n+int\n+(\n+git_ident_config\n+|\n+urlmatch_collect_fn\n+|\n+write_one_config\n+|\n+forbid_remote_url\n+|\n+credential_config_callback\n+)\n+  (const char *C1, const char *C2,\n++ const struct config_context *ctx UNUSED,\n+  void *D) {...}\n+\n+@@\n+identifier C1, C2, D, D2, S, fn2;\n+@@\n+int\n+(\n+http_options\n+|\n+git_status_config\n+|\n+git_commit_config\n+|\n+git_default_core_config\n+|\n+grep_config\n+)\n+  (const char *C1, const char *C2,\n++ const struct config_context *ctx,\n+  void *D) {\n+<+...\n+(\n+fn2(C1, C2\n++ , ctx\n+, D2);\n+|\n+if(fn2(C1, C2\n++ , ctx\n+, D2) < 0) { ... }\n+|\n+return fn2(C1, C2\n++ , ctx\n+, D2);\n+|\n+S = fn2(C1, C2\n++ , ctx\n+, D2);\n+)\n+...+>\n+  }\ndiff --git a/convert.c b/convert.c\nindex 9ee79fe4699..8e96cf83030 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1015,7 +1015,9 @@ static int apply_filter(const char *path, const char *src, size_t len,\n \treturn 0;\n }\n \n-static int read_convert_config(const char *var, const char *value, void *cb UNUSED)\n+static int read_convert_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       void *cb UNUSED)\n {\n \tconst char *key, *name;\n \tsize_t namelen;\ndiff --git a/credential.c b/credential.c\nindex 8825c6f1320..d6647541634 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -49,6 +49,7 @@ static int credential_from_potentially_partial_url(struct credential *c,\n \t\t\t\t\t\t   const char *url);\n \n static int credential_config_callback(const char *var, const char *value,\n+\t\t\t\t      const struct config_context *ctx UNUSED,\n \t\t\t\t      void *data)\n {\n \tstruct credential *c = data;\ndiff --git a/delta-islands.c b/delta-islands.c\nindex c824a5f6a42..5fc6ea6ff55 100644\n--- a/delta-islands.c\n+++ b/delta-islands.c\n@@ -341,7 +341,9 @@ static void free_remote_islands(kh_str_t *remote_islands)\n \tkh_destroy_str(remote_islands);\n }\n \n-static int island_config_callback(const char *k, const char *v, void *cb)\n+static int island_config_callback(const char *k, const char *v,\n+\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t  void *cb)\n {\n \tstruct island_load_data *ild = cb;\n \ndiff --git a/diff.c b/diff.c\nindex c106f8a4ffa..0e382c8f7f0 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -357,7 +357,8 @@ static unsigned parse_color_moved_ws(const char *arg)\n \treturn ret;\n }\n \n-int git_diff_ui_config(const char *var, const char *value, void *cb)\n+int git_diff_ui_config(const char *var, const char *value,\n+\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"diff.color\") || !strcmp(var, \"color.diff\")) {\n \t\tdiff_use_color_default = git_config_colorbool(var, value);\n@@ -440,10 +441,11 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_diff_basic_config(var, value, cb);\n+\treturn git_diff_basic_config(var, value, ctx, cb);\n }\n \n-int git_diff_basic_config(const char *var, const char *value, void *cb)\n+int git_diff_basic_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tconst char *name;\n \n@@ -495,7 +497,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)\n \tif (git_diff_heuristic_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static char *quote_two(const char *one, const char *two)\ndiff --git a/diff.h b/diff.h\nindex 6c10ce289da..33a4f4d5988 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -531,10 +531,13 @@ void free_diffstat_info(struct diffstat_t *diffstat);\n int parse_long_opt(const char *opt, const char **argv,\n \t\t   const char **optarg);\n \n-int git_diff_basic_config(const char *var, const char *value, void *cb);\n+struct config_context;\n+int git_diff_basic_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb);\n int git_diff_heuristic_config(const char *var, const char *value, void *cb);\n void init_diff_ui_defaults(void);\n-int git_diff_ui_config(const char *var, const char *value, void *cb);\n+int git_diff_ui_config(const char *var, const char *value,\n+\t\t       const struct config_context *ctx, void *cb);\n void repo_diff_setup(struct repository *, struct diff_options *);\n struct option *add_diff_options(const struct option *, struct diff_options *);\n int diff_opt_parse(struct diff_options *, const char **, int, const char *);\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 0f71054fbae..5f3d40f3d09 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1860,7 +1860,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \treturn ref;\n }\n \n-static int fetch_pack_config_cb(const char *var, const char *value, void *cb)\n+static int fetch_pack_config_cb(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx, void *cb)\n {\n \tif (strcmp(var, \"fetch.fsck.skiplist\") == 0) {\n \t\tconst char *path;\n@@ -1882,7 +1883,7 @@ static int fetch_pack_config_cb(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static void fetch_pack_config(void)\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 5af0d4715ba..10137444321 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -20,7 +20,8 @@ static int use_branch_desc;\n static int suppress_dest_pattern_seen;\n static struct string_list suppress_dest_patterns = STRING_LIST_INIT_DUP;\n \n-int fmt_merge_msg_config(const char *key, const char *value, void *cb)\n+int fmt_merge_msg_config(const char *key, const char *value,\n+\t\t\t const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(key, \"merge.log\") || !strcmp(key, \"merge.summary\")) {\n \t\tint is_bool;\n@@ -40,7 +41,7 @@ int fmt_merge_msg_config(const char *key, const char *value, void *cb)\n \t\t\tstring_list_append(&suppress_dest_patterns, value);\n \t\tsuppress_dest_pattern_seen = 1;\n \t} else {\n-\t\treturn git_default_config(key, value, cb);\n+\t\treturn git_default_config(key, value, ctx, cb);\n \t}\n \treturn 0;\n }\ndiff --git a/fmt-merge-msg.h b/fmt-merge-msg.h\nindex 99054042dc5..73ca3e44652 100644\n--- a/fmt-merge-msg.h\n+++ b/fmt-merge-msg.h\n@@ -13,7 +13,8 @@ struct fmt_merge_msg_opts {\n };\n \n extern int merge_log_config;\n-int fmt_merge_msg_config(const char *key, const char *value, void *cb);\n+int fmt_merge_msg_config(const char *key, const char *value,\n+\t\t\t const struct config_context *ctx, void *cb);\n int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \t\t  struct fmt_merge_msg_opts *);\n \ndiff --git a/fsck.c b/fsck.c\nindex 3261ef9ec28..55b6a694853 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1163,7 +1163,9 @@ struct fsck_gitmodules_data {\n \tint ret;\n };\n \n-static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)\n+static int fsck_gitmodules_fn(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx UNUSED,\n+\t\t\t      void *vdata)\n {\n \tstruct fsck_gitmodules_data *data = vdata;\n \tconst char *subsection, *key;\n@@ -1373,7 +1375,8 @@ int fsck_finish(struct fsck_options *options)\n \treturn ret;\n }\n \n-int git_fsck_config(const char *var, const char *value, void *cb)\n+int git_fsck_config(const char *var, const char *value,\n+\t\t    const struct config_context *ctx, void *cb)\n {\n \tstruct fsck_options *options = cb;\n \tif (strcmp(var, \"fsck.skiplist\") == 0) {\n@@ -1394,7 +1397,7 @@ int git_fsck_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n /*\ndiff --git a/fsck.h b/fsck.h\nindex e17730e9da9..6359ba359bd 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -233,10 +233,12 @@ void fsck_put_object_name(struct fsck_options *options,\n const char *fsck_describe_object(struct fsck_options *options,\n \t\t\t\t const struct object_id *oid);\n \n+struct key_value_info;\n /*\n  * git_config() callback for use by fsck-y tools that want to support\n  * fsck.<msg> fsck.skipList etc.\n  */\n-int git_fsck_config(const char *var, const char *value, void *cb);\n+int git_fsck_config(const char *var, const char *value,\n+\t\t    const struct config_context *ctx, void *cb);\n \n #endif\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 5b2b99c17c5..14e8aacb957 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -440,8 +440,10 @@ typedef uintmax_t timestamp_t;\n #endif\n \n #ifndef platform_core_config\n+struct config_context;\n static inline int noop_core_config(const char *var UNUSED,\n \t\t\t\t   const char *value UNUSED,\n+\t\t\t\t   const struct config_context *ctx UNUSED,\n \t\t\t\t   void *cb UNUSED)\n {\n \treturn 0;\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 19a3471a0b5..57c862a3a22 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -14,7 +14,8 @@\n #include \"alias.h\"\n #include \"wrapper.h\"\n \n-static int git_gpg_config(const char *, const char *, void *);\n+static int git_gpg_config(const char *, const char *,\n+\t\t\t  const struct config_context *, void *);\n \n static void gpg_interface_lazy_init(void)\n {\n@@ -720,7 +721,9 @@ void set_signing_key(const char *key)\n \tconfigured_signing_key = xstrdup(key);\n }\n \n-static int git_gpg_config(const char *var, const char *value, void *cb UNUSED)\n+static int git_gpg_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t  void *cb UNUSED)\n {\n \tstruct gpg_format *fmt = NULL;\n \tchar *fmtname = NULL;\ndiff --git a/grep.c b/grep.c\nindex f00986c451a..fc22c3e2afb 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -56,7 +56,8 @@ define_list_config_array_extra(color_grep_slots, {\"match\"});\n  * Read the configuration file once and store it in\n  * the grep_defaults template.\n  */\n-int grep_config(const char *var, const char *value, void *cb)\n+int grep_config(const char *var, const char *value,\n+\t\tconst struct config_context *ctx, void *cb)\n {\n \tstruct grep_opt *opt = cb;\n \tconst char *slot;\n@@ -91,9 +92,9 @@ int grep_config(const char *var, const char *value, void *cb)\n \tif (!strcmp(var, \"color.grep\"))\n \t\topt->color = git_config_colorbool(var, value);\n \tif (!strcmp(var, \"color.grep.match\")) {\n-\t\tif (grep_config(\"color.grep.matchcontext\", value, cb) < 0)\n+\t\tif (grep_config(\"color.grep.matchcontext\", value, ctx, cb) < 0)\n \t\t\treturn -1;\n-\t\tif (grep_config(\"color.grep.matchselected\", value, cb) < 0)\n+\t\tif (grep_config(\"color.grep.matchselected\", value, ctx, cb) < 0)\n \t\t\treturn -1;\n \t} else if (skip_prefix(var, \"color.grep.\", &slot)) {\n \t\tint i = LOOKUP_CONFIG(color_grep_slots, slot);\ndiff --git a/grep.h b/grep.h\nindex c59592e3bdb..926c0875c42 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -202,7 +202,9 @@ struct grep_opt {\n \t.output = std_output, \\\n }\n \n-int grep_config(const char *var, const char *value, void *);\n+struct config_context;\n+int grep_config(const char *var, const char *value,\n+\t\tconst struct config_context *ctx, void *data);\n void grep_init(struct grep_opt *, struct repository *repo);\n \n void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);\ndiff --git a/help.c b/help.c\nindex 5d7637dce92..ac0ae5ac0dc 100644\n--- a/help.c\n+++ b/help.c\n@@ -309,7 +309,8 @@ void load_command_list(const char *prefix,\n \texclude_cmds(other_cmds, main_cmds);\n }\n \n-static int get_colopts(const char *var, const char *value, void *data)\n+static int get_colopts(const char *var, const char *value,\n+\t\t       const struct config_context *ctx UNUSED, void *data)\n {\n \tunsigned int *colopts = data;\n \n@@ -459,7 +460,8 @@ void list_developer_interfaces_help(void)\n \tputchar('\\n');\n }\n \n-static int get_alias(const char *var, const char *value, void *data)\n+static int get_alias(const char *var, const char *value,\n+\t\t     const struct config_context *ctx UNUSED, void *data)\n {\n \tstruct string_list *list = data;\n \n@@ -543,6 +545,7 @@ static struct cmdnames aliases;\n #define AUTOCORRECT_IMMEDIATELY (-1)\n \n static int git_unknown_cmd_config(const char *var, const char *value,\n+\t\t\t\t  const struct config_context *ctx UNUSED,\n \t\t\t\t  void *cb UNUSED)\n {\n \tconst char *p;\ndiff --git a/http.c b/http.c\nindex bb58bb3e6a3..762502828c9 100644\n--- a/http.c\n+++ b/http.c\n@@ -363,7 +363,8 @@ static void process_curl_messages(void)\n \t}\n }\n \n-static int http_options(const char *var, const char *value, void *cb)\n+static int http_options(const char *var, const char *value,\n+\t\t\tconst struct config_context *ctx, void *data)\n {\n \tif (!strcmp(\"http.version\", var)) {\n \t\treturn git_config_string(&curl_http_version, var, value);\n@@ -534,7 +535,7 @@ static int http_options(const char *var, const char *value, void *cb)\n \t}\n \n \t/* Fall back on the default ones */\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, data);\n }\n \n static int curl_empty_auth_enabled(void)\ndiff --git a/ident.c b/ident.c\nindex 8fad92d7007..08be4d0747d 100644\n--- a/ident.c\n+++ b/ident.c\n@@ -671,7 +671,9 @@ static int set_ident(const char *var, const char *value)\n \treturn 0;\n }\n \n-int git_ident_config(const char *var, const char *value, void *data UNUSED)\n+int git_ident_config(const char *var, const char *value,\n+\t\t     const struct config_context *ctx UNUSED,\n+\t\t     void *data UNUSED)\n {\n \tif (!strcmp(var, \"user.useconfigonly\")) {\n \t\tident_use_config_only = git_config_bool(var, value);\ndiff --git a/ident.h b/ident.h\nindex 96a64896a01..6a79febba15 100644\n--- a/ident.h\n+++ b/ident.h\n@@ -62,6 +62,8 @@ const char *fmt_name(enum want_ident);\n int committer_ident_sufficiently_given(void);\n int author_ident_sufficiently_given(void);\n \n-int git_ident_config(const char *, const char *, void *);\n+struct config_context;\n+int git_ident_config(const char *, const char *, const struct config_context *,\n+\t\t     void *);\n \n #endif\ndiff --git a/imap-send.c b/imap-send.c\nindex 7f5426177a1..47777e76861 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1323,7 +1323,8 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)\n \treturn 1;\n }\n \n-static int git_imap_config(const char *var, const char *val, void *cb)\n+static int git_imap_config(const char *var, const char *val,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \n \tif (!strcmp(\"imap.sslverify\", var))\n@@ -1357,7 +1358,7 @@ static int git_imap_config(const char *var, const char *val, void *cb)\n \t\t\tserver.host = xstrdup(val);\n \t\t}\n \t} else\n-\t\treturn git_default_config(var, val, cb);\n+\t\treturn git_default_config(var, val, ctx, cb);\n \n \treturn 0;\n }\ndiff --git a/ll-merge.c b/ll-merge.c\nindex 07ec16e8e5b..3936d112e00 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -254,6 +254,7 @@ static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;\n static const char *default_ll_merge;\n \n static int read_merge_config(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t     void *cb UNUSED)\n {\n \tstruct ll_merge_driver *fn;\ndiff --git a/ls-refs.c b/ls-refs.c\nindex f385938b64c..a29c2364a59 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -137,6 +137,7 @@ static void send_possibly_unborn_head(struct ls_refs_data *data)\n }\n \n static int ls_refs_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx UNUSED,\n \t\t\t  void *cb_data)\n {\n \tstruct ls_refs_data *data = cb_data;\ndiff --git a/mailinfo.c b/mailinfo.c\nindex 2aeb20e5e62..931505363cd 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -1241,12 +1241,13 @@ int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)\n \treturn 0;\n }\n \n-static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n+static int git_mailinfo_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *mi_)\n {\n \tstruct mailinfo *mi = mi_;\n \n \tif (!starts_with(var, \"mailinfo.\"))\n-\t\treturn git_default_config(var, value, NULL);\n+\t\treturn git_default_config(var, value, ctx, NULL);\n \tif (!strcmp(var, \"mailinfo.scissors\")) {\n \t\tmi->use_scissors = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/notes-utils.c b/notes-utils.c\nindex 4a793eb347f..97c031c26ec 100644\n--- a/notes-utils.c\n+++ b/notes-utils.c\n@@ -94,7 +94,9 @@ static combine_notes_fn parse_combine_notes_fn(const char *v)\n \t\treturn NULL;\n }\n \n-static int notes_rewrite_config(const char *k, const char *v, void *cb)\n+static int notes_rewrite_config(const char *k, const char *v,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *cb)\n {\n \tstruct notes_rewrite_cfg *c = cb;\n \tif (starts_with(k, \"notes.rewrite.\") && !strcmp(k+14, c->cmd)) {\ndiff --git a/notes.c b/notes.c\nindex f51a2d3630e..e68645a4b89 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -974,7 +974,9 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,\n \tfree(globs_copy);\n }\n \n-static int notes_display_config(const char *k, const char *v, void *cb)\n+static int notes_display_config(const char *k, const char *v,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *cb)\n {\n \tint *load_refs = cb;\n \ndiff --git a/pager.c b/pager.c\nindex 63055d0873f..b8822a9381e 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -43,6 +43,7 @@ static void wait_for_pager_signal(int signo)\n }\n \n static int core_pager_config(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t     void *data UNUSED)\n {\n \tif (!strcmp(var, \"core.pager\"))\n@@ -228,7 +229,9 @@ struct pager_command_config_data {\n \tchar *value;\n };\n \n-static int pager_command_config(const char *var, const char *value, void *vdata)\n+static int pager_command_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *vdata)\n {\n \tstruct pager_command_config_data *data = vdata;\n \tconst char *cmd;\ndiff --git a/pretty.c b/pretty.c\nindex 0bb938021ba..87245353452 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -56,6 +56,7 @@ static void save_user_format(struct rev_info *rev, const char *cp, int is_tforma\n }\n \n static int git_pretty_formats_config(const char *var, const char *value,\n+\t\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t\t     void *cb UNUSED)\n {\n \tstruct cmt_fmt_map *commit_format = NULL;\ndiff --git a/promisor-remote.c b/promisor-remote.c\nindex 1adcd6fb0a5..c22abb85b15 100644\n--- a/promisor-remote.c\n+++ b/promisor-remote.c\n@@ -100,7 +100,9 @@ static void promisor_remote_move_to_tail(struct promisor_remote_config *config,\n \tconfig->promisors_tail = &r->next;\n }\n \n-static int promisor_remote_config(const char *var, const char *value, void *data)\n+static int promisor_remote_config(const char *var, const char *value,\n+\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t  void *data)\n {\n \tstruct promisor_remote_config *config = data;\n \tconst char *name;\ndiff --git a/remote.c b/remote.c\nindex 1bcd36e358a..241999c2842 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -349,7 +349,8 @@ static void read_branches_file(struct remote_state *remote_state,\n \tremote->fetch_tags = 1; /* always auto-follow */\n }\n \n-static int handle_config(const char *key, const char *value, void *cb)\n+static int handle_config(const char *key, const char *value,\n+\t\t\t const struct config_context *ctx UNUSED, void *cb)\n {\n \tconst char *name;\n \tsize_t namelen;\ndiff --git a/revision.c b/revision.c\nindex b33cc1d106a..87ed8ccd444 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1572,7 +1572,9 @@ struct exclude_hidden_refs_cb {\n \tconst char *section;\n };\n \n-static int hide_refs_config(const char *var, const char *value, void *cb_data)\n+static int hide_refs_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    void *cb_data)\n {\n \tstruct exclude_hidden_refs_cb *cb = cb_data;\n \tcb->exclusions->hidden_refs_configured = 1;\ndiff --git a/scalar.c b/scalar.c\nindex 1326e1f6089..df7358f481c 100644\n--- a/scalar.c\n+++ b/scalar.c\n@@ -594,7 +594,9 @@ static int cmd_register(int argc, const char **argv)\n \treturn register_dir();\n }\n \n-static int get_scalar_repos(const char *key, const char *value, void *data)\n+static int get_scalar_repos(const char *key, const char *value,\n+\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    void *data)\n {\n \tstruct string_list *list = data;\n \ndiff --git a/sequencer.c b/sequencer.c\nindex bceb6abcb6c..34754d17596 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -219,7 +219,8 @@ static struct update_ref_record *init_update_ref_record(const char *ref)\n \treturn rec;\n }\n \n-static int git_sequencer_config(const char *k, const char *v, void *cb)\n+static int git_sequencer_config(const char *k, const char *v,\n+\t\t\t\tconst struct config_context *ctx, void *cb)\n {\n \tstruct replay_opts *opts = cb;\n \tint status;\n@@ -274,7 +275,7 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)\n \tif (opts->action == REPLAY_REVERT && !strcmp(k, \"revert.reference\"))\n \t\topts->commit_use_reference = git_config_bool(k, v);\n \n-\treturn git_diff_basic_config(k, v, NULL);\n+\treturn git_diff_basic_config(k, v, ctx, NULL);\n }\n \n void sequencer_init_config(struct replay_opts *opts)\n@@ -2881,7 +2882,9 @@ static int git_config_string_dup(char **dest,\n \treturn 0;\n }\n \n-static int populate_opts_cb(const char *key, const char *value, void *data)\n+static int populate_opts_cb(const char *key, const char *value,\n+\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    void *data)\n {\n \tstruct replay_opts *opts = data;\n \tint error_flag = 1;\ndiff --git a/setup.c b/setup.c\nindex 6f6e92b96be..fadba5bab4b 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -517,7 +517,9 @@ no_prevention_needed:\n \tstartup_info->original_cwd = NULL;\n }\n \n-static int read_worktree_config(const char *var, const char *value, void *vdata)\n+static int read_worktree_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *vdata)\n {\n \tstruct repository_format *data = vdata;\n \n@@ -588,7 +590,8 @@ static enum extension_result handle_extension(const char *var,\n \treturn EXTENSION_UNKNOWN;\n }\n \n-static int check_repo_format(const char *var, const char *value, void *vdata)\n+static int check_repo_format(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx, void *vdata)\n {\n \tstruct repository_format *data = vdata;\n \tconst char *ext;\n@@ -617,7 +620,7 @@ static int check_repo_format(const char *var, const char *value, void *vdata)\n \t\t}\n \t}\n \n-\treturn read_worktree_config(var, value, vdata);\n+\treturn read_worktree_config(var, value, ctx, vdata);\n }\n \n static int check_repository_format_gently(const char *gitdir, struct repository_format *candidate, int *nongit_ok)\n@@ -1115,7 +1118,8 @@ struct safe_directory_data {\n \tint is_safe;\n };\n \n-static int safe_directory_cb(const char *key, const char *value, void *d)\n+static int safe_directory_cb(const char *key, const char *value,\n+\t\t\t     const struct config_context *ctx UNUSED, void *d)\n {\n \tstruct safe_directory_data *data = d;\n \n@@ -1171,7 +1175,9 @@ static int ensure_valid_ownership(const char *gitfile,\n \treturn data.is_safe;\n }\n \n-static int allowed_bare_repo_cb(const char *key, const char *value, void *d)\n+static int allowed_bare_repo_cb(const char *key, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *d)\n {\n \tenum allowed_bare_repo *allowed_bare_repo = d;\n \ndiff --git a/submodule-config.c b/submodule-config.c\nindex 7eb7a0d88d2..a38d4d49731 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -426,7 +426,8 @@ struct parse_config_parameter {\n  * config store (.git/config, etc).  Callers are responsible for\n  * checking for overrides in the main config store when appropriate.\n  */\n-static int parse_config(const char *var, const char *value, void *data)\n+static int parse_config(const char *var, const char *value,\n+\t\t\tconst struct config_context *ctx UNUSED, void *data)\n {\n \tstruct parse_config_parameter *me = data;\n \tstruct submodule *submodule;\n@@ -674,7 +675,8 @@ out:\n \t}\n }\n \n-static int gitmodules_cb(const char *var, const char *value, void *data)\n+static int gitmodules_cb(const char *var, const char *value,\n+\t\t\t const struct config_context *ctx, void *data)\n {\n \tstruct repository *repo = data;\n \tstruct parse_config_parameter parameter;\n@@ -684,7 +686,7 @@ static int gitmodules_cb(const char *var, const char *value, void *data)\n \tparameter.gitmodules_oid = null_oid();\n \tparameter.overwrite = 1;\n \n-\treturn parse_config(var, value, &parameter);\n+\treturn parse_config(var, value, ctx, &parameter);\n }\n \n void repo_read_gitmodules(struct repository *repo, int skip_if_read)\n@@ -801,7 +803,9 @@ void submodule_free(struct repository *r)\n \t\tsubmodule_cache_clear(r->submodule_cache);\n }\n \n-static int config_print_callback(const char *var, const char *value, void *cb_data)\n+static int config_print_callback(const char *var, const char *value,\n+\t\t\t\t const struct config_context *ctx UNUSED,\n+\t\t\t\t void *cb_data)\n {\n \tchar *wanted_key = cb_data;\n \n@@ -843,7 +847,9 @@ struct fetch_config {\n \tint *recurse_submodules;\n };\n \n-static int gitmodules_fetch_config(const char *var, const char *value, void *cb)\n+static int gitmodules_fetch_config(const char *var, const char *value,\n+\t\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t\t   void *cb)\n {\n \tstruct fetch_config *config = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\")) {\n@@ -871,6 +877,7 @@ void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)\n }\n \n static int gitmodules_update_clone_config(const char *var, const char *value,\n+\t\t\t\t\t  const struct config_context *ctx UNUSED,\n \t\t\t\t\t  void *cb)\n {\n \tint *max_jobs = cb;\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex ad78fc17683..85ad815358e 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -42,7 +42,9 @@\n  *\n  */\n \n-static int iterate_cb(const char *var, const char *value, void *data UNUSED)\n+static int iterate_cb(const char *var, const char *value,\n+\t\t      const struct config_context *ctx UNUSED,\n+\t\t      void *data UNUSED)\n {\n \tstatic int nr;\n \n@@ -59,7 +61,8 @@ static int iterate_cb(const char *var, const char *value, void *data UNUSED)\n \treturn 0;\n }\n \n-static int parse_int_cb(const char *var, const char *value, void *data)\n+static int parse_int_cb(const char *var, const char *value,\n+\t\t\tconst struct config_context *ctx UNUSED, void *data)\n {\n \tconst char *key_to_match = data;\n \n@@ -70,7 +73,9 @@ static int parse_int_cb(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n-static int early_config_cb(const char *var, const char *value, void *vdata)\n+static int early_config_cb(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t   void *vdata)\n {\n \tconst char *key = vdata;\n \ndiff --git a/t/helper/test-userdiff.c b/t/helper/test-userdiff.c\nindex 680124a6760..0ce31ce59f5 100644\n--- a/t/helper/test-userdiff.c\n+++ b/t/helper/test-userdiff.c\n@@ -12,7 +12,9 @@ static int driver_cb(struct userdiff_driver *driver,\n \treturn 0;\n }\n \n-static int cmd__userdiff_config(const char *var, const char *value, void *cb UNUSED)\n+static int cmd__userdiff_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *cb UNUSED)\n {\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 78cfc15d52d..83bc4fd109c 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -99,7 +99,8 @@ struct tr2_cfg_data {\n /*\n  * See if the given config key matches any of our patterns of interest.\n  */\n-static int tr2_cfg_cb(const char *key, const char *value, void *d)\n+static int tr2_cfg_cb(const char *key, const char *value,\n+\t\t      const struct config_context *ctx UNUSED, void *d)\n {\n \tstruct strbuf **s;\n \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n@@ -142,8 +143,12 @@ void tr2_list_env_vars_fl(const char *file, int line)\n void tr2_cfg_set_fl(const char *file, int line, const char *key,\n \t\t    const char *value)\n {\n+\tstruct key_value_info kvi = KVI_INIT;\n+\tstruct config_context ctx = {\n+\t\t.kvi = &kvi,\n+\t};\n \tstruct tr2_cfg_data data = { file, line };\n \n \tif (tr2_cfg_load_patterns() > 0)\n-\t\ttr2_cfg_cb(key, value, &data);\n+\t\ttr2_cfg_cb(key, value, &ctx, &data);\n }\ndiff --git a/trace2/tr2_sysenv.c b/trace2/tr2_sysenv.c\nindex 069786cb927..f26ec95ab4d 100644\n--- a/trace2/tr2_sysenv.c\n+++ b/trace2/tr2_sysenv.c\n@@ -57,7 +57,8 @@ static struct tr2_sysenv_entry tr2_sysenv_settings[] = {\n };\n /* clang-format on */\n \n-static int tr2_sysenv_cb(const char *key, const char *value, void *d)\n+static int tr2_sysenv_cb(const char *key, const char *value,\n+\t\t\t const struct config_context *ctx UNUSED, void *d)\n {\n \tint k;\n \ndiff --git a/trailer.c b/trailer.c\nindex a2c3ed6f28c..06dc0b7f683 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -482,6 +482,7 @@ static struct {\n };\n \n static int git_trailer_default_config(const char *conf_key, const char *value,\n+\t\t\t\t      const struct config_context *ctx UNUSED,\n \t\t\t\t      void *cb UNUSED)\n {\n \tconst char *trailer_item, *variable_name;\n@@ -514,6 +515,7 @@ static int git_trailer_default_config(const char *conf_key, const char *value,\n }\n \n static int git_trailer_config(const char *conf_key, const char *value,\n+\t\t\t      const struct config_context *ctx UNUSED,\n \t\t\t      void *cb UNUSED)\n {\n \tconst char *trailer_item, *variable_name;\ndiff --git a/upload-pack.c b/upload-pack.c\nindex d3312006a32..951fd1f9c25 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1309,7 +1309,9 @@ static int parse_object_filter_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-static int upload_pack_config(const char *var, const char *value, void *cb_data)\n+static int upload_pack_config(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx UNUSED,\n+\t\t\t      void *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n \n@@ -1350,7 +1352,9 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data)\n \treturn parse_hide_refs_config(var, value, \"uploadpack\", &data->hidden_refs);\n }\n \n-static int upload_pack_protected_config(const char *var, const char *value, void *cb_data)\n+static int upload_pack_protected_config(const char *var, const char *value,\n+\t\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\t\tvoid *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n \ndiff --git a/urlmatch.c b/urlmatch.c\nindex eba0bdd77fe..1c45f23adf2 100644\n--- a/urlmatch.c\n+++ b/urlmatch.c\n@@ -551,7 +551,8 @@ static int cmp_matches(const struct urlmatch_item *a,\n \treturn 0;\n }\n \n-int urlmatch_config_entry(const char *var, const char *value, void *cb)\n+int urlmatch_config_entry(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tstruct string_list_item *item;\n \tstruct urlmatch_config *collect = cb;\n@@ -565,7 +566,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)\n \n \tif (!skip_prefix(var, collect->section, &key) || *(key++) != '.') {\n \t\tif (collect->cascade_fn)\n-\t\t\treturn collect->cascade_fn(var, value, cb);\n+\t\t\treturn collect->cascade_fn(var, value, ctx, cb);\n \t\treturn 0; /* not interested */\n \t}\n \tdot = strrchr(key, '.');\n@@ -609,7 +610,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)\n \tstrbuf_addstr(&synthkey, collect->section);\n \tstrbuf_addch(&synthkey, '.');\n \tstrbuf_addstr(&synthkey, key);\n-\tretval = collect->collect_fn(synthkey.buf, value, collect->cb);\n+\tretval = collect->collect_fn(synthkey.buf, value, ctx, collect->cb);\n \n \tstrbuf_release(&synthkey);\n \treturn retval;\ndiff --git a/urlmatch.h b/urlmatch.h\nindex bee374a642c..5ba85cea139 100644\n--- a/urlmatch.h\n+++ b/urlmatch.h\n@@ -71,7 +71,8 @@ struct urlmatch_config {\n \t.vars = STRING_LIST_INIT_DUP, \\\n }\n \n-int urlmatch_config_entry(const char *var, const char *value, void *cb);\n+int urlmatch_config_entry(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb);\n void urlmatch_config_release(struct urlmatch_config *config);\n \n #endif /* URL_MATCH_H */\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex 0460e03f5ed..dcbb5e09857 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -307,7 +307,8 @@ int xdiff_compare_lines(const char *l1, long s1,\n \n int git_xmerge_style = -1;\n \n-int git_xmerge_config(const char *var, const char *value, void *cb)\n+int git_xmerge_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"merge.conflictstyle\")) {\n \t\tif (!value)\n@@ -327,5 +328,5 @@ int git_xmerge_config(const char *var, const char *value, void *cb)\n \t\t\t    value, var);\n \t\treturn 0;\n \t}\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex 733c364d26c..e6f80df0462 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -50,7 +50,9 @@ int buffer_is_binary(const char *ptr, unsigned long size);\n \n void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n-int git_xmerge_config(const char *var, const char *value, void *cb);\n+struct config_context;\n+int git_xmerge_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"478842","messageId":"3f0f84df9721ea94981b728e76b5354d73aab2ce.1687803086.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"[PATCH v4 11/12] config: add kvi.path, use it to evaluate includes","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T18:11:22Z","receivedAt":"2023-06-26T18:12:03Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nInclude directives are evaluated using the path of the config file. To\nreduce the dependence on \"config_reader.source\", add a new\n\"key_value_info.path\" member and use that instead of\n\"config_source.path\". This allows us to remove a \"struct config_reader\n*\" field from \"struct config_include_data\", which will subsequently\nallow us to remove \"struct config_reader\" entirely.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 40 +++++++++++++++++++---------------------\n config.h |  2 ++\n 2 files changed, 21 insertions(+), 21 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 4782a289363..070245065f4 100644\n--- a/config.c\n+++ b/config.c\n@@ -162,7 +162,6 @@ struct config_include_data {\n \tconst struct config_options *opts;\n \tstruct git_config_source *config_source;\n \tstruct repository *repo;\n-\tstruct config_reader *config_reader;\n \n \t/*\n \t * All remote URLs discovered when reading all config files.\n@@ -181,8 +180,7 @@ static const char include_depth_advice[] = N_(\n \"from\\n\"\n \"\t%s\\n\"\n \"This might be due to circular includes.\");\n-static int handle_path_include(struct config_source *cs,\n-\t\t\t       const struct key_value_info *kvi,\n+static int handle_path_include(const struct key_value_info *kvi,\n \t\t\t       const char *path,\n \t\t\t       struct config_include_data *inc)\n {\n@@ -205,14 +203,14 @@ static int handle_path_include(struct config_source *cs,\n \tif (!is_absolute_path(path)) {\n \t\tchar *slash;\n \n-\t\tif (!cs || !cs->path) {\n+\t\tif (!kvi || !kvi->path) {\n \t\t\tret = error(_(\"relative config includes must come from files\"));\n \t\t\tgoto cleanup;\n \t\t}\n \n-\t\tslash = find_last_dir_sep(cs->path);\n+\t\tslash = find_last_dir_sep(kvi->path);\n \t\tif (slash)\n-\t\t\tstrbuf_add(&buf, cs->path, slash - cs->path + 1);\n+\t\t\tstrbuf_add(&buf, kvi->path, slash - kvi->path + 1);\n \t\tstrbuf_addstr(&buf, path);\n \t\tpath = buf.buf;\n \t}\n@@ -220,8 +218,8 @@ static int handle_path_include(struct config_source *cs,\n \tif (!access_or_die(path, R_OK, 0)) {\n \t\tif (++inc->depth > MAX_INCLUDE_DEPTH)\n \t\t\tdie(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,\n-\t\t\t    !cs ? \"<unknown>\" :\n-\t\t\t    cs->name ? cs->name :\n+\t\t\t    !kvi ? \"<unknown>\" :\n+\t\t\t    kvi->filename ? kvi->filename :\n \t\t\t    \"the command line\");\n \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n \t\t\t\t\t\t\tkvi->scope, NULL);\n@@ -239,7 +237,7 @@ static void add_trailing_starstar_for_dir(struct strbuf *pat)\n \t\tstrbuf_addstr(pat, \"**\");\n }\n \n-static int prepare_include_condition_pattern(struct config_source *cs,\n+static int prepare_include_condition_pattern(const struct key_value_info *kvi,\n \t\t\t\t\t     struct strbuf *pat)\n {\n \tstruct strbuf path = STRBUF_INIT;\n@@ -256,11 +254,11 @@ static int prepare_include_condition_pattern(struct config_source *cs,\n \tif (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {\n \t\tconst char *slash;\n \n-\t\tif (!cs || !cs->path)\n+\t\tif (!kvi || !kvi->path)\n \t\t\treturn error(_(\"relative config include \"\n \t\t\t\t       \"conditionals must come from files\"));\n \n-\t\tstrbuf_realpath(&path, cs->path, 1);\n+\t\tstrbuf_realpath(&path, kvi->path, 1);\n \t\tslash = find_last_dir_sep(path.buf);\n \t\tif (!slash)\n \t\t\tBUG(\"how is this possible?\");\n@@ -275,7 +273,7 @@ static int prepare_include_condition_pattern(struct config_source *cs,\n \treturn prefix;\n }\n \n-static int include_by_gitdir(struct config_source *cs,\n+static int include_by_gitdir(const struct key_value_info *kvi,\n \t\t\t     const struct config_options *opts,\n \t\t\t     const char *cond, size_t cond_len, int icase)\n {\n@@ -292,7 +290,7 @@ static int include_by_gitdir(struct config_source *cs,\n \n \tstrbuf_realpath(&text, git_dir, 1);\n \tstrbuf_add(&pattern, cond, cond_len);\n-\tprefix = prepare_include_condition_pattern(cs, &pattern);\n+\tprefix = prepare_include_condition_pattern(kvi, &pattern);\n \n again:\n \tif (prefix < 0)\n@@ -428,16 +426,16 @@ static int include_by_remote_url(struct config_include_data *inc,\n \t\t\t\t\t     inc->remote_urls);\n }\n \n-static int include_condition_is_true(struct config_source *cs,\n+static int include_condition_is_true(const struct key_value_info *kvi,\n \t\t\t\t     struct config_include_data *inc,\n \t\t\t\t     const char *cond, size_t cond_len)\n {\n \tconst struct config_options *opts = inc->opts;\n \n \tif (skip_prefix_mem(cond, cond_len, \"gitdir:\", &cond, &cond_len))\n-\t\treturn include_by_gitdir(cs, opts, cond, cond_len, 0);\n+\t\treturn include_by_gitdir(kvi, opts, cond, cond_len, 0);\n \telse if (skip_prefix_mem(cond, cond_len, \"gitdir/i:\", &cond, &cond_len))\n-\t\treturn include_by_gitdir(cs, opts, cond, cond_len, 1);\n+\t\treturn include_by_gitdir(kvi, opts, cond, cond_len, 1);\n \telse if (skip_prefix_mem(cond, cond_len, \"onbranch:\", &cond, &cond_len))\n \t\treturn include_by_branch(cond, cond_len);\n \telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.*.url:\", &cond,\n@@ -453,7 +451,6 @@ static int git_config_include(const char *var, const char *value,\n \t\t\t      void *data)\n {\n \tstruct config_include_data *inc = data;\n-\tstruct config_source *cs = inc->config_reader->source;\n \tconst char *cond, *key;\n \tsize_t cond_len;\n \tint ret;\n@@ -467,16 +464,16 @@ static int git_config_include(const char *var, const char *value,\n \t\treturn ret;\n \n \tif (!strcmp(var, \"include.path\"))\n-\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n+\t\tret = handle_path_include(ctx->kvi, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n-\t    cond && include_condition_is_true(cs, inc, cond, cond_len) &&\n+\t    cond && include_condition_is_true(ctx->kvi, inc, cond, cond_len) &&\n \t    !strcmp(key, \"path\")) {\n \t\tconfig_fn_t old_fn = inc->fn;\n \n \t\tif (inc->opts->unconditional_remote_url)\n \t\t\tinc->fn = forbid_remote_url;\n-\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n+\t\tret = handle_path_include(ctx->kvi, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -661,6 +658,7 @@ void kvi_from_param(struct key_value_info *out)\n \tout->linenr = -1;\n \tout->origin_type = CONFIG_ORIGIN_CMDLINE;\n \tout->scope = CONFIG_SCOPE_COMMAND;\n+\tout->path = NULL;\n }\n \n int git_config_parse_parameter(const char *text,\n@@ -1064,6 +1062,7 @@ static void kvi_from_source(struct config_source *cs,\n \tout->origin_type = cs->origin_type;\n \tout->linenr = cs->linenr;\n \tout->scope = scope;\n+\tout->path = cs->path;\n }\n \n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n@@ -2282,7 +2281,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tinc.opts = opts;\n \t\tinc.repo = repo;\n \t\tinc.config_source = config_source;\n-\t\tinc.config_reader = &the_reader;\n \t\tfn = git_config_include;\n \t\tdata = &inc;\n \t}\ndiff --git a/config.h b/config.h\nindex bd366ddb5ef..b5573e67a00 100644\n--- a/config.h\n+++ b/config.h\n@@ -116,12 +116,14 @@ struct key_value_info {\n \tint linenr;\n \tenum config_origin_type origin_type;\n \tenum config_scope scope;\n+\tconst char *path;\n };\n #define KVI_INIT { \\\n \t.filename = NULL, \\\n \t.linenr = -1, \\\n \t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n \t.scope = CONFIG_SCOPE_UNKNOWN, \\\n+\t.path = NULL, \\\n }\n \n /* Captures additional information that a config callback can use. */\n-- \ngitgitgadget\n\n"},{"id":"478845","messageId":"fe2f154fe8b27bf0edc4e657ece145f54f585e09.1687803086.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"[PATCH v4 12/12] config: pass source to config_parser_event_fn_t","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T18:11:23Z","receivedAt":"2023-06-26T18:12:05Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\n..so that the callback can use a \"struct config_source\" parameter\ninstead of \"config_reader.source\". \"struct config_source\" is internal to\nconfig.c, so we are adding a pointer to a struct defined in config.c\ninto a public function signature defined in config.h, but this is okay\nbecause this function has only ever been (and probably ever will be)\nused internally by config.c.\n\nAs a result, the_reader isn't used anywhere, so \"struct config_reader\"\nis obsolete (it was only intended to be used with the_reader). Remove\nthem.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 77 ++++++++++----------------------------------------------\n config.h |  6 +++++\n 2 files changed, 19 insertions(+), 64 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 070245065f4..2ddeb5e2685 100644\n--- a/config.c\n+++ b/config.c\n@@ -66,40 +66,6 @@ struct config_source {\n };\n #define CONFIG_SOURCE_INIT { 0 }\n \n-struct config_reader {\n-\t/*\n-\t * These members record the \"current\" config source, which can be\n-\t * accessed by parsing callbacks.\n-\t *\n-\t * The \"source\" variable will be non-NULL only when we are actually\n-\t * parsing a real config source (file, blob, cmdline, etc).\n-\t */\n-\tstruct config_source *source;\n-};\n-/*\n- * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n- * \"the_reader\". \"the_reader\" should only be used if that is infeasible, e.g. in\n- * a public function.\n- */\n-static struct config_reader the_reader;\n-\n-static inline void config_reader_push_source(struct config_reader *reader,\n-\t\t\t\t\t     struct config_source *top)\n-{\n-\ttop->prev = reader->source;\n-\treader->source = top;\n-}\n-\n-static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n-{\n-\tstruct config_source *ret;\n-\tif (!reader->source)\n-\t\tBUG(\"tried to pop config source, but we weren't reading config\");\n-\tret = reader->source;\n-\treader->source = reader->source->prev;\n-\treturn ret;\n-}\n-\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -752,14 +718,9 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tstruct strvec to_free = STRVEC_INIT;\n \tint ret = 0;\n \tchar *envw = NULL;\n-\tstruct config_source source = CONFIG_SOURCE_INIT;\n \tstruct key_value_info kvi = KVI_INIT;\n \n-\tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n-\tconfig_reader_push_source(&the_reader, &source);\n-\n \tkvi_from_param(&kvi);\n-\n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n \t\tunsigned long count;\n@@ -816,7 +777,6 @@ out:\n \tstrbuf_release(&envvar);\n \tstrvec_clear(&to_free);\n \tfree(envw);\n-\tconfig_reader_pop_source(&the_reader);\n \treturn ret;\n }\n \n@@ -1045,7 +1005,7 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \n \tif (data->previous_type != CONFIG_EVENT_EOF &&\n \t    data->opts->event_fn(data->previous_type, data->previous_offset,\n-\t\t\t\t offset, data->opts->event_fn_data) < 0)\n+\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n \t\treturn -1;\n \n \tdata->previous_type = type;\n@@ -2002,8 +1962,7 @@ int git_default_config(const char *var, const char *value,\n  * fgetc, ungetc, ftell of top need to be initialized before calling\n  * this function.\n  */\n-static int do_config_from(struct config_reader *reader,\n-\t\t\t  struct config_source *top, config_fn_t fn,\n+static int do_config_from(struct config_source *top, config_fn_t fn,\n \t\t\t  void *data, enum config_scope scope,\n \t\t\t  const struct config_options *opts)\n {\n@@ -2016,21 +1975,17 @@ static int do_config_from(struct config_reader *reader,\n \ttop->total_len = 0;\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n-\tconfig_reader_push_source(reader, top);\n \tkvi_from_source(top, scope, &kvi);\n \n \tret = git_parse_source(top, fn, &kvi, data, opts);\n \n-\t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n \tstrbuf_release(&top->var);\n-\tconfig_reader_pop_source(reader);\n \n \treturn ret;\n }\n \n-static int do_config_from_file(struct config_reader *reader,\n-\t\t\t       config_fn_t fn,\n+static int do_config_from_file(config_fn_t fn,\n \t\t\t       const enum config_origin_type origin_type,\n \t\t\t       const char *name, const char *path, FILE *f,\n \t\t\t       void *data, enum config_scope scope,\n@@ -2049,7 +2004,7 @@ static int do_config_from_file(struct config_reader *reader,\n \ttop.do_ftell = config_file_ftell;\n \n \tflockfile(f);\n-\tret = do_config_from(reader, &top, fn, data, scope, opts);\n+\tret = do_config_from(&top, fn, data, scope, opts);\n \tfunlockfile(f);\n \treturn ret;\n }\n@@ -2057,8 +2012,8 @@ static int do_config_from_file(struct config_reader *reader,\n static int git_config_from_stdin(config_fn_t fn, void *data,\n \t\t\t\t enum config_scope scope)\n {\n-\treturn do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, \"\",\n-\t\t\t\t   NULL, stdin, data, scope, NULL);\n+\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n+\t\t\t\t   data, scope, NULL);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2072,9 +2027,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \t\tBUG(\"filename cannot be NULL\");\n \tf = fopen_or_warn(filename, \"r\");\n \tif (f) {\n-\t\tret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,\n-\t\t\t\t\t  filename, filename, f, data, scope,\n-\t\t\t\t\t  opts);\n+\t\tret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,\n+\t\t\t\t\t  filename, f, data, scope, opts);\n \t\tfclose(f);\n \t}\n \treturn ret;\n@@ -2105,7 +2059,7 @@ int git_config_from_mem(config_fn_t fn,\n \ttop.do_ungetc = config_buf_ungetc;\n \ttop.do_ftell = config_buf_ftell;\n \n-\treturn do_config_from(&the_reader, &top, fn, data, scope, opts);\n+\treturn do_config_from(&top, fn, data, scope, opts);\n }\n \n int git_config_from_blob_oid(config_fn_t fn,\n@@ -2198,8 +2152,7 @@ int git_config_system(void)\n \treturn !git_env_bool(\"GIT_CONFIG_NOSYSTEM\", 0);\n }\n \n-static int do_git_config_sequence(struct config_reader *reader,\n-\t\t\t\t  const struct config_options *opts,\n+static int do_git_config_sequence(const struct config_options *opts,\n \t\t\t\t  const struct repository *repo,\n \t\t\t\t  config_fn_t fn, void *data)\n {\n@@ -2299,7 +2252,7 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n \t\t\t\t\t       data, config_source->scope);\n \t} else {\n-\t\tret = do_git_config_sequence(&the_reader, opts, repo, fn, data);\n+\t\tret = do_git_config_sequence(opts, repo, fn, data);\n \t}\n \n \tif (inc.remote_urls) {\n@@ -3009,7 +2962,6 @@ void git_die_config(const char *key, const char *err, ...)\n  */\n \n struct config_store_data {\n-\tstruct config_reader *config_reader;\n \tsize_t baselen;\n \tchar *key;\n \tint do_not_match;\n@@ -3055,11 +3007,10 @@ static int matches(const char *key, const char *value,\n \t\t(value && !regexec(store->value_pattern, value, 0, NULL, 0));\n }\n \n-static int store_aux_event(enum config_event_t type,\n-\t\t\t   size_t begin, size_t end, void *data)\n+static int store_aux_event(enum config_event_t type, size_t begin, size_t end,\n+\t\t\t   struct config_source *cs, void *data)\n {\n \tstruct config_store_data *store = data;\n-\tstruct config_source *cs = store->config_reader->source;\n \n \tALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);\n \tstore->parsed[store->parsed_nr].begin = begin;\n@@ -3380,8 +3331,6 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \tsize_t contents_sz;\n \tstruct config_store_data store = CONFIG_STORE_INIT;\n \n-\tstore.config_reader = &the_reader;\n-\n \t/* parse-key returns negative; flip the sign to feed exit(3) */\n \tret = 0 - git_config_parse_key(key, &store.key, &store.baselen);\n \tif (ret)\ndiff --git a/config.h b/config.h\nindex b5573e67a00..6332d749047 100644\n--- a/config.h\n+++ b/config.h\n@@ -72,6 +72,7 @@ enum config_event_t {\n \tCONFIG_EVENT_ERROR\n };\n \n+struct config_source;\n /*\n  * The parser event function (if not NULL) is called with the event type and\n  * the begin/end offsets of the parsed elements.\n@@ -81,6 +82,7 @@ enum config_event_t {\n  */\n typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n \t\t\t\t\tsize_t begin_offset, size_t end_offset,\n+\t\t\t\t\tstruct config_source *cs,\n \t\t\t\t\tvoid *event_fn_data);\n \n struct config_options {\n@@ -100,6 +102,10 @@ struct config_options {\n \n \tconst char *commondir;\n \tconst char *git_dir;\n+\t/*\n+\t * event_fn and event_fn_data are for internal use only. Handles events\n+\t * emitted by the config parser.\n+\t */\n \tconfig_parser_event_fn_t event_fn;\n \tvoid *event_fn_data;\n \tenum config_error_action {\n-- \ngitgitgadget\n"},{"id":"478844","messageId":"807057b6d7f0f5c77fede1b4d83550965e28d567.1687803086.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"[PATCH v4 10/12] config.c: remove config_reader from configsets","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T18:11:21Z","receivedAt":"2023-06-26T18:12:06Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nRemove the last usage of \"struct config_reader\" from configsets by\ncopying the \"kvi\" arg instead of recomputing \"kvi\" from\nconfig_reader.source. Since we no longer need to pass both \"struct\nconfig_reader\" and \"struct config_set\" in a single \"void *cb\", remove\n\"struct configset_add_data\" too.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 43 +++++++++++--------------------------------\n 1 file changed, 11 insertions(+), 32 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex a9a45e4ffe0..4782a289363 100644\n--- a/config.c\n+++ b/config.c\n@@ -2311,8 +2311,7 @@ int config_with_options(config_fn_t fn, void *data,\n \treturn ret;\n }\n \n-static void configset_iter(struct config_reader *reader, struct config_set *set,\n-\t\t\t   config_fn_t fn, void *data)\n+static void configset_iter(struct config_set *set, config_fn_t fn, void *data)\n {\n \tint i, value_index;\n \tstruct string_list *values;\n@@ -2406,7 +2405,6 @@ static int configset_find_element(struct config_set *set, const char *key,\n }\n \n static int configset_add_value(const struct key_value_info *kvi_p,\n-\t\t\t       struct config_reader *reader,\n \t\t\t       struct config_set *set, const char *key,\n \t\t\t       const char *value)\n {\n@@ -2437,11 +2435,7 @@ static int configset_add_value(const struct key_value_info *kvi_p,\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (reader->source->name) {\n-\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n-\t} else {\n-\t\tkvi_from_param(kv_info);\n-\t}\n+\tmemcpy(kv_info, kvi_p, sizeof(struct key_value_info));\n \tsi->util = kv_info;\n \n \treturn 0;\n@@ -2489,28 +2483,18 @@ void git_configset_clear(struct config_set *set)\n \tset->list.items = NULL;\n }\n \n-struct configset_add_data {\n-\tstruct config_set *config_set;\n-\tstruct config_reader *config_reader;\n-};\n-#define CONFIGSET_ADD_INIT { 0 }\n-\n static int config_set_callback(const char *key, const char *value,\n \t\t\t       const struct config_context *ctx,\n \t\t\t       void *cb)\n {\n-\tstruct configset_add_data *data = cb;\n-\tconfigset_add_value(ctx->kvi, data->config_reader, data->config_set,\n-\t\t\t    key, value);\n+\tstruct config_set *set = cb;\n+\tconfigset_add_value(ctx->kvi, set, key, value);\n \treturn 0;\n }\n \n int git_configset_add_file(struct config_set *set, const char *filename)\n {\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n-\tdata.config_reader = &the_reader;\n-\tdata.config_set = set;\n-\treturn git_config_from_file(config_set_callback, filename, &data);\n+\treturn git_config_from_file(config_set_callback, filename, set);\n }\n \n int git_configset_get_value(struct config_set *set, const char *key,\n@@ -2676,7 +2660,6 @@ int git_configset_get_pathname(struct config_set *set, const char *key, const ch\n static void repo_read_config(struct repository *repo)\n {\n \tstruct config_options opts = { 0 };\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n \n \topts.respect_includes = 1;\n \topts.commondir = repo->commondir;\n@@ -2688,10 +2671,8 @@ static void repo_read_config(struct repository *repo)\n \t\tgit_configset_clear(repo->config);\n \n \tgit_configset_init(repo->config);\n-\tdata.config_set = repo->config;\n-\tdata.config_reader = &the_reader;\n-\n-\tif (config_with_options(config_set_callback, &data, NULL, repo, &opts) < 0)\n+\tif (config_with_options(config_set_callback, repo->config, NULL,\n+\t\t\t\trepo, &opts) < 0)\n \t\t/*\n \t\t * config_with_options() normally returns only\n \t\t * zero, as most errors are fatal, and\n@@ -2723,7 +2704,7 @@ static void repo_config_clear(struct repository *repo)\n void repo_config(struct repository *repo, config_fn_t fn, void *data)\n {\n \tgit_config_check_init(repo);\n-\tconfigset_iter(&the_reader, repo->config, fn, data);\n+\tconfigset_iter(repo->config, fn, data);\n }\n \n int repo_config_get(struct repository *repo, const char *key)\n@@ -2830,19 +2811,17 @@ static void read_protected_config(void)\n \t\t.ignore_worktree = 1,\n \t\t.system_gently = 1,\n \t};\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n \n \tgit_configset_init(&protected_config);\n-\tdata.config_set = &protected_config;\n-\tdata.config_reader = &the_reader;\n-\tconfig_with_options(config_set_callback, &data, NULL, NULL, &opts);\n+\tconfig_with_options(config_set_callback, &protected_config, NULL,\n+\t\t\t    NULL, &opts);\n }\n \n void git_protected_config(config_fn_t fn, void *data)\n {\n \tif (!protected_config.hash_initialized)\n \t\tread_protected_config();\n-\tconfigset_iter(&the_reader, &protected_config, fn, data);\n+\tconfigset_iter(&protected_config, fn, data);\n }\n \n /* Functions used historically to read configuration from 'the_repository' */\n-- \ngitgitgadget\n\n"},{"id":"478846","messageId":"114723ee4a7135897b1c69a6ee859c4921bbe2f3.1687803086.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"[PATCH v4 09/12] config: pass kvi to die_bad_number()","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-26T18:11:20Z","receivedAt":"2023-06-26T18:12:25Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPlumb \"struct key_value_info\" through all code paths that end in\ndie_bad_number(), which lets us remove the helper functions that read\nanalogous values from \"struct config_reader\". As a result, nothing reads\nconfig_reader.config_kvi any more, so remove that too.\n\nIn config.c, this requires changing the signature of\ngit_configset_get_value() to 'return' \"kvi\" in an out parameter so that\ngit_configset_get_<type>() can pass it to git_config_<type>(). Only\nnumeric types will use \"kvi\", so for non-numeric types (e.g.\ngit_configset_get_string()), pass NULL to indicate that the out\nparameter isn't needed.\n\nOutside of config.c, config callbacks now need to pass \"ctx->kvi\" to any\nof the git_config_<type>() functions that parse a config string into a\nnumber type. Included is a .cocci patch to make that refactor.\n\nThe only exceptional case is builtin/config.c, where git_config_<type>()\nis called outside of a config callback (namely, on user-provided input),\nso config source information has never been available. In this case,\ndie_bad_number() defaults to a generic, but perfectly descriptive\nmessage. Let's provide a safe, non-NULL for \"kvi\" anyway, but make sure\nnot to change the message.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n archive-tar.c                              |   4 +-\n builtin/commit-graph.c                     |   4 +-\n builtin/commit.c                           |  10 +-\n builtin/config.c                           |  21 +--\n builtin/fetch.c                            |   4 +-\n builtin/fsmonitor--daemon.c                |   6 +-\n builtin/grep.c                             |   2 +-\n builtin/index-pack.c                       |   4 +-\n builtin/log.c                              |   2 +-\n builtin/pack-objects.c                     |  14 +-\n builtin/receive-pack.c                     |  10 +-\n builtin/submodule--helper.c                |   4 +-\n config.c                                   | 156 ++++++++-------------\n config.h                                   |  17 ++-\n contrib/coccinelle/git_config_number.cocci |  27 ++++\n diff.c                                     |   9 +-\n fmt-merge-msg.c                            |   2 +-\n help.c                                     |   4 +-\n http.c                                     |  10 +-\n imap-send.c                                |   2 +-\n sequencer.c                                |  22 +--\n setup.c                                    |   2 +-\n submodule-config.c                         |  13 +-\n submodule-config.h                         |   3 +-\n t/helper/test-config.c                     |   6 +-\n upload-pack.c                              |  12 +-\n worktree.c                                 |   2 +-\n 27 files changed, 190 insertions(+), 182 deletions(-)\n create mode 100644 contrib/coccinelle/git_config_number.cocci\n\ndiff --git a/archive-tar.c b/archive-tar.c\nindex ef06e516b1f..3df8af6d1b1 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -412,14 +412,14 @@ static int tar_filter_config(const char *var, const char *value,\n }\n \n static int git_tar_config(const char *var, const char *value,\n-\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"tar.umask\")) {\n \t\tif (value && !strcmp(value, \"user\")) {\n \t\t\ttar_umask = umask(0);\n \t\t\tumask(tar_umask);\n \t\t} else {\n-\t\t\ttar_umask = git_config_int(var, value);\n+\t\t\ttar_umask = git_config_int(var, value, ctx->kvi);\n \t\t}\n \t\treturn 0;\n \t}\ndiff --git a/builtin/commit-graph.c b/builtin/commit-graph.c\nindex 1185c49239a..b071c5ab646 100644\n--- a/builtin/commit-graph.c\n+++ b/builtin/commit-graph.c\n@@ -186,11 +186,11 @@ static int write_option_max_new_filters(const struct option *opt,\n }\n \n static int git_commit_graph_write_config(const char *var, const char *value,\n-\t\t\t\t\t const struct config_context *ctx UNUSED,\n+\t\t\t\t\t const struct config_context *ctx,\n \t\t\t\t\t void *cb UNUSED)\n {\n \tif (!strcmp(var, \"commitgraph.maxnewfilters\"))\n-\t\twrite_opts.max_new_filters = git_config_int(var, value);\n+\t\twrite_opts.max_new_filters = git_config_int(var, value, ctx->kvi);\n \t/*\n \t * No need to fall-back to 'git_default_config', since this was already\n \t * called in 'cmd_commit_graph()'.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 6a2b2503328..9fe691470a3 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1415,7 +1415,8 @@ static int git_status_config(const char *k, const char *v,\n \t\treturn git_column_config(k, v, \"status\", &s->colopts);\n \tif (!strcmp(k, \"status.submodulesummary\")) {\n \t\tint is_bool;\n-\t\ts->submodule_summary = git_config_bool_or_int(k, v, &is_bool);\n+\t\ts->submodule_summary = git_config_bool_or_int(k, v, ctx->kvi,\n+\t\t\t\t\t\t\t      &is_bool);\n \t\tif (is_bool && s->submodule_summary)\n \t\t\ts->submodule_summary = -1;\n \t\treturn 0;\n@@ -1475,11 +1476,11 @@ static int git_status_config(const char *k, const char *v,\n \t}\n \tif (!strcmp(k, \"diff.renamelimit\")) {\n \t\tif (s->rename_limit == -1)\n-\t\t\ts->rename_limit = git_config_int(k, v);\n+\t\t\ts->rename_limit = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"status.renamelimit\")) {\n-\t\ts->rename_limit = git_config_int(k, v);\n+\t\ts->rename_limit = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"diff.renames\")) {\n@@ -1625,7 +1626,8 @@ static int git_commit_config(const char *k, const char *v,\n \t}\n \tif (!strcmp(k, \"commit.verbose\")) {\n \t\tint is_bool;\n-\t\tconfig_commit_verbose = git_config_bool_or_int(k, v, &is_bool);\n+\t\tconfig_commit_verbose = git_config_bool_or_int(k, v, ctx->kvi,\n+\t\t\t\t\t\t\t       &is_bool);\n \t\treturn 0;\n \t}\n \ndiff --git a/builtin/config.c b/builtin/config.c\nindex 9b9f5527311..680269d263c 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -262,13 +262,14 @@ static int format_config(struct strbuf *buf, const char *key_,\n \n \t\tif (type == TYPE_INT)\n \t\t\tstrbuf_addf(buf, \"%\"PRId64,\n-\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\"));\n+\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\", kvi));\n \t\telse if (type == TYPE_BOOL)\n \t\t\tstrbuf_addstr(buf, git_config_bool(key_, value_) ?\n \t\t\t\t      \"true\" : \"false\");\n \t\telse if (type == TYPE_BOOL_OR_INT) {\n \t\t\tint is_bool, v;\n-\t\t\tv = git_config_bool_or_int(key_, value_, &is_bool);\n+\t\t\tv = git_config_bool_or_int(key_, value_, kvi,\n+\t\t\t\t\t\t   &is_bool);\n \t\t\tif (is_bool)\n \t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n \t\t\telse\n@@ -424,7 +425,8 @@ free_strings:\n \treturn ret;\n }\n \n-static char *normalize_value(const char *key, const char *value)\n+static char *normalize_value(const char *key, const char *value,\n+\t\t\t     struct key_value_info *kvi)\n {\n \tif (!value)\n \t\treturn NULL;\n@@ -439,12 +441,12 @@ static char *normalize_value(const char *key, const char *value)\n \t\t */\n \t\treturn xstrdup(value);\n \tif (type == TYPE_INT)\n-\t\treturn xstrfmt(\"%\"PRId64, git_config_int64(key, value));\n+\t\treturn xstrfmt(\"%\"PRId64, git_config_int64(key, value, kvi));\n \tif (type == TYPE_BOOL)\n \t\treturn xstrdup(git_config_bool(key, value) ?  \"true\" : \"false\");\n \tif (type == TYPE_BOOL_OR_INT) {\n \t\tint is_bool, v;\n-\t\tv = git_config_bool_or_int(key, value, &is_bool);\n+\t\tv = git_config_bool_or_int(key, value, kvi, &is_bool);\n \t\tif (!is_bool)\n \t\t\treturn xstrfmt(\"%d\", v);\n \t\telse\n@@ -674,6 +676,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \tchar *value = NULL;\n \tint flags = 0;\n \tint ret = 0;\n+\tstruct key_value_info default_kvi = KVI_INIT;\n \n \tgiven_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));\n \n@@ -891,7 +894,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_SET) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 2);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n \t\tret = git_config_set_in_file_gently(given_config_source.file, argv[0], value);\n \t\tif (ret == CONFIG_NOTHING_SET)\n \t\t\terror(_(\"cannot overwrite multiple values with a single value\\n\"\n@@ -900,7 +903,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_SET_ALL) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 3);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value, argv[2],\n \t\t\t\t\t\t\t     flags);\n@@ -908,7 +911,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_ADD) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 2);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value,\n \t\t\t\t\t\t\t     CONFIG_REGEX_NONE,\n@@ -917,7 +920,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_REPLACE_ALL) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 3);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value, argv[2],\n \t\t\t\t\t\t\t     flags | CONFIG_FLAGS_MULTI_REPLACE);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex a4aa0fbb8f5..fe86dfeb62a 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -137,7 +137,7 @@ static int git_fetch_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"submodule.fetchjobs\")) {\n-\t\tfetch_config->submodule_fetch_jobs = parse_submodule_fetchjobs(k, v);\n+\t\tfetch_config->submodule_fetch_jobs = parse_submodule_fetchjobs(k, v, ctx->kvi);\n \t\treturn 0;\n \t} else if (!strcmp(k, \"fetch.recursesubmodules\")) {\n \t\tfetch_config->recurse_submodules = parse_fetch_recurse_submodules_arg(k, v);\n@@ -145,7 +145,7 @@ static int git_fetch_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"fetch.parallel\")) {\n-\t\tfetch_config->parallel = git_config_int(k, v);\n+\t\tfetch_config->parallel = git_config_int(k, v, ctx->kvi);\n \t\tif (fetch_config->parallel < 0)\n \t\t\tdie(_(\"fetch.parallel cannot be negative\"));\n \t\tif (!fetch_config->parallel)\ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex 91a776e2f17..6d2826b07da 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -41,7 +41,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, FSMONITOR__IPC_THREADS)) {\n-\t\tint i = git_config_int(var, value);\n+\t\tint i = git_config_int(var, value, ctx->kvi);\n \t\tif (i < 1)\n \t\t\treturn error(_(\"value of '%s' out of range: %d\"),\n \t\t\t\t     FSMONITOR__IPC_THREADS, i);\n@@ -50,7 +50,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, FSMONITOR__START_TIMEOUT)) {\n-\t\tint i = git_config_int(var, value);\n+\t\tint i = git_config_int(var, value, ctx->kvi);\n \t\tif (i < 0)\n \t\t\treturn error(_(\"value of '%s' out of range: %d\"),\n \t\t\t\t     FSMONITOR__START_TIMEOUT, i);\n@@ -60,7 +60,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \n \tif (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) {\n \t\tint is_bool;\n-\t\tint i = git_config_bool_or_int(var, value, &is_bool);\n+\t\tint i = git_config_bool_or_int(var, value, ctx->kvi, &is_bool);\n \t\tif (i < 0)\n \t\t\treturn error(_(\"value of '%s' not bool or int: %d\"),\n \t\t\t\t     var, i);\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 757d52b94ec..3a464e6faab 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -301,7 +301,7 @@ static int grep_cmd_config(const char *var, const char *value,\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\n-\t\tnum_threads = git_config_int(var, value);\n+\t\tnum_threads = git_config_int(var, value, ctx->kvi);\n \t\tif (num_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d) for %s\"),\n \t\t\t    num_threads, var);\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex de8884ea5c2..e428f6d9a4a 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1587,13 +1587,13 @@ static int git_index_pack_config(const char *k, const char *v,\n \tstruct pack_idx_option *opts = cb;\n \n \tif (!strcmp(k, \"pack.indexversion\")) {\n-\t\topts->version = git_config_int(k, v);\n+\t\topts->version = git_config_int(k, v, ctx->kvi);\n \t\tif (opts->version > 2)\n \t\t\tdie(_(\"bad pack.indexVersion=%\"PRIu32), opts->version);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.threads\")) {\n-\t\tnr_threads = git_config_int(k, v);\n+\t\tnr_threads = git_config_int(k, v, ctx->kvi);\n \t\tif (nr_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d)\"),\n \t\t\t    nr_threads);\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 09d6a13075b..c7337354aaf 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -574,7 +574,7 @@ static int git_log_config(const char *var, const char *value,\n \tif (!strcmp(var, \"format.subjectprefix\"))\n \t\treturn git_config_string(&fmt_patch_subject_prefix, var, value);\n \tif (!strcmp(var, \"format.filenamemaxlength\")) {\n-\t\tfmt_patch_name_max = git_config_int(var, value);\n+\t\tfmt_patch_name_max = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"format.encodeemailheaders\")) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 34aa0b483a0..38054a38b2b 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3139,23 +3139,23 @@ static int git_pack_config(const char *k, const char *v,\n \t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(k, \"pack.window\")) {\n-\t\twindow = git_config_int(k, v);\n+\t\twindow = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.windowmemory\")) {\n-\t\twindow_memory_limit = git_config_ulong(k, v);\n+\t\twindow_memory_limit = git_config_ulong(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.depth\")) {\n-\t\tdepth = git_config_int(k, v);\n+\t\tdepth = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.deltacachesize\")) {\n-\t\tmax_delta_cache_size = git_config_int(k, v);\n+\t\tmax_delta_cache_size = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.deltacachelimit\")) {\n-\t\tcache_max_small_delta_size = git_config_int(k, v);\n+\t\tcache_max_small_delta_size = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.writebitmaphashcache\")) {\n@@ -3181,7 +3181,7 @@ static int git_pack_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.threads\")) {\n-\t\tdelta_search_threads = git_config_int(k, v);\n+\t\tdelta_search_threads = git_config_int(k, v, ctx->kvi);\n \t\tif (delta_search_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d)\"),\n \t\t\t    delta_search_threads);\n@@ -3192,7 +3192,7 @@ static int git_pack_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.indexversion\")) {\n-\t\tpack_idx_opts.version = git_config_int(k, v);\n+\t\tpack_idx_opts.version = git_config_int(k, v, ctx->kvi);\n \t\tif (pack_idx_opts.version > 2)\n \t\t\tdie(_(\"bad pack.indexVersion=%\"PRIu32),\n \t\t\t    pack_idx_opts.version);\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 94d9898aff7..98f6f0038f0 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -158,12 +158,12 @@ static int receive_pack_config(const char *var, const char *value,\n \t}\n \n \tif (strcmp(var, \"receive.unpacklimit\") == 0) {\n-\t\treceive_unpack_limit = git_config_int(var, value);\n+\t\treceive_unpack_limit = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (strcmp(var, \"transfer.unpacklimit\") == 0) {\n-\t\ttransfer_unpack_limit = git_config_int(var, value);\n+\t\ttransfer_unpack_limit = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n@@ -231,7 +231,7 @@ static int receive_pack_config(const char *var, const char *value,\n \t\treturn git_config_string(&cert_nonce_seed, var, value);\n \n \tif (strcmp(var, \"receive.certnonceslop\") == 0) {\n-\t\tnonce_stamp_slop_limit = git_config_ulong(var, value);\n+\t\tnonce_stamp_slop_limit = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n@@ -246,12 +246,12 @@ static int receive_pack_config(const char *var, const char *value,\n \t}\n \n \tif (strcmp(var, \"receive.keepalive\") == 0) {\n-\t\tkeepalive_in_sec = git_config_int(var, value);\n+\t\tkeepalive_in_sec = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (strcmp(var, \"receive.maxinputsize\") == 0) {\n-\t\tmax_input_size = git_config_int64(var, value);\n+\t\tmax_input_size = git_config_int64(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex f8e9d85e77a..57185cf5f3d 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2192,13 +2192,13 @@ static int update_clone_task_finished(int result,\n }\n \n static int git_update_clone_config(const char *var, const char *value,\n-\t\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t\t   const struct config_context *ctx,\n \t\t\t\t   void *cb)\n {\n \tint *max_jobs = cb;\n \n \tif (!strcmp(var, \"submodule.fetchjobs\"))\n-\t\t*max_jobs = parse_submodule_fetchjobs(var, value);\n+\t\t*max_jobs = parse_submodule_fetchjobs(var, value, ctx->kvi);\n \treturn 0;\n }\n \ndiff --git a/config.c b/config.c\nindex 8546fea370e..a9a45e4ffe0 100644\n--- a/config.c\n+++ b/config.c\n@@ -73,18 +73,8 @@ struct config_reader {\n \t *\n \t * The \"source\" variable will be non-NULL only when we are actually\n \t * parsing a real config source (file, blob, cmdline, etc).\n-\t *\n-\t * The \"config_kvi\" variable will be non-NULL only when we are feeding\n-\t * cached config from a configset into a callback.\n-\t *\n-\t * They cannot be non-NULL at the same time. If they are both NULL, then\n-\t * we aren't parsing anything (and depending on the function looking at\n-\t * the variables, it's either a bug for it to be called in the first\n-\t * place, or it's a function which can be reused for non-config\n-\t * purposes, and should fall back to some sane behavior).\n \t */\n \tstruct config_source *source;\n-\tstruct key_value_info *config_kvi;\n };\n /*\n  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n@@ -96,8 +86,6 @@ static struct config_reader the_reader;\n static inline void config_reader_push_source(struct config_reader *reader,\n \t\t\t\t\t     struct config_source *top)\n {\n-\tif (reader->config_kvi)\n-\t\tBUG(\"source should not be set while iterating a config set\");\n \ttop->prev = reader->source;\n \treader->source = top;\n }\n@@ -112,12 +100,6 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\n \treturn ret;\n }\n \n-static inline void config_reader_set_kvi(struct config_reader *reader,\n-\t\t\t\t\t struct key_value_info *kvi)\n-{\n-\treader->config_kvi = kvi;\n-}\n-\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -1346,80 +1328,78 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)\n \treturn 1;\n }\n \n-static int reader_config_name(struct config_reader *reader, const char **out);\n-static int reader_origin_type(struct config_reader *reader,\n-\t\t\t      enum config_origin_type *type);\n NORETURN\n-static void die_bad_number(struct config_reader *reader, const char *name,\n-\t\t\t   const char *value)\n+static void die_bad_number(const char *name, const char *value,\n+\t\t\t   const struct key_value_info *kvi)\n {\n \tconst char *error_type = (errno == ERANGE) ?\n \t\tN_(\"out of range\") : N_(\"invalid unit\");\n \tconst char *bad_numeric = N_(\"bad numeric config value '%s' for '%s': %s\");\n-\tconst char *config_name = NULL;\n-\tenum config_origin_type config_origin = CONFIG_ORIGIN_UNKNOWN;\n+\n+\tif (!kvi)\n+\t\tBUG(\"kvi should not be NULL\");\n \n \tif (!value)\n \t\tvalue = \"\";\n \n-\t/* Ignoring the return value is okay since we handle missing values. */\n-\treader_config_name(reader, &config_name);\n-\treader_origin_type(reader, &config_origin);\n-\n-\tif (!config_name)\n+\tif (!kvi->filename)\n \t\tdie(_(bad_numeric), value, name, _(error_type));\n \n-\tswitch (config_origin) {\n+\tswitch (kvi->origin_type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in blob %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_FILE:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in file %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_STDIN:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in standard input: %s\"),\n \t\t    value, name, _(error_type));\n \tcase CONFIG_ORIGIN_SUBMODULE_BLOB:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in submodule-blob %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_CMDLINE:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in command line %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tdefault:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \t}\n }\n \n-int git_config_int(const char *name, const char *value)\n+int git_config_int(const char *name, const char *value,\n+\t\t   const struct key_value_info *kvi)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-int64_t git_config_int64(const char *name, const char *value)\n+int64_t git_config_int64(const char *name, const char *value,\n+\t\t\t const struct key_value_info *kvi)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-unsigned long git_config_ulong(const char *name, const char *value)\n+unsigned long git_config_ulong(const char *name, const char *value,\n+\t\t\t       const struct key_value_info *kvi)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-ssize_t git_config_ssize_t(const char *name, const char *value)\n+ssize_t git_config_ssize_t(const char *name, const char *value,\n+\t\t\t   const struct key_value_info *kvi)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n@@ -1524,7 +1504,8 @@ int git_parse_maybe_bool(const char *value)\n \treturn -1;\n }\n \n-int git_config_bool_or_int(const char *name, const char *value, int *is_bool)\n+int git_config_bool_or_int(const char *name, const char *value,\n+\t\t\t   const struct key_value_info *kvi, int *is_bool)\n {\n \tint v = git_parse_maybe_bool_text(value);\n \tif (0 <= v) {\n@@ -1532,7 +1513,7 @@ int git_config_bool_or_int(const char *name, const char *value, int *is_bool)\n \t\treturn v;\n \t}\n \t*is_bool = 0;\n-\treturn git_config_int(name, value);\n+\treturn git_config_int(name, value, kvi);\n }\n \n int git_config_bool(const char *name, const char *value)\n@@ -1658,7 +1639,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t\telse if (!git_parse_maybe_bool_text(value))\n \t\t\tdefault_abbrev = the_hash_algo->hexsz;\n \t\telse {\n-\t\t\tint abbrev = git_config_int(var, value);\n+\t\t\tint abbrev = git_config_int(var, value, ctx->kvi);\n \t\t\tif (abbrev < minimum_abbrev || abbrev > the_hash_algo->hexsz)\n \t\t\t\treturn error(_(\"abbrev length out of range: %d\"), abbrev);\n \t\t\tdefault_abbrev = abbrev;\n@@ -1670,7 +1651,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t\treturn set_disambiguate_hint_config(var, value);\n \n \tif (!strcmp(var, \"core.loosecompression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, ctx->kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -1681,7 +1662,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.compression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, ctx->kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -1695,7 +1676,7 @@ static int git_default_core_config(const char *var, const char *value,\n \n \tif (!strcmp(var, \"core.packedgitwindowsize\")) {\n \t\tint pgsz_x2 = getpagesize() * 2;\n-\t\tpacked_git_window_size = git_config_ulong(var, value);\n+\t\tpacked_git_window_size = git_config_ulong(var, value, ctx->kvi);\n \n \t\t/* This value must be multiple of (pagesize * 2) */\n \t\tpacked_git_window_size /= pgsz_x2;\n@@ -1706,17 +1687,17 @@ static int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.bigfilethreshold\")) {\n-\t\tbig_file_threshold = git_config_ulong(var, value);\n+\t\tbig_file_threshold = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"core.packedgitlimit\")) {\n-\t\tpacked_git_limit = git_config_ulong(var, value);\n+\t\tpacked_git_limit = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"core.deltabasecachelimit\")) {\n-\t\tdelta_base_cache_limit = git_config_ulong(var, value);\n+\t\tdelta_base_cache_limit = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n@@ -1995,12 +1976,12 @@ int git_default_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"pack.packsizelimit\")) {\n-\t\tpack_size_limit_cfg = git_config_ulong(var, value);\n+\t\tpack_size_limit_cfg = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"pack.compression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, ctx->kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -2344,13 +2325,11 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \t\tvalue_index = list->items[i].value_index;\n \t\tvalues = &entry->value_list;\n \n-\t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n \t\tctx.kvi = values->items[value_index].util;\n \t\tif (fn(entry->key, values->items[value_index].string, &ctx, data) < 0)\n \t\t\tgit_die_config_linenr(entry->key,\n \t\t\t\t\t      ctx.kvi->filename,\n \t\t\t\t\t      ctx.kvi->linenr);\n-\t\tconfig_reader_set_kvi(reader, NULL);\n \t}\n }\n \n@@ -2534,11 +2513,12 @@ int git_configset_add_file(struct config_set *set, const char *filename)\n \treturn git_config_from_file(config_set_callback, filename, &data);\n }\n \n-int git_configset_get_value(struct config_set *set, const char *key, const char **value)\n+int git_configset_get_value(struct config_set *set, const char *key,\n+\t\t\t    const char **value, struct key_value_info *kvi)\n {\n \tconst struct string_list *values = NULL;\n \tint ret;\n-\n+\tstruct string_list_item item;\n \t/*\n \t * Follows \"last one wins\" semantic, i.e., if there are multiple matches for the\n \t * queried key in the files of the configset, the value returned will be the last\n@@ -2548,7 +2528,10 @@ int git_configset_get_value(struct config_set *set, const char *key, const char\n \t\treturn ret;\n \n \tassert(values->nr > 0);\n-\t*value = values->items[values->nr - 1].string;\n+\titem = values->items[values->nr - 1];\n+\t*value = item.string;\n+\tif (kvi)\n+\t\t*kvi = *((struct key_value_info *)item.util);\n \treturn 0;\n }\n \n@@ -2601,7 +2584,7 @@ int git_configset_get(struct config_set *set, const char *key)\n int git_configset_get_string(struct config_set *set, const char *key, char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value))\n+\tif (!git_configset_get_value(set, key, &value, NULL))\n \t\treturn git_config_string((const char **)dest, key, value);\n \telse\n \t\treturn 1;\n@@ -2611,7 +2594,7 @@ static int git_configset_get_string_tmp(struct config_set *set, const char *key,\n \t\t\t\t\tconst char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(key);\n \t\t*dest = value;\n@@ -2624,8 +2607,10 @@ static int git_configset_get_string_tmp(struct config_set *set, const char *key,\n int git_configset_get_int(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_int(key, value);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_int(key, value, &kvi);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2634,8 +2619,10 @@ int git_configset_get_int(struct config_set *set, const char *key, int *dest)\n int git_configset_get_ulong(struct config_set *set, const char *key, unsigned long *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_ulong(key, value);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_ulong(key, value, &kvi);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2644,7 +2631,7 @@ int git_configset_get_ulong(struct config_set *set, const char *key, unsigned lo\n int git_configset_get_bool(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\t*dest = git_config_bool(key, value);\n \t\treturn 0;\n \t} else\n@@ -2655,8 +2642,10 @@ int git_configset_get_bool_or_int(struct config_set *set, const char *key,\n \t\t\t\tint *is_bool, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_bool_or_int(key, value, is_bool);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_bool_or_int(key, value, &kvi, is_bool);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2665,7 +2654,7 @@ int git_configset_get_bool_or_int(struct config_set *set, const char *key,\n int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\t*dest = git_parse_maybe_bool(value);\n \t\tif (*dest == -1)\n \t\t\treturn -1;\n@@ -2677,7 +2666,7 @@ int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *d\n int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value))\n+\tif (!git_configset_get_value(set, key, &value, NULL))\n \t\treturn git_config_pathname(dest, key, value);\n \telse\n \t\treturn 1;\n@@ -2747,7 +2736,7 @@ int repo_config_get_value(struct repository *repo,\n \t\t\t  const char *key, const char **value)\n {\n \tgit_config_check_init(repo);\n-\treturn git_configset_get_value(repo->config, key, value);\n+\treturn git_configset_get_value(repo->config, key, value, NULL);\n }\n \n int repo_config_get_value_multi(struct repository *repo, const char *key,\n@@ -3987,18 +3976,6 @@ int parse_config_key(const char *var,\n \treturn 0;\n }\n \n-static int reader_origin_type(struct config_reader *reader,\n-\t\t\t      enum config_origin_type *type)\n-{\n-\tif (the_reader.config_kvi)\n-\t\t*type = reader->config_kvi->origin_type;\n-\telse if(the_reader.source)\n-\t\t*type = reader->source->origin_type;\n-\telse\n-\t\treturn 1;\n-\treturn 0;\n-}\n-\n const char *config_origin_type_name(enum config_origin_type type)\n {\n \tswitch (type) {\n@@ -4037,17 +4014,6 @@ const char *config_scope_name(enum config_scope scope)\n \t}\n }\n \n-static int reader_config_name(struct config_reader *reader, const char **out)\n-{\n-\tif (the_reader.config_kvi)\n-\t\t*out = reader->config_kvi->filename;\n-\telse if (the_reader.source)\n-\t\t*out = reader->source->name;\n-\telse\n-\t\treturn 1;\n-\treturn 0;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex 82eeba94e71..bd366ddb5ef 100644\n--- a/config.h\n+++ b/config.h\n@@ -249,22 +249,26 @@ int git_parse_maybe_bool(const char *);\n  * Parse the string to an integer, including unit factors. Dies on error;\n  * otherwise, returns the parsed result.\n  */\n-int git_config_int(const char *, const char *);\n+int git_config_int(const char *, const char *, const struct key_value_info *);\n \n-int64_t git_config_int64(const char *, const char *);\n+int64_t git_config_int64(const char *, const char *,\n+\t\t\t const struct key_value_info *);\n \n /**\n  * Identical to `git_config_int`, but for unsigned longs.\n  */\n-unsigned long git_config_ulong(const char *, const char *);\n+unsigned long git_config_ulong(const char *, const char *,\n+\t\t\t       const struct key_value_info *);\n \n-ssize_t git_config_ssize_t(const char *, const char *);\n+ssize_t git_config_ssize_t(const char *, const char *,\n+\t\t\t   const struct key_value_info *);\n \n /**\n  * Same as `git_config_bool`, except that integers are returned as-is, and\n  * an `is_bool` flag is unset.\n  */\n-int git_config_bool_or_int(const char *, const char *, int *);\n+int git_config_bool_or_int(const char *, const char *,\n+\t\t\t   const struct key_value_info *, int *);\n \n /**\n  * Parse a string into a boolean value, respecting keywords like \"true\" and\n@@ -529,7 +533,8 @@ int git_configset_get(struct config_set *cs, const char *key);\n  * touching `value`. The caller should not free or modify `value`, as it\n  * is owned by the cache.\n  */\n-int git_configset_get_value(struct config_set *cs, const char *key, const char **dest);\n+int git_configset_get_value(struct config_set *cs, const char *key,\n+\t\t\t    const char **dest, struct key_value_info *kvi);\n \n int git_configset_get_string(struct config_set *cs, const char *key, char **dest);\n int git_configset_get_int(struct config_set *cs, const char *key, int *dest);\ndiff --git a/contrib/coccinelle/git_config_number.cocci b/contrib/coccinelle/git_config_number.cocci\nnew file mode 100644\nindex 00000000000..7b57dceefe6\n--- /dev/null\n+++ b/contrib/coccinelle/git_config_number.cocci\n@@ -0,0 +1,27 @@\n+@@\n+identifier C1, C2, C3;\n+@@\n+(\n+(\n+git_config_int\n+|\n+git_config_int64\n+|\n+git_config_ulong\n+|\n+git_config_ssize_t\n+)\n+  (C1, C2\n++ , ctx->kvi\n+  )\n+|\n+(\n+git_configset_get_value\n+|\n+git_config_bool_or_int\n+)\n+  (C1, C2\n++ , ctx->kvi\n+ , C3\n+  )\n+)\ndiff --git a/diff.c b/diff.c\nindex 0e382c8f7f0..460211e7d40 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -379,13 +379,14 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.context\")) {\n-\t\tdiff_context_default = git_config_int(var, value);\n+\t\tdiff_context_default = git_config_int(var, value, ctx->kvi);\n \t\tif (diff_context_default < 0)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.interhunkcontext\")) {\n-\t\tdiff_interhunk_context_default = git_config_int(var, value);\n+\t\tdiff_interhunk_context_default = git_config_int(var, value,\n+\t\t\t\t\t\t\t\tctx->kvi);\n \t\tif (diff_interhunk_context_default < 0)\n \t\t\treturn -1;\n \t\treturn 0;\n@@ -411,7 +412,7 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.statgraphwidth\")) {\n-\t\tdiff_stat_graph_width = git_config_int(var, value);\n+\t\tdiff_stat_graph_width = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.external\"))\n@@ -450,7 +451,7 @@ int git_diff_basic_config(const char *var, const char *value,\n \tconst char *name;\n \n \tif (!strcmp(var, \"diff.renamelimit\")) {\n-\t\tdiff_rename_limit_default = git_config_int(var, value);\n+\t\tdiff_rename_limit_default = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 10137444321..3ae59bde2f2 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -25,7 +25,7 @@ int fmt_merge_msg_config(const char *key, const char *value,\n {\n \tif (!strcmp(key, \"merge.log\") || !strcmp(key, \"merge.summary\")) {\n \t\tint is_bool;\n-\t\tmerge_log_config = git_config_bool_or_int(key, value, &is_bool);\n+\t\tmerge_log_config = git_config_bool_or_int(key, value, ctx->kvi, &is_bool);\n \t\tif (!is_bool && merge_log_config < 0)\n \t\t\treturn error(\"%s: negative length %s\", key, value);\n \t\tif (is_bool && merge_log_config)\ndiff --git a/help.c b/help.c\nindex ac0ae5ac0dc..389382b1482 100644\n--- a/help.c\n+++ b/help.c\n@@ -545,7 +545,7 @@ static struct cmdnames aliases;\n #define AUTOCORRECT_IMMEDIATELY (-1)\n \n static int git_unknown_cmd_config(const char *var, const char *value,\n-\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t  const struct config_context *ctx,\n \t\t\t\t  void *cb UNUSED)\n {\n \tconst char *p;\n@@ -560,7 +560,7 @@ static int git_unknown_cmd_config(const char *var, const char *value,\n \t\t} else if (!strcmp(value, \"prompt\")) {\n \t\t\tautocorrect = AUTOCORRECT_PROMPT;\n \t\t} else {\n-\t\t\tint v = git_config_int(var, value);\n+\t\t\tint v = git_config_int(var, value, ctx->kvi);\n \t\t\tautocorrect = (v < 0)\n \t\t\t\t? AUTOCORRECT_IMMEDIATELY : v;\n \t\t}\ndiff --git a/http.c b/http.c\nindex 762502828c9..b12b234bde1 100644\n--- a/http.c\n+++ b/http.c\n@@ -414,21 +414,21 @@ static int http_options(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(\"http.minsessions\", var)) {\n-\t\tmin_curl_sessions = git_config_int(var, value);\n+\t\tmin_curl_sessions = git_config_int(var, value, ctx->kvi);\n \t\tif (min_curl_sessions > 1)\n \t\t\tmin_curl_sessions = 1;\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.maxrequests\", var)) {\n-\t\tmax_requests = git_config_int(var, value);\n+\t\tmax_requests = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.lowspeedlimit\", var)) {\n-\t\tcurl_low_speed_limit = (long)git_config_int(var, value);\n+\t\tcurl_low_speed_limit = (long)git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.lowspeedtime\", var)) {\n-\t\tcurl_low_speed_time = (long)git_config_int(var, value);\n+\t\tcurl_low_speed_time = (long)git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n@@ -464,7 +464,7 @@ static int http_options(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(\"http.postbuffer\", var)) {\n-\t\thttp_post_buffer = git_config_ssize_t(var, value);\n+\t\thttp_post_buffer = git_config_ssize_t(var, value, ctx->kvi);\n \t\tif (http_post_buffer < 0)\n \t\t\twarning(_(\"negative value for http.postBuffer; defaulting to %d\"), LARGE_PACKET_MAX);\n \t\tif (http_post_buffer < LARGE_PACKET_MAX)\ndiff --git a/imap-send.c b/imap-send.c\nindex 47777e76861..3518a4ace6c 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1342,7 +1342,7 @@ static int git_imap_config(const char *var, const char *val,\n \telse if (!strcmp(\"imap.authmethod\", var))\n \t\treturn git_config_string(&server.auth_method, var, val);\n \telse if (!strcmp(\"imap.port\", var))\n-\t\tserver.port = git_config_int(var, val);\n+\t\tserver.port = git_config_int(var, val, ctx->kvi);\n \telse if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\tgit_die_config(\"imap.host\", \"Missing value for 'imap.host'\");\ndiff --git a/sequencer.c b/sequencer.c\nindex 34754d17596..a0aaee3056d 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2883,7 +2883,7 @@ static int git_config_string_dup(char **dest,\n }\n \n static int populate_opts_cb(const char *key, const char *value,\n-\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    const struct config_context *ctx,\n \t\t\t    void *data)\n {\n \tstruct replay_opts *opts = data;\n@@ -2892,26 +2892,26 @@ static int populate_opts_cb(const char *key, const char *value,\n \tif (!value)\n \t\terror_flag = 0;\n \telse if (!strcmp(key, \"options.no-commit\"))\n-\t\topts->no_commit = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->no_commit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.edit\"))\n-\t\topts->edit = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->edit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-empty\"))\n \t\topts->allow_empty =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-empty-message\"))\n \t\topts->allow_empty_message =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.keep-redundant-commits\"))\n \t\topts->keep_redundant_commits =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.signoff\"))\n-\t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->signoff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.record-origin\"))\n-\t\topts->record_origin = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->record_origin = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-ff\"))\n-\t\topts->allow_ff = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->allow_ff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.mainline\"))\n-\t\topts->mainline = git_config_int(key, value);\n+\t\topts->mainline = git_config_int(key, value, ctx->kvi);\n \telse if (!strcmp(key, \"options.strategy\"))\n \t\tgit_config_string_dup(&opts->strategy, key, value);\n \telse if (!strcmp(key, \"options.gpg-sign\"))\n@@ -2920,7 +2920,7 @@ static int populate_opts_cb(const char *key, const char *value,\n \t\tstrvec_push(&opts->xopts, value);\n \t} else if (!strcmp(key, \"options.allow-rerere-auto\"))\n \t\topts->allow_rerere_auto =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag) ?\n+\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag) ?\n \t\t\t\tRERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;\n \telse if (!strcmp(key, \"options.default-msg-cleanup\")) {\n \t\topts->explicit_cleanup = 1;\ndiff --git a/setup.c b/setup.c\nindex fadba5bab4b..ca80fb464bb 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -597,7 +597,7 @@ static int check_repo_format(const char *var, const char *value,\n \tconst char *ext;\n \n \tif (strcmp(var, \"core.repositoryformatversion\") == 0)\n-\t\tdata->version = git_config_int(var, value);\n+\t\tdata->version = git_config_int(var, value, ctx->kvi);\n \telse if (skip_prefix(var, \"extensions.\", &ext)) {\n \t\tswitch (handle_extension_v0(var, value, ext, data)) {\n \t\tcase EXTENSION_ERROR:\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 3f25bd13674..54be580e2a9 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -304,9 +304,10 @@ static int parse_fetch_recurse(const char *opt, const char *arg,\n \t}\n }\n \n-int parse_submodule_fetchjobs(const char *var, const char *value)\n+int parse_submodule_fetchjobs(const char *var, const char *value,\n+\t\t\t      const struct key_value_info *kvi)\n {\n-\tint fetchjobs = git_config_int(var, value);\n+\tint fetchjobs = git_config_int(var, value, kvi);\n \tif (fetchjobs < 0)\n \t\tdie(_(\"negative values not allowed for submodule.fetchJobs\"));\n \tif (!fetchjobs)\n@@ -849,14 +850,14 @@ struct fetch_config {\n };\n \n static int gitmodules_fetch_config(const char *var, const char *value,\n-\t\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t\t   const struct config_context *ctx,\n \t\t\t\t   void *cb)\n {\n \tstruct fetch_config *config = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\")) {\n \t\tif (config->max_children)\n \t\t\t*(config->max_children) =\n-\t\t\t\tparse_submodule_fetchjobs(var, value);\n+\t\t\t\tparse_submodule_fetchjobs(var, value, ctx->kvi);\n \t\treturn 0;\n \t} else if (!strcmp(var, \"fetch.recursesubmodules\")) {\n \t\tif (config->recurse_submodules)\n@@ -878,12 +879,12 @@ void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)\n }\n \n static int gitmodules_update_clone_config(const char *var, const char *value,\n-\t\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t\t  const struct config_context *ctx,\n \t\t\t\t\t  void *cb)\n {\n \tint *max_jobs = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\"))\n-\t\t*max_jobs = parse_submodule_fetchjobs(var, value);\n+\t\t*max_jobs = parse_submodule_fetchjobs(var, value, ctx->kvi);\n \treturn 0;\n }\n \ndiff --git a/submodule-config.h b/submodule-config.h\nindex c2045875bbb..2a37689cc27 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -50,7 +50,8 @@ struct repository;\n \n void submodule_cache_free(struct submodule_cache *cache);\n \n-int parse_submodule_fetchjobs(const char *var, const char *value);\n+int parse_submodule_fetchjobs(const char *var, const char *value,\n+\t\t\t      const struct key_value_info *kvi);\n int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);\n struct option;\n int option_fetch_parse_recurse_submodules(const struct option *opt,\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 3f4c3678318..ed444ca4c25 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -63,12 +63,12 @@ static int iterate_cb(const char *var, const char *value,\n }\n \n static int parse_int_cb(const char *var, const char *value,\n-\t\t\tconst struct config_context *ctx UNUSED, void *data)\n+\t\t\tconst struct config_context *ctx, void *data)\n {\n \tconst char *key_to_match = data;\n \n \tif (!strcmp(key_to_match, var)) {\n-\t\tint parsed = git_config_int(value, value);\n+\t\tint parsed = git_config_int(value, value, ctx->kvi);\n \t\tprintf(\"%d\\n\", parsed);\n \t}\n \treturn 0;\n@@ -182,7 +182,7 @@ int cmd__config(int argc, const char **argv)\n \t\t\t\tgoto exit2;\n \t\t\t}\n \t\t}\n-\t\tif (!git_configset_get_value(&cs, argv[2], &v)) {\n+\t\tif (!git_configset_get_value(&cs, argv[2], &v, NULL)) {\n \t\t\tif (!v)\n \t\t\t\tprintf(\"(NULL)\\n\");\n \t\t\telse\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 951fd1f9c25..c03415a5460 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1275,7 +1275,8 @@ static int find_symref(const char *refname,\n }\n \n static int parse_object_filter_config(const char *var, const char *value,\n-\t\t\t\t       struct upload_pack_data *data)\n+\t\t\t\t      const struct key_value_info *kvi,\n+\t\t\t\t      struct upload_pack_data *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *sub, *key;\n@@ -1302,7 +1303,8 @@ static int parse_object_filter_config(const char *var, const char *value,\n \t\t}\n \t\tstring_list_insert(&data->allowed_filters, buf.buf)->util =\n \t\t\t(void *)(intptr_t)1;\n-\t\tdata->tree_filter_max_depth = git_config_ulong(var, value);\n+\t\tdata->tree_filter_max_depth = git_config_ulong(var, value,\n+\t\t\t\t\t\t\t       kvi);\n \t}\n \n \tstrbuf_release(&buf);\n@@ -1310,7 +1312,7 @@ static int parse_object_filter_config(const char *var, const char *value,\n }\n \n static int upload_pack_config(const char *var, const char *value,\n-\t\t\t      const struct config_context *ctx UNUSED,\n+\t\t\t      const struct config_context *ctx,\n \t\t\t      void *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n@@ -1331,7 +1333,7 @@ static int upload_pack_config(const char *var, const char *value,\n \t\telse\n \t\t\tdata->allow_uor &= ~ALLOW_ANY_SHA1;\n \t} else if (!strcmp(\"uploadpack.keepalive\", var)) {\n-\t\tdata->keepalive = git_config_int(var, value);\n+\t\tdata->keepalive = git_config_int(var, value, ctx->kvi);\n \t\tif (!data->keepalive)\n \t\t\tdata->keepalive = -1;\n \t} else if (!strcmp(\"uploadpack.allowfilter\", var)) {\n@@ -1346,7 +1348,7 @@ static int upload_pack_config(const char *var, const char *value,\n \t\tdata->advertise_sid = git_config_bool(var, value);\n \t}\n \n-\tif (parse_object_filter_config(var, value, data) < 0)\n+\tif (parse_object_filter_config(var, value, ctx->kvi, data) < 0)\n \t\treturn -1;\n \n \treturn parse_hide_refs_config(var, value, \"uploadpack\", &data->hidden_refs);\ndiff --git a/worktree.c b/worktree.c\nindex c448fecd4b3..af277ec9010 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -835,7 +835,7 @@ int init_worktree_config(struct repository *r)\n \t * Relocate that value to avoid breaking all worktrees with this\n \t * upgrade to worktree config.\n \t */\n-\tif (!git_configset_get_value(&cs, \"core.worktree\", &core_worktree)) {\n+\tif (!git_configset_get_value(&cs, \"core.worktree\", &core_worktree, NULL)) {\n \t\tif ((res = move_config_setting(\"core.worktree\", core_worktree,\n \t\t\t\t\t       common_config_file,\n \t\t\t\t\t       main_worktree_file)))\n-- \ngitgitgadget\n\n"},{"id":"478859","messageId":"xmqqleg63qcf.fsf@gitster.g","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"Re: [PATCH v4 00/12] config: remove global state from config iteration","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-26T20:45:20Z","receivedAt":"2023-06-26T20:45:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> I also tested these patches on top of 'seen', and the only conflict I found was\n> with en/header-split-cache-h-part-3, where ll-merge.c got renamed to merge-ll.c,\n> so we need to apply the config refactor there (should work with .cocci).\n\nBoth recursive and ort merge strategies should be able to deal with\nll-merge.c <-> merge-ll.c, I think.\n\nThanks.\n\n"},{"id":"478969","messageId":"kl6lleg3qyx8.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59629","inReplyTo":"20230623203219.3255267-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 06/12] builtin/config.c: test misuse of format_config()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-06-28T17:28:19Z","receivedAt":"2023-06-28T17:29:29Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> \"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> +test_expect_success '--show-origin with --default' '\n>> +\ttest_must_fail git config --show-origin --default foo some.key\n>> +'\n>\n> On my machine, this fails with\n>\n>   BUG: config.c:4035: current_config_origin_type called outside config callback\n>   /usr/local/google/home/jonathantanmy/git/t/test-lib-functions.sh: line 1067: 3255109 Aborted                 \"$@\" 2>&7\n>   test_must_fail: died by signal 6: git config --show-origin --default foo some.key\n>\n> (So it indeed fails, as expected, but test_must_fail seems to not like\n> the exit code.)\n\nAh you're right. I was under the impression that this was doing the\nright thing on MacOS, but it doesn't work there either.\n\nI think a good way to assert that we run into BUG() (Maybe\ntest_match_signal on the numeric value of SIGABRT? But that sounds error\nprone), so I'll either use test_expect_failure or squash this patch.\n"},{"id":"478973","messageId":"7bfffb454c5d837077fe08087e43abb194efd1f4.1687980392.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","subject":"[PATCH v5 01/11] config: inline git_color_default_config","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-28T19:26:20Z","receivedAt":"2023-06-28T19:26:43Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\ngit_color_default_config() is a shorthand for calling two other config\ncallbacks. There are no other non-static functions that do this and it\nwill complicate our refactoring of config_fn_t so inline it instead.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/add.c         | 5 ++++-\n builtin/branch.c      | 5 ++++-\n builtin/clean.c       | 6 ++++--\n builtin/grep.c        | 5 ++++-\n builtin/show-branch.c | 5 ++++-\n builtin/tag.c         | 6 +++++-\n color.c               | 8 --------\n color.h               | 6 +-----\n 8 files changed, 26 insertions(+), 20 deletions(-)\n\ndiff --git a/builtin/add.c b/builtin/add.c\nindex 6137e7b4ad7..e01efdfc50d 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -365,7 +365,10 @@ static int add_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static const char embedded_advice[] = N_(\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 075e580d224..8337b9e71bb 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -117,7 +117,10 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static const char *branch_get_color(enum color_branch ix)\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 78852d28cec..57e7f7cac64 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -130,8 +130,10 @@ static int git_clean_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\t/* inspect the color.ui config variable and others */\n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static const char *clean_get_color(enum color_clean ix)\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex b86c754defb..76cf999d310 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -293,7 +293,10 @@ static int wait_all(void)\n static int grep_cmd_config(const char *var, const char *value, void *cb)\n {\n \tint st = grep_config(var, value, cb);\n-\tif (git_color_default_config(var, value, NULL) < 0)\n+\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\tst = -1;\n+\telse if (git_default_config(var, value, cb) < 0)\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex 7ef4a642c17..a2461270d4b 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -579,7 +579,10 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_color_default_config(var, value, cb);\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static int omit_in_dense(struct commit *commit, struct commit **rev, int n)\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 49b64c7a288..1acf5f7a59f 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -209,7 +209,11 @@ static int git_tag_config(const char *var, const char *value, void *cb)\n \n \tif (starts_with(var, \"column.\"))\n \t\treturn git_column_config(var, value, \"tag\", &colopts);\n-\treturn git_color_default_config(var, value, cb);\n+\n+\tif (git_color_config(var, value, cb) < 0)\n+\t\treturn -1;\n+\n+\treturn git_default_config(var, value, cb);\n }\n \n static void write_tag_body(int fd, const struct object_id *oid)\ndiff --git a/color.c b/color.c\nindex 83abb11eda0..b24b19566b9 100644\n--- a/color.c\n+++ b/color.c\n@@ -430,14 +430,6 @@ int git_color_config(const char *var, const char *value, void *cb UNUSED)\n \treturn 0;\n }\n \n-int git_color_default_config(const char *var, const char *value, void *cb)\n-{\n-\tif (git_color_config(var, value, cb) < 0)\n-\t\treturn -1;\n-\n-\treturn git_default_config(var, value, cb);\n-}\n-\n void color_print_strbuf(FILE *fp, const char *color, const struct strbuf *sb)\n {\n \tif (*color)\ndiff --git a/color.h b/color.h\nindex cfc8f841b23..bb28343be21 100644\n--- a/color.h\n+++ b/color.h\n@@ -88,12 +88,8 @@ extern const int column_colors_ansi_max;\n  */\n extern int color_stdout_is_tty;\n \n-/*\n- * Use the first one if you need only color config; the second is a convenience\n- * if you are just going to change to git_default_config, too.\n- */\n+/* Parse color config. */\n int git_color_config(const char *var, const char *value, void *cb);\n-int git_color_default_config(const char *var, const char *value, void *cb);\n \n /*\n  * Parse a config option, which can be a boolean or one of\n-- \ngitgitgadget\n\n"},{"id":"478974","messageId":"739c519ce625caa0fe5d0758b707ec2828c37ac3.1687980392.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","subject":"[PATCH v5 02/11] urlmatch.h: use config_fn_t type","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-28T19:26:21Z","receivedAt":"2023-06-28T19:26:47Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThese are actually used as config callbacks, so use the typedef-ed type\nand make future refactors easier.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n urlmatch.h | 5 +++--\n 1 file changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/urlmatch.h b/urlmatch.h\nindex 9f40b00bfb8..bee374a642c 100644\n--- a/urlmatch.h\n+++ b/urlmatch.h\n@@ -2,6 +2,7 @@\n #define URL_MATCH_H\n \n #include \"string-list.h\"\n+#include \"config.h\"\n \n struct url_info {\n \t/* normalized url on success, must be freed, otherwise NULL */\n@@ -48,8 +49,8 @@ struct urlmatch_config {\n \tconst char *key;\n \n \tvoid *cb;\n-\tint (*collect_fn)(const char *var, const char *value, void *cb);\n-\tint (*cascade_fn)(const char *var, const char *value, void *cb);\n+\tconfig_fn_t collect_fn;\n+\tconfig_fn_t cascade_fn;\n \t/*\n \t * Compare the two matches, the one just discovered and the existing\n \t * best match and return a negative value if the found item is to be\n-- \ngitgitgadget\n\n"},{"id":"478975","messageId":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v4.git.git.1687803083.gitgitgadget@gmail.com","subject":"[PATCH v5 00/11] config: remove global state from config iteration","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-28T19:26:19Z","receivedAt":"2023-06-28T19:26:51Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"As promised, this version addresses the comments on v3.\n\n= Changes since v4\n\n- Squash 6-7/12 since `test_must_fail` doesn't catch BUG()\n- Move a hunk to later in the series where it belongs\n- Replace a memcpy with `*a = *b`\n\n= Changes since v3\n\n- Rebase onto newer 'master'\n- Move the 'remove UNUSED from tr2_cfg_cb' hunk from 9/12 -> 8/12. It should\n  have been there all along; v3 8/12 didn't build at all.\n\n\nGlen Choo (11):\n  config: inline git_color_default_config\n  urlmatch.h: use config_fn_t type\n  config: add ctx arg to config_fn_t\n  config.c: pass ctx in configsets\n  config: pass ctx with config files\n  config.c: pass ctx with CLI config\n  trace2: plumb config kvi\n  config: pass kvi to die_bad_number()\n  config.c: remove config_reader from configsets\n  config: add kvi.path, use it to evaluate includes\n  config: pass source to config_parser_event_fn_t\n\n alias.c                                       |   3 +-\n archive-tar.c                                 |   5 +-\n archive-zip.c                                 |   1 +\n builtin/add.c                                 |   8 +-\n builtin/blame.c                               |   5 +-\n builtin/branch.c                              |   8 +-\n builtin/cat-file.c                            |   5 +-\n builtin/checkout.c                            |  12 +-\n builtin/clean.c                               |   9 +-\n builtin/clone.c                               |  11 +-\n builtin/column.c                              |   3 +-\n builtin/commit-graph.c                        |   3 +-\n builtin/commit.c                              |  20 +-\n builtin/config.c                              |  72 ++-\n builtin/difftool.c                            |   5 +-\n builtin/fetch.c                               |  13 +-\n builtin/fsmonitor--daemon.c                   |  11 +-\n builtin/grep.c                                |  12 +-\n builtin/help.c                                |   5 +-\n builtin/index-pack.c                          |   9 +-\n builtin/log.c                                 |  12 +-\n builtin/merge.c                               |   7 +-\n builtin/multi-pack-index.c                    |   1 +\n builtin/pack-objects.c                        |  19 +-\n builtin/patch-id.c                            |   5 +-\n builtin/pull.c                                |   5 +-\n builtin/push.c                                |   5 +-\n builtin/read-tree.c                           |   5 +-\n builtin/rebase.c                              |   5 +-\n builtin/receive-pack.c                        |  15 +-\n builtin/reflog.c                              |   7 +-\n builtin/remote.c                              |  15 +-\n builtin/repack.c                              |   5 +-\n builtin/reset.c                               |   5 +-\n builtin/send-pack.c                           |   5 +-\n builtin/show-branch.c                         |   8 +-\n builtin/stash.c                               |   5 +-\n builtin/submodule--helper.c                   |   3 +-\n builtin/tag.c                                 |   9 +-\n builtin/var.c                                 |   5 +-\n builtin/worktree.c                            |   5 +-\n bundle-uri.c                                  |   9 +-\n color.c                                       |   8 -\n color.h                                       |   6 +-\n compat/mingw.c                                |   3 +-\n compat/mingw.h                                |   4 +-\n config.c                                      | 552 +++++++-----------\n config.h                                      |  80 ++-\n connect.c                                     |   4 +-\n .../coccinelle/config_fn_ctx.pending.cocci    | 144 +++++\n contrib/coccinelle/git_config_number.cocci    |  27 +\n convert.c                                     |   4 +-\n credential.c                                  |   1 +\n delta-islands.c                               |   4 +-\n diff.c                                        |  19 +-\n diff.h                                        |   7 +-\n fetch-pack.c                                  |   5 +-\n fmt-merge-msg.c                               |   7 +-\n fmt-merge-msg.h                               |   3 +-\n fsck.c                                        |  12 +-\n fsck.h                                        |   4 +-\n git-compat-util.h                             |   2 +\n gpg-interface.c                               |   7 +-\n grep.c                                        |   7 +-\n grep.h                                        |   4 +-\n help.c                                        |   9 +-\n http.c                                        |  15 +-\n ident.c                                       |   4 +-\n ident.h                                       |   4 +-\n imap-send.c                                   |   7 +-\n ll-merge.c                                    |   1 +\n ls-refs.c                                     |   1 +\n mailinfo.c                                    |   5 +-\n notes-utils.c                                 |   4 +-\n notes.c                                       |   4 +-\n pager.c                                       |   5 +-\n pretty.c                                      |   1 +\n promisor-remote.c                             |   4 +-\n remote.c                                      |   8 +-\n revision.c                                    |   4 +-\n scalar.c                                      |   4 +-\n sequencer.c                                   |  29 +-\n setup.c                                       |  18 +-\n submodule-config.c                            |  31 +-\n submodule-config.h                            |   3 +-\n t/helper/test-config.c                        |  24 +-\n t/helper/test-userdiff.c                      |   4 +-\n t/t1300-config.sh                             |  27 +\n trace2.c                                      |   4 +-\n trace2.h                                      |   3 +-\n trace2/tr2_cfg.c                              |  16 +-\n trace2/tr2_sysenv.c                           |   3 +-\n trace2/tr2_tgt.h                              |   4 +-\n trace2/tr2_tgt_event.c                        |   4 +-\n trace2/tr2_tgt_normal.c                       |   4 +-\n trace2/tr2_tgt_perf.c                         |   4 +-\n trailer.c                                     |   2 +\n upload-pack.c                                 |  18 +-\n urlmatch.c                                    |   7 +-\n urlmatch.h                                    |   8 +-\n worktree.c                                    |   2 +-\n xdiff-interface.c                             |   5 +-\n xdiff-interface.h                             |   4 +-\n 103 files changed, 960 insertions(+), 638 deletions(-)\n create mode 100644 contrib/coccinelle/config_fn_ctx.pending.cocci\n create mode 100644 contrib/coccinelle/git_config_number.cocci\n\n\nbase-commit: 6ff334181cfb6485d3ba50843038209a2a253907\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1497%2Fchooglen%2Fconfig%2Fno-global-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1497/chooglen/config/no-global-v5\nPull-Request: https://github.com/git/git/pull/1497\n\nRange-diff vs v4:\n\n  1:  7bfffb454c5 =  1:  7bfffb454c5 config: inline git_color_default_config\n  2:  739c519ce62 =  2:  739c519ce62 urlmatch.h: use config_fn_t type\n  3:  a9a0a50f32a =  3:  a9a0a50f32a config: add ctx arg to config_fn_t\n  4:  39b2e291f86 =  4:  39b2e291f86 config.c: pass ctx in configsets\n  5:  bfc6d2833c5 =  5:  bfc6d2833c5 config: pass ctx with config files\n  6:  897bdc759b5 <  -:  ----------- builtin/config.c: test misuse of format_config()\n  7:  33e4437737d !  6:  7b24eefbcf3 config.c: pass ctx with CLI config\n     @@ Commit message\n          * git_config_parse_parameter() hasn't been setting config source\n            information, so plumb \"kvi\" there too.\n      \n     -    * \"git config --get-urlmatch --show-scope\" iterates config to collect\n     -      values, but then attempts to display the scope after config iteration.\n     -      Fix this by copying the \"kvi\" value in the collection phase so that it\n     -      can be read back later. This means that we can now support \"git config\n     -      --get-urlmatch --show-origin\" (we don't allow this combination of args\n     -      because of this bug), but that is left unchanged for now.\n     +    * Several sites in builtin/config.c have been calling current_config_*()\n     +      functions outside of config callbacks (indirectly, via the\n     +      format_config() helper), which means they're reading state that isn't\n     +      set correctly:\n      \n     -    * \"git config --default\" doesn't have config source metadata when\n     -      displaying the default value. Fix this by treating the default value\n     -      as if it came from the command line (e.g. like we do with \"git -c\" or\n     -      \"git config --file\"), using kvi_from_param().\n     +      * \"git config --get-urlmatch --show-scope\" iterates config to collect\n     +        values, but then attempts to display the scope after config\n     +        iteration, causing the \"unknown\" scope to be shown instead of the\n     +        config file's scope. It's clear that this wasn't intended: we knew\n     +        that \"--get-urlmatch\" couldn't show config source metadata, which is\n     +        why \"--show-origin\" was marked incompatible with \"--get-urlmatch\"\n     +        when it was introduced [1]. It was most likely a mistake that we\n     +        allowed \"--show-scope\" to sneak through.\n     +\n     +        Fix this by copying the \"kvi\" value in the collection phase so that\n     +        it can be read back later. This means that we can now support \"git\n     +        config --get-urlmatch --show-origin\", but that is left unchanged\n     +        for now.\n     +\n     +      * \"git config --default\" doesn't have config source metadata when\n     +        displaying the default value, so \"--show-scope\" also results in\n     +        \"unknown\", and \"--show-origin\" results in a BUG(). Fix this by\n     +        treating the default value as if it came from the command line (e.g.\n     +        like we do with \"git -c\" or \"git config --file\"), using\n     +        kvi_from_param().\n     +\n     +    [1] https://lore.kernel.org/git/20160205112001.GA13397@sigill.intra.peff.net/\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n     @@ config.c: static int configset_find_element(struct config_set *set, const char *\n       \t\t\t       const char *value)\n       {\n      @@ config.c: static int configset_add_value(struct config_reader *reader,\n     - \tl_item->e = e;\n     - \tl_item->value_index = e->value_list.nr - 1;\n     - \n     --\tif (!reader->source)\n     --\t\tBUG(\"configset_add_value has no source\");\n     + \tif (!reader->source)\n     + \t\tBUG(\"configset_add_value has no source\");\n       \tif (reader->source->name) {\n      -\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n      +\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n     @@ config.h: void git_global_config(char **user, char **xdg);\n        * Match and parse a config key of the form:\n      \n       ## t/t1300-config.sh ##\n     -@@ t/t1300-config.sh: test_expect_success 'urlmatch with --show-scope' '\n     - \tEOF\n     +@@ t/t1300-config.sh: test_expect_success 'urlmatch' '\n     + \ttest_cmp expect actual\n     + '\n       \n     - \tcat >expect <<-EOF &&\n     --\tunknown\thttp.cookiefile /tmp/cookie.txt\n     --\tunknown\thttp.sslverify false\n     ++test_expect_success 'urlmatch with --show-scope' '\n     ++\tcat >.git/config <<-\\EOF &&\n     ++\t[http \"https://weak.example.com\"]\n     ++\t\tsslVerify = false\n     ++\t\tcookieFile = /tmp/cookie.txt\n     ++\tEOF\n     ++\n     ++\tcat >expect <<-EOF &&\n      +\tlocal\thttp.cookiefile /tmp/cookie.txt\n      +\tlocal\thttp.sslverify false\n     - \tEOF\n     - \tgit config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&\n     - \ttest_cmp expect actual\n     ++\tEOF\n     ++\tgit config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&\n     ++\ttest_cmp expect actual\n     ++'\n     ++\n     + test_expect_success 'urlmatch favors more specific URLs' '\n     + \tcat >.git/config <<-\\EOF &&\n     + \t[http \"https://example.com/\"]\n      @@ t/t1300-config.sh: test_expect_success '--show-origin blob ref' '\n     + \ttest_cmp expect output\n       '\n       \n     - test_expect_success '--show-origin with --default' '\n     --\ttest_must_fail git config --show-origin --default foo some.key\n     ++test_expect_success '--show-origin with --default' '\n      +\tgit config --show-origin --default foo some.key >actual &&\n      +\techo \"command line:\tfoo\" >expect &&\n      +\ttest_cmp expect actual\n     - '\n     - \n     ++'\n     ++\n       test_expect_success '--show-scope with --list' '\n     + \tcat >expect <<-EOF &&\n     + \tglobal\tuser.global=true\n      @@ t/t1300-config.sh: test_expect_success '--show-scope with --show-origin' '\n     - \n     - test_expect_success '--show-scope with --default' '\n     - \tgit config --show-scope --default foo some.key >actual &&\n     --\techo \"unknown\tfoo\" >expect &&\n     -+\techo \"command\tfoo\" >expect &&\n     - \ttest_cmp expect actual\n     + \ttest_cmp expect output\n       '\n       \n     ++test_expect_success '--show-scope with --default' '\n     ++\tgit config --show-scope --default foo some.key >actual &&\n     ++\techo \"command\tfoo\" >expect &&\n     ++\ttest_cmp expect actual\n     ++'\n     ++\n     + test_expect_success 'override global and system config' '\n     + \ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n     + \tcat >\"$HOME\"/.gitconfig <<-EOF &&\n  8:  9bd5f60282c =  7:  7d64dcbdade trace2: plumb config kvi\n  9:  114723ee4a7 =  8:  9e71c10ca0a config: pass kvi to die_bad_number()\n 10:  807057b6d7f !  9:  4776600e790 config.c: remove config_reader from configsets\n     @@ config.c: static int configset_add_value(const struct key_value_info *kvi_p,\n       \tl_item->e = e;\n       \tl_item->value_index = e->value_list.nr - 1;\n       \n     +-\tif (!reader->source)\n     +-\t\tBUG(\"configset_add_value has no source\");\n      -\tif (reader->source->name) {\n      -\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n      -\t} else {\n      -\t\tkvi_from_param(kv_info);\n      -\t}\n     -+\tmemcpy(kv_info, kvi_p, sizeof(struct key_value_info));\n     ++\t*kv_info = *kvi_p;\n       \tsi->util = kv_info;\n       \n       \treturn 0;\n 11:  3f0f84df972 = 10:  2b33977aba6 config: add kvi.path, use it to evaluate includes\n 12:  fe2f154fe8b = 11:  8347d3c9b80 config: pass source to config_parser_event_fn_t\n\n-- \ngitgitgadget\n"},{"id":"478976","messageId":"39b2e291f8669e1d77725619d6b3c2b34b2acecc.1687980393.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","subject":"[PATCH v5 04/11] config.c: pass ctx in configsets","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-28T19:26:23Z","receivedAt":"2023-06-28T19:26:56Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPass config_context to config callbacks in configset_iter(), trivially\nsetting the .kvi member to the cached key_value_info. Then, in config\ncallbacks that are only used with configsets, use the .kvi member to\nreplace calls to current_config_*(), and delete current_config_line()\nbecause it has no remaining callers.\n\nThis leaves builtin/config.c and config.c as the only remaining users of\ncurrent_config_*().\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/remote.c       | 10 ++++++----\n config.c               | 35 ++++++++++++++++-------------------\n config.h               |  2 +-\n remote.c               |  7 ++++---\n t/helper/test-config.c | 11 ++++++-----\n 5 files changed, 33 insertions(+), 32 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 87de81105e2..d47f9ee21cf 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -646,17 +646,19 @@ struct push_default_info\n };\n \n static int config_read_push_default(const char *key, const char *value,\n-\tconst struct config_context *ctx UNUSED, void *cb)\n+\tconst struct config_context *ctx, void *cb)\n {\n+\tconst struct key_value_info *kvi = ctx->kvi;\n+\n \tstruct push_default_info* info = cb;\n \tif (strcmp(key, \"remote.pushdefault\") ||\n \t    !value || strcmp(value, info->old_name))\n \t\treturn 0;\n \n-\tinfo->scope = current_config_scope();\n+\tinfo->scope = kvi->scope;\n \tstrbuf_reset(&info->origin);\n-\tstrbuf_addstr(&info->origin, current_config_name());\n-\tinfo->linenr = current_config_line();\n+\tstrbuf_addstr(&info->origin, config_origin_type_name(kvi->origin_type));\n+\tinfo->linenr = kvi->linenr;\n \n \treturn 0;\n }\ndiff --git a/config.c b/config.c\nindex 850e432e301..662d406ac1e 100644\n--- a/config.c\n+++ b/config.c\n@@ -2317,6 +2317,7 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \tstruct string_list *values;\n \tstruct config_set_element *entry;\n \tstruct configset_list *list = &set->list;\n+\tstruct config_context ctx = CONFIG_CONTEXT_INIT;\n \n \tfor (i = 0; i < list->nr; i++) {\n \t\tentry = list->items[i].e;\n@@ -2324,12 +2325,11 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \t\tvalues = &entry->value_list;\n \n \t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n-\n-\t\tif (fn(entry->key, values->items[value_index].string, NULL, data) < 0)\n+\t\tctx.kvi = values->items[value_index].util;\n+\t\tif (fn(entry->key, values->items[value_index].string, &ctx, data) < 0)\n \t\t\tgit_die_config_linenr(entry->key,\n-\t\t\t\t\t      reader->config_kvi->filename,\n-\t\t\t\t\t      reader->config_kvi->linenr);\n-\n+\t\t\t\t\t      ctx.kvi->filename,\n+\t\t\t\t\t      ctx.kvi->linenr);\n \t\tconfig_reader_set_kvi(reader, NULL);\n \t}\n }\n@@ -3984,13 +3984,8 @@ static int reader_origin_type(struct config_reader *reader,\n \treturn 0;\n }\n \n-const char *current_config_origin_type(void)\n+const char *config_origin_type_name(enum config_origin_type type)\n {\n-\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n-\n-\tif (reader_origin_type(&the_reader, &type))\n-\t\tBUG(\"current_config_origin_type called outside config callback\");\n-\n \tswitch (type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\treturn \"blob\";\n@@ -4007,6 +4002,16 @@ const char *current_config_origin_type(void)\n \t}\n }\n \n+const char *current_config_origin_type(void)\n+{\n+\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n+\n+\tif (reader_origin_type(&the_reader, &type))\n+\t\tBUG(\"current_config_origin_type called outside config callback\");\n+\n+\treturn config_origin_type_name(type);\n+}\n+\n const char *config_scope_name(enum config_scope scope)\n {\n \tswitch (scope) {\n@@ -4054,14 +4059,6 @@ enum config_scope current_config_scope(void)\n \t\treturn the_reader.parsing_scope;\n }\n \n-int current_config_line(void)\n-{\n-\tif (the_reader.config_kvi)\n-\t\treturn the_reader.config_kvi->linenr;\n-\telse\n-\t\treturn the_reader.source->linenr;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex cd30125a8a4..ddf147bb2d1 100644\n--- a/config.h\n+++ b/config.h\n@@ -387,7 +387,7 @@ int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n enum config_scope current_config_scope(void);\n const char *current_config_origin_type(void);\n const char *current_config_name(void);\n-int current_config_line(void);\n+const char *config_origin_type_name(enum config_origin_type type);\n \n /*\n  * Match and parse a config key of the form:\ndiff --git a/remote.c b/remote.c\nindex 241999c2842..1dab860141b 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -350,7 +350,7 @@ static void read_branches_file(struct remote_state *remote_state,\n }\n \n static int handle_config(const char *key, const char *value,\n-\t\t\t const struct config_context *ctx UNUSED, void *cb)\n+\t\t\t const struct config_context *ctx, void *cb)\n {\n \tconst char *name;\n \tsize_t namelen;\n@@ -358,6 +358,7 @@ static int handle_config(const char *key, const char *value,\n \tstruct remote *remote;\n \tstruct branch *branch;\n \tstruct remote_state *remote_state = cb;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \n \tif (parse_config_key(key, \"branch\", &name, &namelen, &subkey) >= 0) {\n \t\t/* There is no subsection. */\n@@ -415,8 +416,8 @@ static int handle_config(const char *key, const char *value,\n \t}\n \tremote = make_remote(remote_state, name, namelen);\n \tremote->origin = REMOTE_CONFIG;\n-\tif (current_config_scope() == CONFIG_SCOPE_LOCAL ||\n-\t    current_config_scope() == CONFIG_SCOPE_WORKTREE)\n+\tif (kvi->scope == CONFIG_SCOPE_LOCAL ||\n+\t    kvi->scope == CONFIG_SCOPE_WORKTREE)\n \t\tremote->configured_in_repo = 1;\n \tif (!strcmp(subkey, \"mirror\"))\n \t\tremote->mirror = git_config_bool(key, value);\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 85ad815358e..3f4c3678318 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -43,9 +43,10 @@\n  */\n \n static int iterate_cb(const char *var, const char *value,\n-\t\t      const struct config_context *ctx UNUSED,\n+\t\t      const struct config_context *ctx,\n \t\t      void *data UNUSED)\n {\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \tstatic int nr;\n \n \tif (nr++)\n@@ -53,10 +54,10 @@ static int iterate_cb(const char *var, const char *value,\n \n \tprintf(\"key=%s\\n\", var);\n \tprintf(\"value=%s\\n\", value ? value : \"(null)\");\n-\tprintf(\"origin=%s\\n\", current_config_origin_type());\n-\tprintf(\"name=%s\\n\", current_config_name());\n-\tprintf(\"lno=%d\\n\", current_config_line());\n-\tprintf(\"scope=%s\\n\", config_scope_name(current_config_scope()));\n+\tprintf(\"origin=%s\\n\", config_origin_type_name(kvi->origin_type));\n+\tprintf(\"name=%s\\n\", kvi->filename ? kvi->filename : \"\");\n+\tprintf(\"lno=%d\\n\", kvi->linenr);\n+\tprintf(\"scope=%s\\n\", config_scope_name(kvi->scope));\n \n \treturn 0;\n }\n-- \ngitgitgadget\n\n"},{"id":"478977","messageId":"bfc6d2833c5fb904149452246021a432917d2776.1687980393.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","subject":"[PATCH v5 05/11] config: pass ctx with config files","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-28T19:26:24Z","receivedAt":"2023-06-28T19:26:59Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPass config_context to config_callbacks when parsing config files. To\nprovide the .kvi member, refactor out the configset logic that caches\n\"struct config_source\" and \"enum config_scope\" as a \"struct\nkey_value_info\". Make the \"enum config_scope\" available to the config\nfile machinery by plumbing an additional arg through\ngit_config_from_file_with_options().\n\nWe do not exercise ctx yet because the remaining current_config_*()\ncallers may be used with config_with_options(), which may read config\nfrom parameters, but parameters don't pass ctx yet.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n bundle-uri.c       |   1 +\n config.c           | 105 ++++++++++++++++++++++++++++++---------------\n config.h           |   8 ++--\n fsck.c             |   3 +-\n submodule-config.c |   5 ++-\n 5 files changed, 81 insertions(+), 41 deletions(-)\n\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex 0d5acc3dc51..64f32387745 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -255,6 +255,7 @@ int bundle_uri_parse_config_format(const char *uri,\n \t}\n \tresult = git_config_from_file_with_options(config_to_bundle_list,\n \t\t\t\t\t\t   filename, list,\n+\t\t\t\t\t\t   CONFIG_SCOPE_UNKNOWN,\n \t\t\t\t\t\t   &opts);\n \n \tif (!result && list->mode == BUNDLE_MODE_NONE) {\ndiff --git a/config.c b/config.c\nindex 662d406ac1e..31718711827 100644\n--- a/config.c\n+++ b/config.c\n@@ -259,7 +259,9 @@ static int handle_path_include(struct config_source *cs, const char *path,\n \t\t\t    !cs ? \"<unknown>\" :\n \t\t\t    cs->name ? cs->name :\n \t\t\t    \"the command line\");\n-\t\tret = git_config_from_file(git_config_include, path, inc);\n+\t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n+\t\t\t\t\t\t\tcurrent_config_scope(),\n+\t\t\t\t\t\t\tNULL);\n \t\tinc->depth--;\n \t}\n cleanup:\n@@ -503,7 +505,7 @@ static int git_config_include(const char *var, const char *value,\n \t * Pass along all values, including \"include\" directives; this makes it\n \t * possible to query information on the includes themselves.\n \t */\n-\tret = inc->fn(var, value, NULL, inc->data);\n+\tret = inc->fn(var, value, ctx, inc->data);\n \tif (ret < 0)\n \t\treturn ret;\n \n@@ -939,12 +941,15 @@ static char *parse_value(struct config_source *cs)\n \t}\n }\n \n-static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n-\t\t     struct strbuf *name)\n+static int get_value(struct config_source *cs, struct key_value_info *kvi,\n+\t\t     config_fn_t fn, void *data, struct strbuf *name)\n {\n \tint c;\n \tchar *value;\n \tint ret;\n+\tstruct config_context ctx = {\n+\t\t.kvi = kvi,\n+\t};\n \n \t/* Get the full name */\n \tfor (;;) {\n@@ -973,7 +978,8 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \t * accurate line number in error messages.\n \t */\n \tcs->linenr--;\n-\tret = fn(name->buf, value, NULL, data);\n+\tkvi->linenr = cs->linenr;\n+\tret = fn(name->buf, value, &ctx, data);\n \tif (ret >= 0)\n \t\tcs->linenr++;\n \treturn ret;\n@@ -1072,8 +1078,19 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \treturn 0;\n }\n \n+static void kvi_from_source(struct config_source *cs,\n+\t\t\t    enum config_scope scope,\n+\t\t\t    struct key_value_info *out)\n+{\n+\tout->filename = strintern(cs->name);\n+\tout->origin_type = cs->origin_type;\n+\tout->linenr = cs->linenr;\n+\tout->scope = scope;\n+}\n+\n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n-\t\t\t    void *data, const struct config_options *opts)\n+\t\t\t    struct key_value_info *kvi, void *data,\n+\t\t\t    const struct config_options *opts)\n {\n \tint comment = 0;\n \tsize_t baselen = 0;\n@@ -1157,7 +1174,7 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t */\n \t\tstrbuf_setlen(var, baselen);\n \t\tstrbuf_addch(var, tolower(c));\n-\t\tif (get_value(cs, fn, data, var) < 0)\n+\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n \t\t\tbreak;\n \t}\n \n@@ -2010,9 +2027,11 @@ int git_default_config(const char *var, const char *value,\n  * this function.\n  */\n static int do_config_from(struct config_reader *reader,\n-\t\t\t  struct config_source *top, config_fn_t fn, void *data,\n+\t\t\t  struct config_source *top, config_fn_t fn,\n+\t\t\t  void *data, enum config_scope scope,\n \t\t\t  const struct config_options *opts)\n {\n+\tstruct key_value_info kvi = KVI_INIT;\n \tint ret;\n \n \t/* push config-file parsing state stack */\n@@ -2022,8 +2041,9 @@ static int do_config_from(struct config_reader *reader,\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n \tconfig_reader_push_source(reader, top);\n+\tkvi_from_source(top, scope, &kvi);\n \n-\tret = git_parse_source(top, fn, data, opts);\n+\tret = git_parse_source(top, fn, &kvi, data, opts);\n \n \t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n@@ -2037,7 +2057,8 @@ static int do_config_from_file(struct config_reader *reader,\n \t\t\t       config_fn_t fn,\n \t\t\t       const enum config_origin_type origin_type,\n \t\t\t       const char *name, const char *path, FILE *f,\n-\t\t\t       void *data, const struct config_options *opts)\n+\t\t\t       void *data, enum config_scope scope,\n+\t\t\t       const struct config_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n@@ -2052,19 +2073,20 @@ static int do_config_from_file(struct config_reader *reader,\n \ttop.do_ftell = config_file_ftell;\n \n \tflockfile(f);\n-\tret = do_config_from(reader, &top, fn, data, opts);\n+\tret = do_config_from(reader, &top, fn, data, scope, opts);\n \tfunlockfile(f);\n \treturn ret;\n }\n \n-static int git_config_from_stdin(config_fn_t fn, void *data)\n+static int git_config_from_stdin(config_fn_t fn, void *data,\n+\t\t\t\t enum config_scope scope)\n {\n \treturn do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, \"\",\n-\t\t\t\t   NULL, stdin, data, NULL);\n+\t\t\t\t   NULL, stdin, data, scope, NULL);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n-\t\t\t\t      void *data,\n+\t\t\t\t      void *data, enum config_scope scope,\n \t\t\t\t      const struct config_options *opts)\n {\n \tint ret = -1;\n@@ -2075,7 +2097,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \tf = fopen_or_warn(filename, \"r\");\n \tif (f) {\n \t\tret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,\n-\t\t\t\t\t  filename, filename, f, data, opts);\n+\t\t\t\t\t  filename, filename, f, data, scope,\n+\t\t\t\t\t  opts);\n \t\tfclose(f);\n \t}\n \treturn ret;\n@@ -2083,13 +2106,15 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \n int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n {\n-\treturn git_config_from_file_with_options(fn, filename, data, NULL);\n+\treturn git_config_from_file_with_options(fn, filename, data,\n+\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, NULL);\n }\n \n int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type origin_type,\n \t\t\tconst char *name, const char *buf, size_t len,\n-\t\t\tvoid *data, const struct config_options *opts)\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \n@@ -2104,14 +2129,15 @@ int git_config_from_mem(config_fn_t fn,\n \ttop.do_ungetc = config_buf_ungetc;\n \ttop.do_ftell = config_buf_ftell;\n \n-\treturn do_config_from(&the_reader, &top, fn, data, opts);\n+\treturn do_config_from(&the_reader, &top, fn, data, scope, opts);\n }\n \n int git_config_from_blob_oid(config_fn_t fn,\n \t\t\t      const char *name,\n \t\t\t      struct repository *repo,\n \t\t\t      const struct object_id *oid,\n-\t\t\t      void *data)\n+\t\t\t      void *data,\n+\t\t\t      enum config_scope scope)\n {\n \tenum object_type type;\n \tchar *buf;\n@@ -2127,7 +2153,7 @@ int git_config_from_blob_oid(config_fn_t fn,\n \t}\n \n \tret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size,\n-\t\t\t\t  data, NULL);\n+\t\t\t\t  data, scope, NULL);\n \tfree(buf);\n \n \treturn ret;\n@@ -2136,13 +2162,14 @@ int git_config_from_blob_oid(config_fn_t fn,\n static int git_config_from_blob_ref(config_fn_t fn,\n \t\t\t\t    struct repository *repo,\n \t\t\t\t    const char *name,\n-\t\t\t\t    void *data)\n+\t\t\t\t    void *data,\n+\t\t\t\t    enum config_scope scope)\n {\n \tstruct object_id oid;\n \n \tif (repo_get_oid(repo, name, &oid) < 0)\n \t\treturn error(_(\"unable to resolve config blob '%s'\"), name);\n-\treturn git_config_from_blob_oid(fn, name, repo, &oid, data);\n+\treturn git_config_from_blob_oid(fn, name, repo, &oid, data, scope);\n }\n \n char *git_system_config(void)\n@@ -2228,27 +2255,34 @@ static int do_git_config_sequence(struct config_reader *reader,\n \tif (git_config_system() && system_config &&\n \t    !access_or_die(system_config, R_OK,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n-\t\tret += git_config_from_file(fn, system_config, data);\n+\t\tret += git_config_from_file_with_options(fn, system_config,\n+\t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n+\t\t\t\t\t\t\t NULL);\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);\n \tgit_global_config(&user_config, &xdg_config);\n \n \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file(fn, xdg_config, data);\n+\t\tret += git_config_from_file_with_options(fn, xdg_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n \tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n-\t\tret += git_config_from_file(fn, user_config, data);\n+\t\tret += git_config_from_file_with_options(fn, user_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);\n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n-\t\tret += git_config_from_file(fn, repo_config, data);\n+\t\tret += git_config_from_file_with_options(fn, repo_config, data,\n+\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n \tif (!opts->ignore_worktree && worktree_config &&\n \t    repo && repo->repository_format_worktree_config &&\n \t    !access_or_die(worktree_config, R_OK, 0)) {\n-\t\tret += git_config_from_file(fn, worktree_config, data);\n+\t\t\tret += git_config_from_file_with_options(fn, worktree_config, data,\n+\t\t\t\t\t\t\t\t CONFIG_SCOPE_WORKTREE,\n+\t\t\t\t\t\t\t\t NULL);\n \t}\n \n \tconfig_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);\n@@ -2292,12 +2326,14 @@ int config_with_options(config_fn_t fn, void *data,\n \t * regular lookup sequence.\n \t */\n \tif (config_source && config_source->use_stdin) {\n-\t\tret = git_config_from_stdin(fn, data);\n+\t\tret = git_config_from_stdin(fn, data, config_source->scope);\n \t} else if (config_source && config_source->file) {\n-\t\tret = git_config_from_file(fn, config_source->file, data);\n+\t\tret = git_config_from_file_with_options(fn, config_source->file,\n+\t\t\t\t\t\t\tdata, config_source->scope,\n+\t\t\t\t\t\t\tNULL);\n \t} else if (config_source && config_source->blob) {\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n-\t\t\t\t\t\tdata);\n+\t\t\t\t\t       data, config_source->scope);\n \t} else {\n \t\tret = do_git_config_sequence(&the_reader, opts, repo, fn, data);\n \t}\n@@ -2440,16 +2476,14 @@ static int configset_add_value(struct config_reader *reader,\n \tif (!reader->source)\n \t\tBUG(\"configset_add_value has no source\");\n \tif (reader->source->name) {\n-\t\tkv_info->filename = strintern(reader->source->name);\n-\t\tkv_info->linenr = reader->source->linenr;\n-\t\tkv_info->origin_type = reader->source->origin_type;\n+\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n \t} else {\n \t\t/* for values read from `git_config_from_parameters()` */\n \t\tkv_info->filename = NULL;\n \t\tkv_info->linenr = -1;\n \t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n+\t\tkv_info->scope = reader->parsing_scope;\n \t}\n-\tkv_info->scope = reader->parsing_scope;\n \tsi->util = kv_info;\n \n \treturn 0;\n@@ -3490,7 +3524,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \t\t */\n \t\tif (git_config_from_file_with_options(store_aux,\n \t\t\t\t\t\t      config_filename,\n-\t\t\t\t\t\t      &store, &opts)) {\n+\t\t\t\t\t\t      &store, CONFIG_SCOPE_UNKNOWN,\n+\t\t\t\t\t\t      &opts)) {\n \t\t\terror(_(\"invalid config file %s\"), config_filename);\n \t\t\tret = CONFIG_INVALID_FILE;\n \t\t\tgoto out_free;\ndiff --git a/config.h b/config.h\nindex ddf147bb2d1..206bf1f175a 100644\n--- a/config.h\n+++ b/config.h\n@@ -169,16 +169,18 @@ int git_default_config(const char *, const char *,\n int git_config_from_file(config_fn_t fn, const char *, void *);\n \n int git_config_from_file_with_options(config_fn_t fn, const char *,\n-\t\t\t\t      void *,\n+\t\t\t\t      void *, enum config_scope,\n \t\t\t\t      const struct config_options *);\n int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type,\n \t\t\tconst char *name,\n \t\t\tconst char *buf, size_t len,\n-\t\t\tvoid *data, const struct config_options *opts);\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_options *opts);\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     struct repository *repo,\n-\t\t\t     const struct object_id *oid, void *data);\n+\t\t\t     const struct object_id *oid, void *data,\n+\t\t\t     enum config_scope scope);\n void git_config_push_parameter(const char *text);\n void git_config_push_env(const char *spec);\n int git_config_from_parameters(config_fn_t fn, void *data);\ndiff --git a/fsck.c b/fsck.c\nindex 55b6a694853..f92c216fb5c 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1238,7 +1238,8 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\tdata.ret = 0;\n \t\tconfig_opts.error_action = CONFIG_ERROR_SILENT;\n \t\tif (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB,\n-\t\t\t\t\t\".gitmodules\", buf, size, &data, &config_opts))\n+\t\t\t\t\t\".gitmodules\", buf, size, &data,\n+\t\t\t\t\tCONFIG_SCOPE_UNKNOWN, &config_opts))\n \t\t\tdata.ret |= report(options, oid, OBJ_BLOB,\n \t\t\t\t\tFSCK_MSG_GITMODULES_PARSE,\n \t\t\t\t\t\"could not parse gitmodules blob\");\ndiff --git a/submodule-config.c b/submodule-config.c\nindex a38d4d49731..3f25bd13674 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -606,7 +606,7 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \tparameter.gitmodules_oid = &oid;\n \tparameter.overwrite = 0;\n \tgit_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf,\n-\t\t\tconfig, config_size, &parameter, NULL);\n+\t\t\t    config, config_size, &parameter, CONFIG_SCOPE_UNKNOWN, NULL);\n \tstrbuf_release(&rev);\n \tfree(config);\n \n@@ -714,7 +714,8 @@ void gitmodules_config_oid(const struct object_id *commit_oid)\n \n \tif (gitmodule_oid_from_commit(commit_oid, &oid, &rev)) {\n \t\tgit_config_from_blob_oid(gitmodules_cb, rev.buf,\n-\t\t\t\t\t the_repository, &oid, the_repository);\n+\t\t\t\t\t the_repository, &oid, the_repository,\n+\t\t\t\t\t CONFIG_SCOPE_UNKNOWN);\n \t}\n \tstrbuf_release(&rev);\n \n-- \ngitgitgadget\n\n"},{"id":"478978","messageId":"7b24eefbcf3e6b99134c50ff51a441b18ef7fd86.1687980393.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","subject":"[PATCH v5 06/11] config.c: pass ctx with CLI config","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-28T19:26:25Z","receivedAt":"2023-06-28T19:27:00Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPass config_context when parsing CLI config. To provide the .kvi member,\nrefactor out kvi_from_param() from the logic that caches CLI config in\nconfigsets. Now that config_context and config_context.kvi is always\npresent when config machinery calls config callbacks, plumb \"kvi\" so\nthat we can remove all calls of current_config_scope() except for\ntrace2/*.c (which will be handled in a later commit), and remove all\nother current_config_*() (the functions themselves and their calls).\nNote that this results in .kvi containing a different, more complete\nset of information than the mocked up \"struct config_source\" in\ngit_config_from_parameters().\n\nPlumbing \"kvi\" reveals a few places where we've been doing the wrong\nthing:\n\n* git_config_parse_parameter() hasn't been setting config source\n  information, so plumb \"kvi\" there too.\n\n* Several sites in builtin/config.c have been calling current_config_*()\n  functions outside of config callbacks (indirectly, via the\n  format_config() helper), which means they're reading state that isn't\n  set correctly:\n\n  * \"git config --get-urlmatch --show-scope\" iterates config to collect\n    values, but then attempts to display the scope after config\n    iteration, causing the \"unknown\" scope to be shown instead of the\n    config file's scope. It's clear that this wasn't intended: we knew\n    that \"--get-urlmatch\" couldn't show config source metadata, which is\n    why \"--show-origin\" was marked incompatible with \"--get-urlmatch\"\n    when it was introduced [1]. It was most likely a mistake that we\n    allowed \"--show-scope\" to sneak through.\n\n    Fix this by copying the \"kvi\" value in the collection phase so that\n    it can be read back later. This means that we can now support \"git\n    config --get-urlmatch --show-origin\", but that is left unchanged\n    for now.\n\n  * \"git config --default\" doesn't have config source metadata when\n    displaying the default value, so \"--show-scope\" also results in\n    \"unknown\", and \"--show-origin\" results in a BUG(). Fix this by\n    treating the default value as if it came from the command line (e.g.\n    like we do with \"git -c\" or \"git config --file\"), using\n    kvi_from_param().\n\n[1] https://lore.kernel.org/git/20160205112001.GA13397@sigill.intra.peff.net/\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/config.c  | 47 ++++++++++++++++++----------\n config.c          | 80 ++++++++++++++++++++++++-----------------------\n config.h          |  3 +-\n t/t1300-config.sh | 27 ++++++++++++++++\n 4 files changed, 99 insertions(+), 58 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex f4fccf99cb8..9b9f5527311 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -194,38 +194,42 @@ static void check_argc(int argc, int min, int max)\n \tusage_builtin_config();\n }\n \n-static void show_config_origin(struct strbuf *buf)\n+static void show_config_origin(const struct key_value_info *kvi,\n+\t\t\t       struct strbuf *buf)\n {\n \tconst char term = end_nul ? '\\0' : '\\t';\n \n-\tstrbuf_addstr(buf, current_config_origin_type());\n+\tstrbuf_addstr(buf, config_origin_type_name(kvi->origin_type));\n \tstrbuf_addch(buf, ':');\n \tif (end_nul)\n-\t\tstrbuf_addstr(buf, current_config_name());\n+\t\tstrbuf_addstr(buf, kvi->filename ? kvi->filename : \"\");\n \telse\n-\t\tquote_c_style(current_config_name(), buf, NULL, 0);\n+\t\tquote_c_style(kvi->filename ? kvi->filename : \"\", buf, NULL, 0);\n \tstrbuf_addch(buf, term);\n }\n \n-static void show_config_scope(struct strbuf *buf)\n+static void show_config_scope(const struct key_value_info *kvi,\n+\t\t\t      struct strbuf *buf)\n {\n \tconst char term = end_nul ? '\\0' : '\\t';\n-\tconst char *scope = config_scope_name(current_config_scope());\n+\tconst char *scope = config_scope_name(kvi->scope);\n \n \tstrbuf_addstr(buf, N_(scope));\n \tstrbuf_addch(buf, term);\n }\n \n static int show_all_config(const char *key_, const char *value_,\n-\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t   const struct config_context *ctx,\n \t\t\t   void *cb UNUSED)\n {\n+\tconst struct key_value_info *kvi = ctx->kvi;\n+\n \tif (show_origin || show_scope) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \t\tif (show_scope)\n-\t\t\tshow_config_scope(&buf);\n+\t\t\tshow_config_scope(kvi, &buf);\n \t\tif (show_origin)\n-\t\t\tshow_config_origin(&buf);\n+\t\t\tshow_config_origin(kvi, &buf);\n \t\t/* Use fwrite as \"buf\" can contain \\0's if \"end_null\" is set. */\n \t\tfwrite(buf.buf, 1, buf.len, stdout);\n \t\tstrbuf_release(&buf);\n@@ -243,12 +247,13 @@ struct strbuf_list {\n \tint alloc;\n };\n \n-static int format_config(struct strbuf *buf, const char *key_, const char *value_)\n+static int format_config(struct strbuf *buf, const char *key_,\n+\t\t\t const char *value_, const struct key_value_info *kvi)\n {\n \tif (show_scope)\n-\t\tshow_config_scope(buf);\n+\t\tshow_config_scope(kvi, buf);\n \tif (show_origin)\n-\t\tshow_config_origin(buf);\n+\t\tshow_config_origin(kvi, buf);\n \tif (show_keys)\n \t\tstrbuf_addstr(buf, key_);\n \tif (!omit_values) {\n@@ -303,9 +308,10 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value\n }\n \n static int collect_config(const char *key_, const char *value_,\n-\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tstruct strbuf_list *values = cb;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \n \tif (!use_key_regexp && strcmp(key_, key))\n \t\treturn 0;\n@@ -320,7 +326,7 @@ static int collect_config(const char *key_, const char *value_,\n \tALLOC_GROW(values->items, values->nr + 1, values->alloc);\n \tstrbuf_init(&values->items[values->nr], 0);\n \n-\treturn format_config(&values->items[values->nr++], key_, value_);\n+\treturn format_config(&values->items[values->nr++], key_, value_, kvi);\n }\n \n static int get_value(const char *key_, const char *regex_, unsigned flags)\n@@ -382,11 +388,14 @@ static int get_value(const char *key_, const char *regex_, unsigned flags)\n \t\t\t    &config_options);\n \n \tif (!values.nr && default_value) {\n+\t\tstruct key_value_info kvi = KVI_INIT;\n \t\tstruct strbuf *item;\n+\n+\t\tkvi_from_param(&kvi);\n \t\tALLOC_GROW(values.items, values.nr + 1, values.alloc);\n \t\titem = &values.items[values.nr++];\n \t\tstrbuf_init(item, 0);\n-\t\tif (format_config(item, key_, default_value) < 0)\n+\t\tif (format_config(item, key_, default_value, &kvi) < 0)\n \t\t\tdie(_(\"failed to format default config value: %s\"),\n \t\t\t\tdefault_value);\n \t}\n@@ -563,15 +572,17 @@ static void check_write(void)\n struct urlmatch_current_candidate_value {\n \tchar value_is_null;\n \tstruct strbuf value;\n+\tstruct key_value_info kvi;\n };\n \n static int urlmatch_collect_fn(const char *var, const char *value,\n-\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       const struct config_context *ctx,\n \t\t\t       void *cb)\n {\n \tstruct string_list *values = cb;\n \tstruct string_list_item *item = string_list_insert(values, var);\n \tstruct urlmatch_current_candidate_value *matched = item->util;\n+\tconst struct key_value_info *kvi = ctx->kvi;\n \n \tif (!matched) {\n \t\tmatched = xmalloc(sizeof(*matched));\n@@ -580,6 +591,7 @@ static int urlmatch_collect_fn(const char *var, const char *value,\n \t} else {\n \t\tstrbuf_reset(&matched->value);\n \t}\n+\tmatched->kvi = *kvi;\n \n \tif (value) {\n \t\tstrbuf_addstr(&matched->value, value);\n@@ -627,7 +639,8 @@ static int get_urlmatch(const char *var, const char *url)\n \t\tstruct strbuf buf = STRBUF_INIT;\n \n \t\tformat_config(&buf, item->string,\n-\t\t\t      matched->value_is_null ? NULL : matched->value.buf);\n+\t\t\t      matched->value_is_null ? NULL : matched->value.buf,\n+\t\t\t      &matched->kvi);\n \t\tfwrite(buf.buf, 1, buf.len, stdout);\n \t\tstrbuf_release(&buf);\n \ndiff --git a/config.c b/config.c\nindex 31718711827..4986942b091 100644\n--- a/config.c\n+++ b/config.c\n@@ -219,7 +219,9 @@ static const char include_depth_advice[] = N_(\n \"from\\n\"\n \"\t%s\\n\"\n \"This might be due to circular includes.\");\n-static int handle_path_include(struct config_source *cs, const char *path,\n+static int handle_path_include(struct config_source *cs,\n+\t\t\t       const struct key_value_info *kvi,\n+\t\t\t       const char *path,\n \t\t\t       struct config_include_data *inc)\n {\n \tint ret = 0;\n@@ -260,8 +262,7 @@ static int handle_path_include(struct config_source *cs, const char *path,\n \t\t\t    cs->name ? cs->name :\n \t\t\t    \"the command line\");\n \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n-\t\t\t\t\t\t\tcurrent_config_scope(),\n-\t\t\t\t\t\t\tNULL);\n+\t\t\t\t\t\t\tkvi->scope, NULL);\n \t\tinc->depth--;\n \t}\n cleanup:\n@@ -510,7 +511,7 @@ static int git_config_include(const char *var, const char *value,\n \t\treturn ret;\n \n \tif (!strcmp(var, \"include.path\"))\n-\t\tret = handle_path_include(cs, value, inc);\n+\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n \t    cond && include_condition_is_true(cs, inc, cond, cond_len) &&\n@@ -519,7 +520,7 @@ static int git_config_include(const char *var, const char *value,\n \n \t\tif (inc->opts->unconditional_remote_url)\n \t\t\tinc->fn = forbid_remote_url;\n-\t\tret = handle_path_include(cs, value, inc);\n+\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -677,27 +678,44 @@ out_free_ret_1:\n }\n \n static int config_parse_pair(const char *key, const char *value,\n-\t\t\t  config_fn_t fn, void *data)\n+\t\t\t     struct key_value_info *kvi,\n+\t\t\t     config_fn_t fn, void *data)\n {\n \tchar *canonical_name;\n \tint ret;\n+\tstruct config_context ctx = {\n+\t\t.kvi = kvi,\n+\t};\n \n \tif (!strlen(key))\n \t\treturn error(_(\"empty config key\"));\n \tif (git_config_parse_key(key, &canonical_name, NULL))\n \t\treturn -1;\n \n-\tret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;\n+\tret = (fn(canonical_name, value, &ctx, data) < 0) ? -1 : 0;\n \tfree(canonical_name);\n \treturn ret;\n }\n \n+\n+/* for values read from `git_config_from_parameters()` */\n+void kvi_from_param(struct key_value_info *out)\n+{\n+\tout->filename = NULL;\n+\tout->linenr = -1;\n+\tout->origin_type = CONFIG_ORIGIN_CMDLINE;\n+\tout->scope = CONFIG_SCOPE_COMMAND;\n+}\n+\n int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n \tconst char *value;\n \tstruct strbuf **pair;\n \tint ret;\n+\tstruct key_value_info kvi = KVI_INIT;\n+\n+\tkvi_from_param(&kvi);\n \n \tpair = strbuf_split_str(text, '=', 2);\n \tif (!pair[0])\n@@ -716,12 +734,13 @@ int git_config_parse_parameter(const char *text,\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \t}\n \n-\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n+\tret = config_parse_pair(pair[0]->buf, value, &kvi, fn, data);\n \tstrbuf_list_free(pair);\n \treturn ret;\n }\n \n-static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n+static int parse_config_env_list(char *env, struct key_value_info *kvi,\n+\t\t\t\t config_fn_t fn, void *data)\n {\n \tchar *cur = env;\n \twhile (cur && *cur) {\n@@ -755,7 +774,7 @@ static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n \t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n \t\t\t}\n \n-\t\t\tif (config_parse_pair(key, value, fn, data) < 0)\n+\t\t\tif (config_parse_pair(key, value, kvi, fn, data) < 0)\n \t\t\t\treturn -1;\n \t\t}\n \t\telse {\n@@ -780,10 +799,13 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tint ret = 0;\n \tchar *envw = NULL;\n \tstruct config_source source = CONFIG_SOURCE_INIT;\n+\tstruct key_value_info kvi = KVI_INIT;\n \n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n \tconfig_reader_push_source(&the_reader, &source);\n \n+\tkvi_from_param(&kvi);\n+\n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n \t\tunsigned long count;\n@@ -819,7 +841,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \t\t\t}\n \t\t\tstrbuf_reset(&envvar);\n \n-\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n+\t\t\tif (config_parse_pair(key, value, &kvi, fn, data) < 0) {\n \t\t\t\tret = -1;\n \t\t\t\tgoto out;\n \t\t\t}\n@@ -830,7 +852,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tif (env) {\n \t\t/* sq_dequote will write over it */\n \t\tenvw = xstrdup(env);\n-\t\tif (parse_config_env_list(envw, fn, data) < 0) {\n+\t\tif (parse_config_env_list(envw, &kvi, fn, data) < 0) {\n \t\t\tret = -1;\n \t\t\tgoto out;\n \t\t}\n@@ -2442,7 +2464,8 @@ static int configset_find_element(struct config_set *set, const char *key,\n \treturn 0;\n }\n \n-static int configset_add_value(struct config_reader *reader,\n+static int configset_add_value(const struct key_value_info *kvi_p,\n+\t\t\t       struct config_reader *reader,\n \t\t\t       struct config_set *set, const char *key,\n \t\t\t       const char *value)\n {\n@@ -2476,13 +2499,9 @@ static int configset_add_value(struct config_reader *reader,\n \tif (!reader->source)\n \t\tBUG(\"configset_add_value has no source\");\n \tif (reader->source->name) {\n-\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n+\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n \t} else {\n-\t\t/* for values read from `git_config_from_parameters()` */\n-\t\tkv_info->filename = NULL;\n-\t\tkv_info->linenr = -1;\n-\t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n-\t\tkv_info->scope = reader->parsing_scope;\n+\t\tkvi_from_param(kv_info);\n \t}\n \tsi->util = kv_info;\n \n@@ -2538,11 +2557,12 @@ struct configset_add_data {\n #define CONFIGSET_ADD_INIT { 0 }\n \n static int config_set_callback(const char *key, const char *value,\n-\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       const struct config_context *ctx,\n \t\t\t       void *cb)\n {\n \tstruct configset_add_data *data = cb;\n-\tconfigset_add_value(data->config_reader, data->config_set, key, value);\n+\tconfigset_add_value(ctx->kvi, data->config_reader, data->config_set,\n+\t\t\t    key, value);\n \treturn 0;\n }\n \n@@ -4037,16 +4057,6 @@ const char *config_origin_type_name(enum config_origin_type type)\n \t}\n }\n \n-const char *current_config_origin_type(void)\n-{\n-\tenum config_origin_type type = CONFIG_ORIGIN_UNKNOWN;\n-\n-\tif (reader_origin_type(&the_reader, &type))\n-\t\tBUG(\"current_config_origin_type called outside config callback\");\n-\n-\treturn config_origin_type_name(type);\n-}\n-\n const char *config_scope_name(enum config_scope scope)\n {\n \tswitch (scope) {\n@@ -4078,14 +4088,6 @@ static int reader_config_name(struct config_reader *reader, const char **out)\n \treturn 0;\n }\n \n-const char *current_config_name(void)\n-{\n-\tconst char *name;\n-\tif (reader_config_name(&the_reader, &name))\n-\t\tBUG(\"current_config_name called outside config callback\");\n-\treturn name ? name : \"\";\n-}\n-\n enum config_scope current_config_scope(void)\n {\n \tif (the_reader.config_kvi)\ndiff --git a/config.h b/config.h\nindex 206bf1f175a..ea92392400e 100644\n--- a/config.h\n+++ b/config.h\n@@ -387,9 +387,8 @@ void git_global_config(char **user, char **xdg);\n int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n \n enum config_scope current_config_scope(void);\n-const char *current_config_origin_type(void);\n-const char *current_config_name(void);\n const char *config_origin_type_name(enum config_origin_type type);\n+void kvi_from_param(struct key_value_info *out);\n \n /*\n  * Match and parse a config key of the form:\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 86bfbc2b364..387d336c91f 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1668,6 +1668,21 @@ test_expect_success 'urlmatch' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'urlmatch with --show-scope' '\n+\tcat >.git/config <<-\\EOF &&\n+\t[http \"https://weak.example.com\"]\n+\t\tsslVerify = false\n+\t\tcookieFile = /tmp/cookie.txt\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\tlocal\thttp.cookiefile /tmp/cookie.txt\n+\tlocal\thttp.sslverify false\n+\tEOF\n+\tgit config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'urlmatch favors more specific URLs' '\n \tcat >.git/config <<-\\EOF &&\n \t[http \"https://example.com/\"]\n@@ -2055,6 +2070,12 @@ test_expect_success '--show-origin blob ref' '\n \ttest_cmp expect output\n '\n \n+test_expect_success '--show-origin with --default' '\n+\tgit config --show-origin --default foo some.key >actual &&\n+\techo \"command line:\tfoo\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success '--show-scope with --list' '\n \tcat >expect <<-EOF &&\n \tglobal\tuser.global=true\n@@ -2123,6 +2144,12 @@ test_expect_success '--show-scope with --show-origin' '\n \ttest_cmp expect output\n '\n \n+test_expect_success '--show-scope with --default' '\n+\tgit config --show-scope --default foo some.key >actual &&\n+\techo \"command\tfoo\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'override global and system config' '\n \ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n \tcat >\"$HOME\"/.gitconfig <<-EOF &&\n-- \ngitgitgadget\n\n"},{"id":"478979","messageId":"7d64dcbdadea163a84b1512ab0034362602e40c8.1687980393.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","subject":"[PATCH v5 07/11] trace2: plumb config kvi","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-28T19:26:26Z","receivedAt":"2023-06-28T19:27:02Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThere is a code path starting from trace2_def_param_fl() that eventually\ncalls current_config_scope(), and thus it needs to have \"kvi\" plumbed\nthrough it. Additional plumbing is also needed to get \"kvi\" to\ntrace2_def_param_fl(), which gets called by two code paths:\n\n- Through tr2_cfg_cb(), which is a config callback, so it trivially\n  receives \"kvi\" via the \"struct config_context ctx\" parameter.\n\n- Through tr2_list_env_vars_fl(), which is a high level function that\n  lists environment variables for tracing. This has been secretly\n  behaving like git_config_from_parameters() (in that it parses config\n  from environment variables/the CLI), but does not set config source\n  information.\n\n  Teach tr2_list_env_vars_fl() to be well-behaved by using\n  kvi_from_param(), which is used elsewhere for CLI/environment\n  variable-based config.\n\nAs a result, current_config_scope() has no more callers, so remove it.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c                | 46 -----------------------------------------\n config.h                |  1 -\n trace2.c                |  4 ++--\n trace2.h                |  3 ++-\n trace2/tr2_cfg.c        |  9 +++++---\n trace2/tr2_tgt.h        |  4 +++-\n trace2/tr2_tgt_event.c  |  4 ++--\n trace2/tr2_tgt_normal.c |  4 ++--\n trace2/tr2_tgt_perf.c   |  4 ++--\n 9 files changed, 19 insertions(+), 60 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 4986942b091..d10faba56d3 100644\n--- a/config.c\n+++ b/config.c\n@@ -85,16 +85,6 @@ struct config_reader {\n \t */\n \tstruct config_source *source;\n \tstruct key_value_info *config_kvi;\n-\t/*\n-\t * The \"scope\" of the current config source being parsed (repo, global,\n-\t * etc). Like \"source\", this is only set when parsing a config source.\n-\t * It's not part of \"source\" because it transcends a single file (i.e.,\n-\t * a file included from .git/config is still in \"repo\" scope).\n-\t *\n-\t * When iterating through a configset, the equivalent value is\n-\t * \"config_kvi.scope\" (see above).\n-\t */\n-\tenum config_scope parsing_scope;\n };\n /*\n  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n@@ -125,19 +115,9 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\n static inline void config_reader_set_kvi(struct config_reader *reader,\n \t\t\t\t\t struct key_value_info *kvi)\n {\n-\tif (kvi && (reader->source || reader->parsing_scope))\n-\t\tBUG(\"kvi should not be set while parsing a config source\");\n \treader->config_kvi = kvi;\n }\n \n-static inline void config_reader_set_scope(struct config_reader *reader,\n-\t\t\t\t\t   enum config_scope scope)\n-{\n-\tif (scope && reader->config_kvi)\n-\t\tBUG(\"scope should only be set when iterating through a config source\");\n-\treader->parsing_scope = scope;\n-}\n-\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -412,19 +392,13 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tenum config_scope store_scope = inc->config_reader->parsing_scope;\n-\n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tconfig_reader_set_scope(inc->config_reader, 0);\n-\n \tinc->remote_urls = xmalloc(sizeof(*inc->remote_urls));\n \tstring_list_init_dup(inc->remote_urls);\n \tconfig_with_options(add_remote_url, inc->remote_urls,\n \t\t\t    inc->config_source, inc->repo, &opts);\n-\n-\tconfig_reader_set_scope(inc->config_reader, store_scope);\n }\n \n static int forbid_remote_url(const char *var, const char *value UNUSED,\n@@ -2255,7 +2229,6 @@ static int do_git_config_sequence(struct config_reader *reader,\n \tchar *user_config = NULL;\n \tchar *repo_config;\n \tchar *worktree_config;\n-\tenum config_scope prev_parsing_scope = reader->parsing_scope;\n \n \t/*\n \t * Ensure that either:\n@@ -2273,7 +2246,6 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\tworktree_config = NULL;\n \t}\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_SYSTEM);\n \tif (git_config_system() && system_config &&\n \t    !access_or_die(system_config, R_OK,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n@@ -2281,7 +2253,6 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n \t\t\t\t\t\t\t NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_GLOBAL);\n \tgit_global_config(&user_config, &xdg_config);\n \n \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n@@ -2292,13 +2263,11 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\tret += git_config_from_file_with_options(fn, user_config, data,\n \t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_LOCAL);\n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n \t\tret += git_config_from_file_with_options(fn, repo_config, data,\n \t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_WORKTREE);\n \tif (!opts->ignore_worktree && worktree_config &&\n \t    repo && repo->repository_format_worktree_config &&\n \t    !access_or_die(worktree_config, R_OK, 0)) {\n@@ -2307,11 +2276,9 @@ static int do_git_config_sequence(struct config_reader *reader,\n \t\t\t\t\t\t\t\t NULL);\n \t}\n \n-\tconfig_reader_set_scope(reader, CONFIG_SCOPE_COMMAND);\n \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n \t\tdie(_(\"unable to parse command-line config\"));\n \n-\tconfig_reader_set_scope(reader, prev_parsing_scope);\n \tfree(system_config);\n \tfree(xdg_config);\n \tfree(user_config);\n@@ -2326,7 +2293,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\t\tconst struct config_options *opts)\n {\n \tstruct config_include_data inc = CONFIG_INCLUDE_INIT;\n-\tenum config_scope prev_scope = the_reader.parsing_scope;\n \tint ret;\n \n \tif (opts->respect_includes) {\n@@ -2340,9 +2306,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tdata = &inc;\n \t}\n \n-\tif (config_source)\n-\t\tconfig_reader_set_scope(&the_reader, config_source->scope);\n-\n \t/*\n \t * If we have a specific filename, use it. Otherwise, follow the\n \t * regular lookup sequence.\n@@ -2364,7 +2327,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tstring_list_clear(inc.remote_urls, 0);\n \t\tFREE_AND_NULL(inc.remote_urls);\n \t}\n-\tconfig_reader_set_scope(&the_reader, prev_scope);\n \treturn ret;\n }\n \n@@ -4088,14 +4050,6 @@ static int reader_config_name(struct config_reader *reader, const char **out)\n \treturn 0;\n }\n \n-enum config_scope current_config_scope(void)\n-{\n-\tif (the_reader.config_kvi)\n-\t\treturn the_reader.config_kvi->scope;\n-\telse\n-\t\treturn the_reader.parsing_scope;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex ea92392400e..82eeba94e71 100644\n--- a/config.h\n+++ b/config.h\n@@ -386,7 +386,6 @@ void git_global_config(char **user, char **xdg);\n \n int git_config_parse_parameter(const char *, config_fn_t fn, void *data);\n \n-enum config_scope current_config_scope(void);\n const char *config_origin_type_name(enum config_origin_type type);\n void kvi_from_param(struct key_value_info *out);\n \ndiff --git a/trace2.c b/trace2.c\nindex 0efc4e7b958..49c23bfd05a 100644\n--- a/trace2.c\n+++ b/trace2.c\n@@ -634,7 +634,7 @@ void trace2_thread_exit_fl(const char *file, int line)\n }\n \n void trace2_def_param_fl(const char *file, int line, const char *param,\n-\t\t\t const char *value)\n+\t\t\t const char *value, const struct key_value_info *kvi)\n {\n \tstruct tr2_tgt *tgt_j;\n \tint j;\n@@ -644,7 +644,7 @@ void trace2_def_param_fl(const char *file, int line, const char *param,\n \n \tfor_each_wanted_builtin (j, tgt_j)\n \t\tif (tgt_j->pfn_param_fl)\n-\t\t\ttgt_j->pfn_param_fl(file, line, param, value);\n+\t\t\ttgt_j->pfn_param_fl(file, line, param, value, kvi);\n }\n \n void trace2_def_repo_fl(const char *file, int line, struct repository *repo)\ndiff --git a/trace2.h b/trace2.h\nindex 4ced30c0db3..f5c5a9e6bac 100644\n--- a/trace2.h\n+++ b/trace2.h\n@@ -325,6 +325,7 @@ void trace2_thread_exit_fl(const char *file, int line);\n \n #define trace2_thread_exit() trace2_thread_exit_fl(__FILE__, __LINE__)\n \n+struct key_value_info;\n /*\n  * Emits a \"def_param\" message containing a key/value pair.\n  *\n@@ -334,7 +335,7 @@ void trace2_thread_exit_fl(const char *file, int line);\n  * `core.abbrev`, `status.showUntrackedFiles`, or `--no-ahead-behind`.\n  */\n void trace2_def_param_fl(const char *file, int line, const char *param,\n-\t\t\t const char *value);\n+\t\t\t const char *value, const struct key_value_info *kvi);\n \n #define trace2_def_param(param, value) \\\n \ttrace2_def_param_fl(__FILE__, __LINE__, (param), (value))\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 83bc4fd109c..733d9d2872a 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -100,7 +100,7 @@ struct tr2_cfg_data {\n  * See if the given config key matches any of our patterns of interest.\n  */\n static int tr2_cfg_cb(const char *key, const char *value,\n-\t\t      const struct config_context *ctx UNUSED, void *d)\n+\t\t      const struct config_context *ctx, void *d)\n {\n \tstruct strbuf **s;\n \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n@@ -109,7 +109,8 @@ static int tr2_cfg_cb(const char *key, const char *value,\n \t\tstruct strbuf *buf = *s;\n \t\tint wm = wildmatch(buf->buf, key, WM_CASEFOLD);\n \t\tif (wm == WM_MATCH) {\n-\t\t\ttrace2_def_param_fl(data->file, data->line, key, value);\n+\t\t\ttrace2_def_param_fl(data->file, data->line, key, value,\n+\t\t\t\t\t    ctx->kvi);\n \t\t\treturn 0;\n \t\t}\n \t}\n@@ -127,8 +128,10 @@ void tr2_cfg_list_config_fl(const char *file, int line)\n \n void tr2_list_env_vars_fl(const char *file, int line)\n {\n+\tstruct key_value_info kvi = KVI_INIT;\n \tstruct strbuf **s;\n \n+\tkvi_from_param(&kvi);\n \tif (tr2_load_env_vars() <= 0)\n \t\treturn;\n \n@@ -136,7 +139,7 @@ void tr2_list_env_vars_fl(const char *file, int line)\n \t\tstruct strbuf *buf = *s;\n \t\tconst char *val = getenv(buf->buf);\n \t\tif (val && *val)\n-\t\t\ttrace2_def_param_fl(file, line, buf->buf, val);\n+\t\t\ttrace2_def_param_fl(file, line, buf->buf, val, &kvi);\n \t}\n }\n \ndiff --git a/trace2/tr2_tgt.h b/trace2/tr2_tgt.h\nindex bf8745c4f05..1f626cffea0 100644\n--- a/trace2/tr2_tgt.h\n+++ b/trace2/tr2_tgt.h\n@@ -69,8 +69,10 @@ typedef void(tr2_tgt_evt_exec_result_fl_t)(const char *file, int line,\n \t\t\t\t\t   uint64_t us_elapsed_absolute,\n \t\t\t\t\t   int exec_id, int code);\n \n+struct key_value_info;\n typedef void(tr2_tgt_evt_param_fl_t)(const char *file, int line,\n-\t\t\t\t     const char *param, const char *value);\n+\t\t\t\t     const char *param, const char *value,\n+\t\t\t\t     const struct key_value_info *kvi);\n \n typedef void(tr2_tgt_evt_repo_fl_t)(const char *file, int line,\n \t\t\t\t    const struct repository *repo);\ndiff --git a/trace2/tr2_tgt_event.c b/trace2/tr2_tgt_event.c\nindex 2af53e5d4de..53091781eca 100644\n--- a/trace2/tr2_tgt_event.c\n+++ b/trace2/tr2_tgt_event.c\n@@ -477,11 +477,11 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, const struct key_value_info *kvi)\n {\n \tconst char *event_name = \"def_param\";\n \tstruct json_writer jw = JSON_WRITER_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tjw_object_begin(&jw, 0);\ndiff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c\nindex 1ebfb464d54..d25ea131643 100644\n--- a/trace2/tr2_tgt_normal.c\n+++ b/trace2/tr2_tgt_normal.c\n@@ -297,10 +297,10 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, const struct key_value_info *kvi)\n {\n \tstruct strbuf buf_payload = STRBUF_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tstrbuf_addf(&buf_payload, \"def_param scope:%s %s=%s\", scope_name, param,\ndiff --git a/trace2/tr2_tgt_perf.c b/trace2/tr2_tgt_perf.c\nindex 328e483a05e..a6f9a8a193e 100644\n--- a/trace2/tr2_tgt_perf.c\n+++ b/trace2/tr2_tgt_perf.c\n@@ -439,12 +439,12 @@ static void fn_exec_result_fl(const char *file, int line,\n }\n \n static void fn_param_fl(const char *file, int line, const char *param,\n-\t\t\tconst char *value)\n+\t\t\tconst char *value, const struct key_value_info *kvi)\n {\n \tconst char *event_name = \"def_param\";\n \tstruct strbuf buf_payload = STRBUF_INIT;\n \tstruct strbuf scope_payload = STRBUF_INIT;\n-\tenum config_scope scope = current_config_scope();\n+\tenum config_scope scope = kvi->scope;\n \tconst char *scope_name = config_scope_name(scope);\n \n \tstrbuf_addf(&buf_payload, \"%s:%s\", param, value);\n-- \ngitgitgadget\n\n"},{"id":"478981","messageId":"a9a0a50f32a6d01e2d5cd411f97588734aea2fd1.1687980392.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","subject":"[PATCH v5 03/11] config: add ctx arg to config_fn_t","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-28T19:26:22Z","receivedAt":"2023-06-28T19:27:04Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nAdd a new \"const struct config_context *ctx\" arg to config_fn_t to hold\nadditional information about the config iteration operation.\nconfig_context has a \"struct key_value_info kvi\" member that holds\nmetadata about the config source being read (e.g. what kind of config\nsource it is, the filename, etc). In this series, we're only interested\nin .kvi, so we could have just used \"struct key_value_info\" as an arg,\nbut config_context makes it possible to add/adjust members in the future\nwithout changing the config_fn_t signature. We could also consider other\nways of organizing the args (e.g. moving the config name and value into\nconfig_context or key_value_info), but in my experiments, the\nincremental benefit doesn't justify the added complexity (e.g. a\nconfig_fn_t will sometimes invoke another config_fn_t but with a\ndifferent config value).\n\nIn subsequent commits, the .kvi member will replace the global \"struct\nconfig_reader\" in config.c, making config iteration a global-free\noperation. It requires much more work for the machinery to provide\nmeaningful values of .kvi, so for now, merely change the signature and\ncall sites, pass NULL as a placeholder value, and don't rely on the arg\nin any meaningful way.\n\nMost of the changes are performed by\ncontrib/coccinelle/config_fn_ctx.pending.cocci, which, for every\nconfig_fn_t:\n\n- Modifies the signature to accept \"const struct config_context *ctx\"\n- Passes \"ctx\" to any inner config_fn_t, if needed\n- Adds UNUSED attributes to \"ctx\", if needed\n\nMost config_fn_t instances are easily identified by seeing if they are\ncalled by the various config functions. Most of the remaining ones are\nmanually named in the .cocci patch. Manual cleanups are still needed,\nbut the majority of it is trivial; it's either adjusting config_fn_t\nthat the .cocci patch didn't catch, or adding forward declarations of\n\"struct config_context ctx\" to make the signatures make sense.\n\nThe non-trivial changes are in cases where we are invoking a config_fn_t\noutside of config machinery, and we now need to decide what value of\n\"ctx\" to pass. These cases are:\n\n- trace2/tr2_cfg.c:tr2_cfg_set_fl()\n\n  This is indirectly called by git_config_set() so that the trace2\n  machinery can notice the new config values and update its settings\n  using the tr2 config parsing function, i.e. tr2_cfg_cb().\n\n- builtin/checkout.c:checkout_main()\n\n  This calls git_xmerge_config() as a shorthand for parsing a CLI arg.\n  This might be worth refactoring away in the future, since\n  git_xmerge_config() can call git_default_config(), which can do much\n  more than just parsing.\n\nHandle them by creating a KVI_INIT macro that initializes \"struct\nkey_value_info\" to a reasonable default, and use that to construct the\n\"ctx\" arg.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n alias.c                                       |   3 +-\n archive-tar.c                                 |   3 +-\n archive-zip.c                                 |   1 +\n builtin/add.c                                 |   5 +-\n builtin/blame.c                               |   5 +-\n builtin/branch.c                              |   5 +-\n builtin/cat-file.c                            |   5 +-\n builtin/checkout.c                            |  12 +-\n builtin/clean.c                               |   5 +-\n builtin/clone.c                               |  11 +-\n builtin/column.c                              |   3 +-\n builtin/commit-graph.c                        |   1 +\n builtin/commit.c                              |  10 +-\n builtin/config.c                              |  10 +-\n builtin/difftool.c                            |   5 +-\n builtin/fetch.c                               |   9 +-\n builtin/fsmonitor--daemon.c                   |   5 +-\n builtin/grep.c                                |   7 +-\n builtin/help.c                                |   5 +-\n builtin/index-pack.c                          |   5 +-\n builtin/log.c                                 |  10 +-\n builtin/merge.c                               |   7 +-\n builtin/multi-pack-index.c                    |   1 +\n builtin/pack-objects.c                        |   5 +-\n builtin/patch-id.c                            |   5 +-\n builtin/pull.c                                |   5 +-\n builtin/push.c                                |   5 +-\n builtin/read-tree.c                           |   5 +-\n builtin/rebase.c                              |   5 +-\n builtin/receive-pack.c                        |   5 +-\n builtin/reflog.c                              |   7 +-\n builtin/remote.c                              |   7 +-\n builtin/repack.c                              |   5 +-\n builtin/reset.c                               |   5 +-\n builtin/send-pack.c                           |   5 +-\n builtin/show-branch.c                         |   5 +-\n builtin/stash.c                               |   5 +-\n builtin/submodule--helper.c                   |   1 +\n builtin/tag.c                                 |   5 +-\n builtin/var.c                                 |   5 +-\n builtin/worktree.c                            |   5 +-\n bundle-uri.c                                  |   8 +-\n compat/mingw.c                                |   3 +-\n compat/mingw.h                                |   4 +-\n config.c                                      |  38 +++--\n config.h                                      |  41 +++--\n connect.c                                     |   4 +-\n .../coccinelle/config_fn_ctx.pending.cocci    | 144 ++++++++++++++++++\n convert.c                                     |   4 +-\n credential.c                                  |   1 +\n delta-islands.c                               |   4 +-\n diff.c                                        |  10 +-\n diff.h                                        |   7 +-\n fetch-pack.c                                  |   5 +-\n fmt-merge-msg.c                               |   5 +-\n fmt-merge-msg.h                               |   3 +-\n fsck.c                                        |   9 +-\n fsck.h                                        |   4 +-\n git-compat-util.h                             |   2 +\n gpg-interface.c                               |   7 +-\n grep.c                                        |   7 +-\n grep.h                                        |   4 +-\n help.c                                        |   7 +-\n http.c                                        |   5 +-\n ident.c                                       |   4 +-\n ident.h                                       |   4 +-\n imap-send.c                                   |   5 +-\n ll-merge.c                                    |   1 +\n ls-refs.c                                     |   1 +\n mailinfo.c                                    |   5 +-\n notes-utils.c                                 |   4 +-\n notes.c                                       |   4 +-\n pager.c                                       |   5 +-\n pretty.c                                      |   1 +\n promisor-remote.c                             |   4 +-\n remote.c                                      |   3 +-\n revision.c                                    |   4 +-\n scalar.c                                      |   4 +-\n sequencer.c                                   |   9 +-\n setup.c                                       |  16 +-\n submodule-config.c                            |  17 ++-\n t/helper/test-config.c                        |  11 +-\n t/helper/test-userdiff.c                      |   4 +-\n trace2/tr2_cfg.c                              |   9 +-\n trace2/tr2_sysenv.c                           |   3 +-\n trailer.c                                     |   2 +\n upload-pack.c                                 |   8 +-\n urlmatch.c                                    |   7 +-\n urlmatch.h                                    |   3 +-\n xdiff-interface.c                             |   5 +-\n xdiff-interface.h                             |   4 +-\n 91 files changed, 515 insertions(+), 181 deletions(-)\n create mode 100644 contrib/coccinelle/config_fn_ctx.pending.cocci\n\ndiff --git a/alias.c b/alias.c\nindex 54a1a23d2cf..910dd252a01 100644\n--- a/alias.c\n+++ b/alias.c\n@@ -12,7 +12,8 @@ struct config_alias_data {\n \tstruct string_list *list;\n };\n \n-static int config_alias_cb(const char *key, const char *value, void *d)\n+static int config_alias_cb(const char *key, const char *value,\n+\t\t\t   const struct config_context *ctx UNUSED, void *d)\n {\n \tstruct config_alias_data *data = d;\n \tconst char *p;\ndiff --git a/archive-tar.c b/archive-tar.c\nindex 4cd81d8161e..ef06e516b1f 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -411,7 +411,8 @@ static int tar_filter_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-static int git_tar_config(const char *var, const char *value, void *cb)\n+static int git_tar_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n {\n \tif (!strcmp(var, \"tar.umask\")) {\n \t\tif (value && !strcmp(value, \"user\")) {\ndiff --git a/archive-zip.c b/archive-zip.c\nindex d0d065a312e..b6811951955 100644\n--- a/archive-zip.c\n+++ b/archive-zip.c\n@@ -617,6 +617,7 @@ static void dos_time(timestamp_t *timestamp, int *dos_date, int *dos_time)\n }\n \n static int archive_zip_config(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx UNUSED,\n \t\t\t      void *data UNUSED)\n {\n \treturn userdiff_config(var, value);\ndiff --git a/builtin/add.c b/builtin/add.c\nindex e01efdfc50d..1009ae13c13 100644\n--- a/builtin/add.c\n+++ b/builtin/add.c\n@@ -357,7 +357,8 @@ static struct option builtin_add_options[] = {\n \tOPT_END(),\n };\n \n-static int add_config(const char *var, const char *value, void *cb)\n+static int add_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"add.ignoreerrors\") ||\n \t    !strcmp(var, \"add.ignore-errors\")) {\n@@ -368,7 +369,7 @@ static int add_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static const char embedded_advice[] = N_(\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 2df6039a6e0..d0970a1ab13 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -694,7 +694,8 @@ static const char *add_prefix(const char *prefix, const char *path)\n \treturn prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n }\n \n-static int git_blame_config(const char *var, const char *value, void *cb)\n+static int git_blame_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"blame.showroot\")) {\n \t\tshow_root = git_config_bool(var, value);\n@@ -767,7 +768,7 @@ static int git_blame_config(const char *var, const char *value, void *cb)\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int blame_copy_callback(const struct option *option, const char *arg, int unset)\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 8337b9e71bb..af6d2e75fb0 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -83,7 +83,8 @@ static unsigned int colopts;\n \n define_list_config_array(color_branch_slots);\n \n-static int git_branch_config(const char *var, const char *value, void *cb)\n+static int git_branch_config(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -120,7 +121,7 @@ static int git_branch_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static const char *branch_get_color(enum color_branch ix)\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 7ff56d5a781..b3c41b54c8d 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -873,12 +873,13 @@ static int batch_objects(struct batch_options *opt)\n \treturn retval;\n }\n \n-static int git_cat_file_config(const char *var, const char *value, void *cb)\n+static int git_cat_file_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int batch_option_callback(const struct option *opt,\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 715eeb5048f..4e1f7dc26c2 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1186,7 +1186,8 @@ static int switch_branches(const struct checkout_opts *opts,\n \treturn ret || writeout_error;\n }\n \n-static int git_checkout_config(const char *var, const char *value, void *cb)\n+static int git_checkout_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tstruct checkout_opts *opts = cb;\n \n@@ -1202,7 +1203,7 @@ static int git_checkout_config(const char *var, const char *value, void *cb)\n \tif (starts_with(var, \"submodule.\"))\n \t\treturn git_default_submodule_config(var, value, NULL);\n \n-\treturn git_xmerge_config(var, value, NULL);\n+\treturn git_xmerge_config(var, value, ctx, NULL);\n }\n \n static void setup_new_branch_info_and_source_tree(\n@@ -1689,8 +1690,13 @@ static int checkout_main(int argc, const char **argv, const char *prefix,\n \t}\n \n \tif (opts->conflict_style) {\n+\t\tstruct key_value_info kvi = KVI_INIT;\n+\t\tstruct config_context ctx = {\n+\t\t\t.kvi = &kvi,\n+\t\t};\n \t\topts->merge = 1; /* implied */\n-\t\tgit_xmerge_config(\"merge.conflictstyle\", opts->conflict_style, NULL);\n+\t\tgit_xmerge_config(\"merge.conflictstyle\", opts->conflict_style,\n+\t\t\t\t  &ctx, NULL);\n \t}\n \tif (opts->force) {\n \t\topts->discard_changes = 1;\ndiff --git a/builtin/clean.c b/builtin/clean.c\nindex 57e7f7cac64..5eff1b802a7 100644\n--- a/builtin/clean.c\n+++ b/builtin/clean.c\n@@ -103,7 +103,8 @@ struct menu_stuff {\n \n define_list_config_array(color_interactive_slots);\n \n-static int git_clean_config(const char *var, const char *value, void *cb)\n+static int git_clean_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -133,7 +134,7 @@ static int git_clean_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static const char *clean_get_color(enum color_clean ix)\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 15f9912b4ca..c0f6e067493 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -790,7 +790,8 @@ static int checkout(int submodule_progress, int filter_submodules)\n \treturn err;\n }\n \n-static int git_clone_config(const char *k, const char *v, void *cb)\n+static int git_clone_config(const char *k, const char *v,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(k, \"clone.defaultremotename\")) {\n \t\tfree(remote_name);\n@@ -801,17 +802,19 @@ static int git_clone_config(const char *k, const char *v, void *cb)\n \tif (!strcmp(k, \"clone.filtersubmodules\"))\n \t\tconfig_filter_submodules = git_config_bool(k, v);\n \n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n-static int write_one_config(const char *key, const char *value, void *data)\n+static int write_one_config(const char *key, const char *value,\n+\t\t\t    const struct config_context *ctx,\n+\t\t\t    void *data)\n {\n \t/*\n \t * give git_clone_config a chance to write config values back to the\n \t * environment, since git_config_set_multivar_gently only deals with\n \t * config-file writes\n \t */\n-\tint apply_failed = git_clone_config(key, value, data);\n+\tint apply_failed = git_clone_config(key, value, ctx, data);\n \tif (apply_failed)\n \t\treturn apply_failed;\n \ndiff --git a/builtin/column.c b/builtin/column.c\nindex de623a16c2d..4a6148ca479 100644\n--- a/builtin/column.c\n+++ b/builtin/column.c\n@@ -13,7 +13,8 @@ static const char * const builtin_column_usage[] = {\n };\n static unsigned int colopts;\n \n-static int column_config(const char *var, const char *value, void *cb)\n+static int column_config(const char *var, const char *value,\n+\t\t\t const struct config_context *ctx UNUSED, void *cb)\n {\n \treturn git_column_config(var, value, cb, &colopts);\n }\ndiff --git a/builtin/commit-graph.c b/builtin/commit-graph.c\nindex dd732b35348..1185c49239a 100644\n--- a/builtin/commit-graph.c\n+++ b/builtin/commit-graph.c\n@@ -186,6 +186,7 @@ static int write_option_max_new_filters(const struct option *opt,\n }\n \n static int git_commit_graph_write_config(const char *var, const char *value,\n+\t\t\t\t\t const struct config_context *ctx UNUSED,\n \t\t\t\t\t void *cb UNUSED)\n {\n \tif (!strcmp(var, \"commitgraph.maxnewfilters\"))\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 9ab57ea1aaa..6a2b2503328 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1405,7 +1405,8 @@ static int parse_status_slot(const char *slot)\n \treturn LOOKUP_CONFIG(color_status_slots, slot);\n }\n \n-static int git_status_config(const char *k, const char *v, void *cb)\n+static int git_status_config(const char *k, const char *v,\n+\t\t\t     const struct config_context *ctx, void *cb)\n {\n \tstruct wt_status *s = cb;\n \tconst char *slot_name;\n@@ -1490,7 +1491,7 @@ static int git_status_config(const char *k, const char *v, void *cb)\n \t\ts->detect_rename = git_config_rename(k, v);\n \t\treturn 0;\n \t}\n-\treturn git_diff_ui_config(k, v, NULL);\n+\treturn git_diff_ui_config(k, v, ctx, NULL);\n }\n \n int cmd_status(int argc, const char **argv, const char *prefix)\n@@ -1605,7 +1606,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)\n \treturn 0;\n }\n \n-static int git_commit_config(const char *k, const char *v, void *cb)\n+static int git_commit_config(const char *k, const char *v,\n+\t\t\t     const struct config_context *ctx, void *cb)\n {\n \tstruct wt_status *s = cb;\n \n@@ -1627,7 +1629,7 @@ static int git_commit_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_status_config(k, v, s);\n+\treturn git_status_config(k, v, ctx, s);\n }\n \n int cmd_commit(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/config.c b/builtin/config.c\nindex d40fddb042a..f4fccf99cb8 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -217,6 +217,7 @@ static void show_config_scope(struct strbuf *buf)\n }\n \n static int show_all_config(const char *key_, const char *value_,\n+\t\t\t   const struct config_context *ctx UNUSED,\n \t\t\t   void *cb UNUSED)\n {\n \tif (show_origin || show_scope) {\n@@ -301,7 +302,8 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value\n \treturn 0;\n }\n \n-static int collect_config(const char *key_, const char *value_, void *cb)\n+static int collect_config(const char *key_, const char *value_,\n+\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n {\n \tstruct strbuf_list *values = cb;\n \n@@ -470,6 +472,7 @@ static const char *get_colorbool_slot;\n static char parsed_color[COLOR_MAXLEN];\n \n static int git_get_color_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n \t\t\t\tvoid *cb UNUSED)\n {\n \tif (!strcmp(var, get_color_slot)) {\n@@ -503,6 +506,7 @@ static int get_colorbool_found;\n static int get_diff_color_found;\n static int get_color_ui_found;\n static int git_get_colorbool_config(const char *var, const char *value,\n+\t\t\t\t    const struct config_context *ctx UNUSED,\n \t\t\t\t    void *data UNUSED)\n {\n \tif (!strcmp(var, get_colorbool_slot))\n@@ -561,7 +565,9 @@ struct urlmatch_current_candidate_value {\n \tstruct strbuf value;\n };\n \n-static int urlmatch_collect_fn(const char *var, const char *value, void *cb)\n+static int urlmatch_collect_fn(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       void *cb)\n {\n \tstruct string_list *values = cb;\n \tstruct string_list_item *item = string_list_insert(values, var);\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex 0049342f5c0..f289530068b 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -40,14 +40,15 @@ static const char *const builtin_difftool_usage[] = {\n \tNULL\n };\n \n-static int difftool_config(const char *var, const char *value, void *cb)\n+static int difftool_config(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"difftool.trustexitcode\")) {\n \t\ttrust_exit_code = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int print_tool_help(void)\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex e3871048cf6..a4aa0fbb8f5 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -110,7 +110,8 @@ struct fetch_config {\n \tint submodule_fetch_jobs;\n };\n \n-static int git_fetch_config(const char *k, const char *v, void *cb)\n+static int git_fetch_config(const char *k, const char *v,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tstruct fetch_config *fetch_config = cb;\n \n@@ -164,7 +165,7 @@ static int git_fetch_config(const char *k, const char *v, void *cb)\n \t\t\t    \"fetch.output\", v);\n \t}\n \n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n static int parse_refmap_arg(const struct option *opt, const char *arg, int unset)\n@@ -1799,7 +1800,9 @@ struct remote_group_data {\n \tstruct string_list *list;\n };\n \n-static int get_remote_group(const char *key, const char *value, void *priv)\n+static int get_remote_group(const char *key, const char *value,\n+\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    void *priv)\n {\n \tstruct remote_group_data *g = priv;\n \ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex f6dd9a784c1..91a776e2f17 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -37,7 +37,8 @@ static int fsmonitor__start_timeout_sec = 60;\n #define FSMONITOR__ANNOUNCE_STARTUP \"fsmonitor.announcestartup\"\n static int fsmonitor__announce_startup = 0;\n \n-static int fsmonitor_config(const char *var, const char *value, void *cb)\n+static int fsmonitor_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, FSMONITOR__IPC_THREADS)) {\n \t\tint i = git_config_int(var, value);\n@@ -67,7 +68,7 @@ static int fsmonitor_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n /*\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 76cf999d310..757d52b94ec 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -290,13 +290,14 @@ static int wait_all(void)\n \treturn hit;\n }\n \n-static int grep_cmd_config(const char *var, const char *value, void *cb)\n+static int grep_cmd_config(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n-\tint st = grep_config(var, value, cb);\n+\tint st = grep_config(var, value, ctx, cb);\n \n \tif (git_color_config(var, value, cb) < 0)\n \t\tst = -1;\n-\telse if (git_default_config(var, value, cb) < 0)\n+\telse if (git_default_config(var, value, ctx, cb) < 0)\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\ndiff --git a/builtin/help.c b/builtin/help.c\nindex d3cf4af3f6e..c348f201254 100644\n--- a/builtin/help.c\n+++ b/builtin/help.c\n@@ -398,7 +398,8 @@ static int add_man_viewer_info(const char *var, const char *value)\n \treturn 0;\n }\n \n-static int git_help_config(const char *var, const char *value, void *cb)\n+static int git_help_config(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"help.format\")) {\n \t\tif (!value)\n@@ -421,7 +422,7 @@ static int git_help_config(const char *var, const char *value, void *cb)\n \tif (starts_with(var, \"man.\"))\n \t\treturn add_man_viewer_info(var, value);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static struct cmdnames main_cmds, other_cmds;\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex d0d8067510b..de8884ea5c2 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1581,7 +1581,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name,\n \tstrbuf_release(&pack_name);\n }\n \n-static int git_index_pack_config(const char *k, const char *v, void *cb)\n+static int git_index_pack_config(const char *k, const char *v,\n+\t\t\t\t const struct config_context *ctx, void *cb)\n {\n \tstruct pack_idx_option *opts = cb;\n \n@@ -1608,7 +1609,7 @@ static int git_index_pack_config(const char *k, const char *v, void *cb)\n \t\telse\n \t\t\topts->flags &= ~WRITE_REV;\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n static int cmp_uint32(const void *a_, const void *b_)\ndiff --git a/builtin/log.c b/builtin/log.c\nindex c85f13a5d5e..09d6a13075b 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -564,7 +564,8 @@ static int cmd_log_walk(struct rev_info *rev)\n \treturn retval;\n }\n \n-static int git_log_config(const char *var, const char *value, void *cb)\n+static int git_log_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tconst char *slot_name;\n \n@@ -613,7 +614,7 @@ static int git_log_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_diff_ui_config(var, value, cb);\n+\treturn git_diff_ui_config(var, value, ctx, cb);\n }\n \n int cmd_whatchanged(int argc, const char **argv, const char *prefix)\n@@ -979,7 +980,8 @@ static enum cover_from_description parse_cover_from_description(const char *arg)\n \t\tdie(_(\"%s: invalid cover from description mode\"), arg);\n }\n \n-static int git_format_config(const char *var, const char *value, void *cb)\n+static int git_format_config(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"format.headers\")) {\n \t\tif (!value)\n@@ -1108,7 +1110,7 @@ static int git_format_config(const char *var, const char *value, void *cb)\n \tif (!strcmp(var, \"diff.noprefix\"))\n \t\treturn 0;\n \n-\treturn git_log_config(var, value, cb);\n+\treturn git_log_config(var, value, ctx, cb);\n }\n \n static const char *output_directory = NULL;\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 8da3e46abb0..cad624fb797 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -623,7 +623,8 @@ static void parse_branch_merge_options(char *bmo)\n \tfree(argv);\n }\n \n-static int git_merge_config(const char *k, const char *v, void *cb)\n+static int git_merge_config(const char *k, const char *v,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tint status;\n \tconst char *str;\n@@ -668,10 +669,10 @@ static int git_merge_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\tstatus = fmt_merge_msg_config(k, v, cb);\n+\tstatus = fmt_merge_msg_config(k, v, ctx, cb);\n \tif (status)\n \t\treturn status;\n-\treturn git_diff_ui_config(k, v, cb);\n+\treturn git_diff_ui_config(k, v, ctx, cb);\n }\n \n static int read_tree_trivial(struct object_id *common, struct object_id *head,\ndiff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\nindex 1b5083f8b26..a0a7b82cc69 100644\n--- a/builtin/multi-pack-index.c\n+++ b/builtin/multi-pack-index.c\n@@ -82,6 +82,7 @@ static struct option *add_common_options(struct option *prev)\n }\n \n static int git_multi_pack_index_write_config(const char *var, const char *value,\n+\t\t\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t\t\t     void *cb UNUSED)\n {\n \tif (!strcmp(var, \"pack.writebitmaphashcache\")) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 3af2d84f589..34aa0b483a0 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3135,7 +3135,8 @@ static void prepare_pack(int window, int depth)\n \tfree(delta_list);\n }\n \n-static int git_pack_config(const char *k, const char *v, void *cb)\n+static int git_pack_config(const char *k, const char *v,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(k, \"pack.window\")) {\n \t\twindow = git_config_int(k, v);\n@@ -3227,7 +3228,7 @@ static int git_pack_config(const char *k, const char *v, void *cb)\n \t\tex->uri = xstrdup(pack_end + 1);\n \t\toidmap_put(&configured_exclusions, ex);\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n /* Counters for trace2 output when in --stdin-packs mode. */\ndiff --git a/builtin/patch-id.c b/builtin/patch-id.c\nindex 9d5585d3a72..03eddd0fb82 100644\n--- a/builtin/patch-id.c\n+++ b/builtin/patch-id.c\n@@ -196,7 +196,8 @@ struct patch_id_opts {\n \tint verbatim;\n };\n \n-static int git_patch_id_config(const char *var, const char *value, void *cb)\n+static int git_patch_id_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tstruct patch_id_opts *opts = cb;\n \n@@ -209,7 +210,7 @@ static int git_patch_id_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n int cmd_patch_id(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 0c7bac97b75..83fca5b1d46 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -361,7 +361,8 @@ static enum rebase_type config_get_rebase(int *rebase_unspecified)\n /**\n  * Read config variables.\n  */\n-static int git_pull_config(const char *var, const char *value, void *cb)\n+static int git_pull_config(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"rebase.autostash\")) {\n \t\tconfig_autostash = git_config_bool(var, value);\n@@ -374,7 +375,7 @@ static int git_pull_config(const char *var, const char *value, void *cb)\n \t\tcheck_trust_level = 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n /**\ndiff --git a/builtin/push.c b/builtin/push.c\nindex dbdf609daf3..a2f68f77324 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -510,7 +510,8 @@ static void set_push_cert_flags(int *flags, int v)\n }\n \n \n-static int git_push_config(const char *k, const char *v, void *cb)\n+static int git_push_config(const char *k, const char *v,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \tconst char *slot_name;\n \tint *flags = cb;\n@@ -577,7 +578,7 @@ static int git_push_config(const char *k, const char *v, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(k, v, NULL);\n+\treturn git_default_config(k, v, ctx, NULL);\n }\n \n int cmd_push(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/read-tree.c b/builtin/read-tree.c\nindex 440f19b1b87..8877dd6d4b5 100644\n--- a/builtin/read-tree.c\n+++ b/builtin/read-tree.c\n@@ -102,12 +102,13 @@ static int debug_merge(const struct cache_entry * const *stages,\n \treturn 0;\n }\n \n-static int git_read_tree_config(const char *var, const char *value, void *cb)\n+static int git_read_tree_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"submodule.recurse\"))\n \t\treturn git_default_submodule_config(var, value, cb);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix)\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ace1d5e8d11..60930e2d8e0 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -772,7 +772,8 @@ static void parse_rebase_merges_value(struct rebase_options *options, const char\n \t\tdie(_(\"Unknown rebase-merges mode: %s\"), value);\n }\n \n-static int rebase_config(const char *var, const char *value, void *data)\n+static int rebase_config(const char *var, const char *value,\n+\t\t\t const struct config_context *ctx, void *data)\n {\n \tstruct rebase_options *opts = data;\n \n@@ -831,7 +832,7 @@ static int rebase_config(const char *var, const char *value, void *data)\n \t\treturn git_config_string(&opts->default_backend, var, value);\n \t}\n \n-\treturn git_default_config(var, value, data);\n+\treturn git_default_config(var, value, ctx, data);\n }\n \n static int checkout_up_to_date(struct rebase_options *options)\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 1a31a583674..94d9898aff7 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -139,7 +139,8 @@ static enum deny_action parse_deny_action(const char *var, const char *value)\n \treturn DENY_IGNORE;\n }\n \n-static int receive_pack_config(const char *var, const char *value, void *cb)\n+static int receive_pack_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tint status = parse_hide_refs_config(var, value, \"receive\", &hidden_refs);\n \n@@ -266,7 +267,7 @@ static int receive_pack_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static void show_ref(const char *path, const struct object_id *oid)\ndiff --git a/builtin/reflog.c b/builtin/reflog.c\nindex a1fa0c855f4..84251cc9517 100644\n--- a/builtin/reflog.c\n+++ b/builtin/reflog.c\n@@ -108,7 +108,8 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)\n #define EXPIRE_TOTAL   01\n #define EXPIRE_UNREACH 02\n \n-static int reflog_expire_config(const char *var, const char *value, void *cb)\n+static int reflog_expire_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx, void *cb)\n {\n \tconst char *pattern, *key;\n \tsize_t pattern_len;\n@@ -117,7 +118,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)\n \tstruct reflog_expire_cfg *ent;\n \n \tif (parse_config_key(var, \"gc\", &pattern, &pattern_len, &key) < 0)\n-\t\treturn git_default_config(var, value, cb);\n+\t\treturn git_default_config(var, value, ctx, cb);\n \n \tif (!strcmp(key, \"reflogexpire\")) {\n \t\tslot = EXPIRE_TOTAL;\n@@ -128,7 +129,7 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)\n \t\tif (git_config_expiry_date(&expire, var, value))\n \t\t\treturn -1;\n \t} else\n-\t\treturn git_default_config(var, value, cb);\n+\t\treturn git_default_config(var, value, ctx, cb);\n \n \tif (!pattern) {\n \t\tswitch (slot) {\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 1e0b137d977..87de81105e2 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -268,6 +268,7 @@ static const char *abbrev_ref(const char *name, const char *prefix)\n #define abbrev_branch(name) abbrev_ref((name), \"refs/heads/\")\n \n static int config_read_branches(const char *key, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n \t\t\t\tvoid *data UNUSED)\n {\n \tconst char *orig_key = key;\n@@ -645,7 +646,7 @@ struct push_default_info\n };\n \n static int config_read_push_default(const char *key, const char *value,\n-\tvoid *cb)\n+\tconst struct config_context *ctx UNUSED, void *cb)\n {\n \tstruct push_default_info* info = cb;\n \tif (strcmp(key, \"remote.pushdefault\") ||\n@@ -1494,7 +1495,9 @@ static int prune(int argc, const char **argv, const char *prefix)\n \treturn result;\n }\n \n-static int get_remote_default(const char *key, const char *value UNUSED, void *priv)\n+static int get_remote_default(const char *key, const char *value UNUSED,\n+\t\t\t      const struct config_context *ctx UNUSED,\n+\t\t\t      void *priv)\n {\n \tif (strcmp(key, \"remotes.default\") == 0) {\n \t\tint *found = priv;\ndiff --git a/builtin/repack.c b/builtin/repack.c\nindex 0541c3ce157..6f74570bf94 100644\n--- a/builtin/repack.c\n+++ b/builtin/repack.c\n@@ -59,7 +59,8 @@ struct pack_objects_args {\n \tint local;\n };\n \n-static int repack_config(const char *var, const char *value, void *cb)\n+static int repack_config(const char *var, const char *value,\n+\t\t\t const struct config_context *ctx, void *cb)\n {\n \tstruct pack_objects_args *cruft_po_args = cb;\n \tif (!strcmp(var, \"repack.usedeltabaseoffset\")) {\n@@ -91,7 +92,7 @@ static int repack_config(const char *var, const char *value, void *cb)\n \t\treturn git_config_string(&cruft_po_args->depth, var, value);\n \tif (!strcmp(var, \"repack.cruftthreads\"))\n \t\treturn git_config_string(&cruft_po_args->threads, var, value);\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n /*\ndiff --git a/builtin/reset.c b/builtin/reset.c\nindex f99f32d5802..1ae82f2e89c 100644\n--- a/builtin/reset.c\n+++ b/builtin/reset.c\n@@ -312,12 +312,13 @@ static int reset_refs(const char *rev, const struct object_id *oid)\n \treturn update_ref_status;\n }\n \n-static int git_reset_config(const char *var, const char *value, void *cb)\n+static int git_reset_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"submodule.recurse\"))\n \t\treturn git_default_submodule_config(var, value, cb);\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n int cmd_reset(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/send-pack.c b/builtin/send-pack.c\nindex 4784143004d..cd6d9e41129 100644\n--- a/builtin/send-pack.c\n+++ b/builtin/send-pack.c\n@@ -131,7 +131,8 @@ static void print_helper_status(struct ref *ref)\n \tstrbuf_release(&buf);\n }\n \n-static int send_pack_config(const char *k, const char *v, void *cb)\n+static int send_pack_config(const char *k, const char *v,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(k, \"push.gpgsign\")) {\n \t\tconst char *value;\n@@ -151,7 +152,7 @@ static int send_pack_config(const char *k, const char *v, void *cb)\n \t\t\t}\n \t\t}\n \t}\n-\treturn git_default_config(k, v, cb);\n+\treturn git_default_config(k, v, ctx, cb);\n }\n \n int cmd_send_pack(int argc, const char **argv, const char *prefix)\ndiff --git a/builtin/show-branch.c b/builtin/show-branch.c\nindex a2461270d4b..f2fd245b838 100644\n--- a/builtin/show-branch.c\n+++ b/builtin/show-branch.c\n@@ -559,7 +559,8 @@ static void append_one_rev(const char *av)\n \tdie(\"bad sha1 reference %s\", av);\n }\n \n-static int git_show_branch_config(const char *var, const char *value, void *cb)\n+static int git_show_branch_config(const char *var, const char *value,\n+\t\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"showbranch.default\")) {\n \t\tif (!value)\n@@ -582,7 +583,7 @@ static int git_show_branch_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int omit_in_dense(struct commit *commit, struct commit **rev, int n)\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex a7e17ffe384..e5c4246d2d4 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -837,7 +837,8 @@ static int show_stat = 1;\n static int show_patch;\n static int show_include_untracked;\n \n-static int git_stash_config(const char *var, const char *value, void *cb)\n+static int git_stash_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"stash.showstat\")) {\n \t\tshow_stat = git_config_bool(var, value);\n@@ -851,7 +852,7 @@ static int git_stash_config(const char *var, const char *value, void *cb)\n \t\tshow_include_untracked = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n-\treturn git_diff_basic_config(var, value, cb);\n+\treturn git_diff_basic_config(var, value, ctx, cb);\n }\n \n static void diff_include_untracked(const struct stash_info *info, struct diff_options *diff_opt)\ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex 6a16208e8a8..f8e9d85e77a 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2192,6 +2192,7 @@ static int update_clone_task_finished(int result,\n }\n \n static int git_update_clone_config(const char *var, const char *value,\n+\t\t\t\t   const struct config_context *ctx UNUSED,\n \t\t\t\t   void *cb)\n {\n \tint *max_jobs = cb;\ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 1acf5f7a59f..b7dfb5e2cad 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -188,7 +188,8 @@ static const char tag_template_nocleanup[] =\n \t\"Lines starting with '%c' will be kept; you may remove them\"\n \t\" yourself if you want to.\\n\");\n \n-static int git_tag_config(const char *var, const char *value, void *cb)\n+static int git_tag_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"tag.gpgsign\")) {\n \t\tconfig_sign_tag = git_config_bool(var, value);\n@@ -213,7 +214,7 @@ static int git_tag_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static void write_tag_body(int fd, const struct object_id *oid)\ndiff --git a/builtin/var.c b/builtin/var.c\nindex 21499989807..ae011bdf409 100644\n--- a/builtin/var.c\n+++ b/builtin/var.c\n@@ -71,13 +71,14 @@ static const struct git_var *get_git_var(const char *var)\n \treturn NULL;\n }\n \n-static int show_config(const char *var, const char *value, void *cb)\n+static int show_config(const char *var, const char *value,\n+\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (value)\n \t\tprintf(\"%s=%s\\n\", var, value);\n \telse\n \t\tprintf(\"%s\\n\", var);\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n int cmd_var(int argc, const char **argv, const char *prefix UNUSED)\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 5a9cf076ad2..a30d37a273f 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -128,14 +128,15 @@ static int verbose;\n static int guess_remote;\n static timestamp_t expire;\n \n-static int git_worktree_config(const char *var, const char *value, void *cb)\n+static int git_worktree_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"worktree.guessremote\")) {\n \t\tguess_remote = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int delete_git_dir(const char *id)\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex 2a2db1a1d39..0d5acc3dc51 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -224,7 +224,9 @@ static int bundle_list_update(const char *key, const char *value,\n \treturn 0;\n }\n \n-static int config_to_bundle_list(const char *key, const char *value, void *data)\n+static int config_to_bundle_list(const char *key, const char *value,\n+\t\t\t\t const struct config_context *ctx UNUSED,\n+\t\t\t\t void *data)\n {\n \tstruct bundle_list *list = data;\n \treturn bundle_list_update(key, value, list);\n@@ -871,7 +873,9 @@ cached:\n \treturn advertise_bundle_uri;\n }\n \n-static int config_to_packet_line(const char *key, const char *value, void *data)\n+static int config_to_packet_line(const char *key, const char *value,\n+\t\t\t\t const struct config_context *ctx UNUSED,\n+\t\t\t\t void *data)\n {\n \tstruct packet_reader *writer = data;\n \ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex d06cdc6254f..4186bc7a417 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -244,7 +244,8 @@ static int core_restrict_inherited_handles = -1;\n static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;\n static char *unset_environment_variables;\n \n-int mingw_core_config(const char *var, const char *value, void *cb)\n+int mingw_core_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"core.hidedotfiles\")) {\n \t\tif (value && !strcasecmp(value, \"dotgitonly\"))\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 209cf7cebad..5e34c873473 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -11,7 +11,9 @@ typedef _sigset_t sigset_t;\n #undef _POSIX_THREAD_SAFE_FUNCTIONS\n #endif\n \n-int mingw_core_config(const char *var, const char *value, void *cb);\n+struct config_context;\n+int mingw_core_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb);\n #define platform_core_config mingw_core_config\n \n /*\ndiff --git a/config.c b/config.c\nindex 2eeb6621421..850e432e301 100644\n--- a/config.c\n+++ b/config.c\n@@ -209,7 +209,8 @@ struct config_include_data {\n };\n #define CONFIG_INCLUDE_INIT { 0 }\n \n-static int git_config_include(const char *var, const char *value, void *data);\n+static int git_config_include(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx, void *data);\n \n #define MAX_INCLUDE_DEPTH 10\n static const char include_depth_advice[] = N_(\n@@ -388,7 +389,8 @@ static int include_by_branch(const char *cond, size_t cond_len)\n \treturn ret;\n }\n \n-static int add_remote_url(const char *var, const char *value, void *data)\n+static int add_remote_url(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx UNUSED, void *data)\n {\n \tstruct string_list *remote_urls = data;\n \tconst char *remote_name;\n@@ -423,6 +425,7 @@ static void populate_remote_urls(struct config_include_data *inc)\n }\n \n static int forbid_remote_url(const char *var, const char *value UNUSED,\n+\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t     void *data UNUSED)\n {\n \tconst char *remote_name;\n@@ -486,7 +489,9 @@ static int include_condition_is_true(struct config_source *cs,\n \treturn 0;\n }\n \n-static int git_config_include(const char *var, const char *value, void *data)\n+static int git_config_include(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx,\n+\t\t\t      void *data)\n {\n \tstruct config_include_data *inc = data;\n \tstruct config_source *cs = inc->config_reader->source;\n@@ -498,7 +503,7 @@ static int git_config_include(const char *var, const char *value, void *data)\n \t * Pass along all values, including \"include\" directives; this makes it\n \t * possible to query information on the includes themselves.\n \t */\n-\tret = inc->fn(var, value, inc->data);\n+\tret = inc->fn(var, value, NULL, inc->data);\n \tif (ret < 0)\n \t\treturn ret;\n \n@@ -680,7 +685,7 @@ static int config_parse_pair(const char *key, const char *value,\n \tif (git_config_parse_key(key, &canonical_name, NULL))\n \t\treturn -1;\n \n-\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n+\tret = (fn(canonical_name, value, NULL, data) < 0) ? -1 : 0;\n \tfree(canonical_name);\n \treturn ret;\n }\n@@ -968,7 +973,7 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \t * accurate line number in error messages.\n \t */\n \tcs->linenr--;\n-\tret = fn(name->buf, value, data);\n+\tret = fn(name->buf, value, NULL, data);\n \tif (ret >= 0)\n \t\tcs->linenr++;\n \treturn ret;\n@@ -1562,7 +1567,8 @@ int git_config_color(char *dest, const char *var, const char *value)\n \treturn 0;\n }\n \n-static int git_default_core_config(const char *var, const char *value, void *cb)\n+static int git_default_core_config(const char *var, const char *value,\n+\t\t\t\t   const struct config_context *ctx, void *cb)\n {\n \t/* This needs a better name */\n \tif (!strcmp(var, \"core.filemode\")) {\n@@ -1842,7 +1848,7 @@ static int git_default_core_config(const char *var, const char *value, void *cb)\n \t}\n \n \t/* Add other config variables here and to Documentation/config.txt. */\n-\treturn platform_core_config(var, value, cb);\n+\treturn platform_core_config(var, value, ctx, cb);\n }\n \n static int git_default_sparse_config(const char *var, const char *value)\n@@ -1944,15 +1950,16 @@ static int git_default_mailmap_config(const char *var, const char *value)\n \treturn 0;\n }\n \n-int git_default_config(const char *var, const char *value, void *cb)\n+int git_default_config(const char *var, const char *value,\n+\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (starts_with(var, \"core.\"))\n-\t\treturn git_default_core_config(var, value, cb);\n+\t\treturn git_default_core_config(var, value, ctx, cb);\n \n \tif (starts_with(var, \"user.\") ||\n \t    starts_with(var, \"author.\") ||\n \t    starts_with(var, \"committer.\"))\n-\t\treturn git_ident_config(var, value, cb);\n+\t\treturn git_ident_config(var, value, ctx, cb);\n \n \tif (starts_with(var, \"i18n.\"))\n \t\treturn git_default_i18n_config(var, value);\n@@ -2318,7 +2325,7 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \n \t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n \n-\t\tif (fn(entry->key, values->items[value_index].string, data) < 0)\n+\t\tif (fn(entry->key, values->items[value_index].string, NULL, data) < 0)\n \t\t\tgit_die_config_linenr(entry->key,\n \t\t\t\t\t      reader->config_kvi->filename,\n \t\t\t\t\t      reader->config_kvi->linenr);\n@@ -2496,7 +2503,9 @@ struct configset_add_data {\n };\n #define CONFIGSET_ADD_INIT { 0 }\n \n-static int config_set_callback(const char *key, const char *value, void *cb)\n+static int config_set_callback(const char *key, const char *value,\n+\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       void *cb)\n {\n \tstruct configset_add_data *data = cb;\n \tconfigset_add_value(data->config_reader, data->config_set, key, value);\n@@ -3106,7 +3115,8 @@ static int store_aux_event(enum config_event_t type,\n \treturn 0;\n }\n \n-static int store_aux(const char *key, const char *value, void *cb)\n+static int store_aux(const char *key, const char *value,\n+\t\t     const struct config_context *ctx UNUSED, void *cb)\n {\n \tstruct config_store_data *store = cb;\n \ndiff --git a/config.h b/config.h\nindex d1c5577589e..cd30125a8a4 100644\n--- a/config.h\n+++ b/config.h\n@@ -110,8 +110,29 @@ struct config_options {\n \t} error_action;\n };\n \n+/* Config source metadata for a given config key-value pair */\n+struct key_value_info {\n+\tconst char *filename;\n+\tint linenr;\n+\tenum config_origin_type origin_type;\n+\tenum config_scope scope;\n+};\n+#define KVI_INIT { \\\n+\t.filename = NULL, \\\n+\t.linenr = -1, \\\n+\t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n+\t.scope = CONFIG_SCOPE_UNKNOWN, \\\n+}\n+\n+/* Captures additional information that a config callback can use. */\n+struct config_context {\n+\t/* Config source metadata for key and value. */\n+\tconst struct key_value_info *kvi;\n+};\n+#define CONFIG_CONTEXT_INIT { 0 }\n+\n /**\n- * A config callback function takes three parameters:\n+ * A config callback function takes four parameters:\n  *\n  * - the name of the parsed variable. This is in canonical \"flat\" form: the\n  *   section, subsection, and variable segments will be separated by dots,\n@@ -122,15 +143,22 @@ struct config_options {\n  *   value specified, the value will be NULL (typically this means it\n  *   should be interpreted as boolean true).\n  *\n+ * - the 'config context', that is, additional information about the config\n+ *   iteration operation provided by the config machinery. For example, this\n+ *   includes information about the config source being parsed (e.g. the\n+ *   filename).\n+ *\n  * - a void pointer passed in by the caller of the config API; this can\n  *   contain callback-specific data\n  *\n  * A config callback should return 0 for success, or -1 if the variable\n  * could not be parsed properly.\n  */\n-typedef int (*config_fn_t)(const char *, const char *, void *);\n+typedef int (*config_fn_t)(const char *, const char *,\n+\t\t\t   const struct config_context *, void *);\n \n-int git_default_config(const char *, const char *, void *);\n+int git_default_config(const char *, const char *,\n+\t\t       const struct config_context *, void *);\n \n /**\n  * Read a specific file in git-config format.\n@@ -667,13 +695,6 @@ int git_config_get_expiry(const char *key, const char **output);\n /* parse either \"this many days\" integer, or \"5.days.ago\" approxidate */\n int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t now);\n \n-struct key_value_info {\n-\tconst char *filename;\n-\tint linenr;\n-\tenum config_origin_type origin_type;\n-\tenum config_scope scope;\n-};\n-\n /**\n  * First prints the error message specified by the caller in `err` and then\n  * dies printing the line number and the file name of the highest priority\ndiff --git a/connect.c b/connect.c\nindex 3a0186280c4..cddac1d96b8 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -964,7 +964,7 @@ static struct child_process *git_tcp_connect(int fd[2], char *host, int flags)\n static char *git_proxy_command;\n \n static int git_proxy_command_options(const char *var, const char *value,\n-\t\tvoid *cb)\n+\t\tconst struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"core.gitproxy\")) {\n \t\tconst char *for_pos;\n@@ -1010,7 +1010,7 @@ static int git_proxy_command_options(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static int git_use_proxy(const char *host)\ndiff --git a/contrib/coccinelle/config_fn_ctx.pending.cocci b/contrib/coccinelle/config_fn_ctx.pending.cocci\nnew file mode 100644\nindex 00000000000..6d3d1000a96\n--- /dev/null\n+++ b/contrib/coccinelle/config_fn_ctx.pending.cocci\n@@ -0,0 +1,144 @@\n+@ get_fn @\n+identifier fn, R;\n+@@\n+(\n+(\n+git_config_from_file\n+|\n+git_config_from_file_with_options\n+|\n+git_config_from_mem\n+|\n+git_config_from_blob_oid\n+|\n+read_early_config\n+|\n+read_very_early_config\n+|\n+config_with_options\n+|\n+git_config\n+|\n+git_protected_config\n+|\n+config_from_gitmodules\n+)\n+  (fn, ...)\n+|\n+repo_config(R, fn, ...)\n+)\n+\n+@ extends get_fn @\n+identifier C1, C2, D;\n+@@\n+int fn(const char *C1, const char *C2,\n++ const struct config_context *ctx,\n+  void *D);\n+\n+@ extends get_fn @\n+@@\n+int fn(const char *, const char *,\n++ const struct config_context *,\n+  void *);\n+\n+@ extends get_fn @\n+// Don't change fns that look like callback fns but aren't\n+identifier fn2 != tar_filter_config && != git_diff_heuristic_config &&\n+  != git_default_submodule_config && != git_color_config &&\n+  != bundle_list_update && != parse_object_filter_config;\n+identifier C1, C2, D1, D2, S;\n+attribute name UNUSED;\n+@@\n+int fn(const char *C1, const char *C2,\n++ const struct config_context *ctx,\n+  void *D1) {\n+<+...\n+(\n+fn2(C1, C2\n++ , ctx\n+, D2);\n+|\n+if(fn2(C1, C2\n++ , ctx\n+, D2) < 0) { ... }\n+|\n+return fn2(C1, C2\n++ , ctx\n+, D2);\n+|\n+S = fn2(C1, C2\n++ , ctx\n+, D2);\n+)\n+...+>\n+  }\n+\n+@ extends get_fn@\n+identifier C1, C2, D;\n+attribute name UNUSED;\n+@@\n+int fn(const char *C1, const char *C2,\n++ const struct config_context *ctx UNUSED,\n+  void *D) {...}\n+\n+\n+// The previous rules don't catch all callbacks, especially if they're defined\n+// in a separate file from the git_config() call. Fix these manually.\n+@@\n+identifier C1, C2, D;\n+attribute name UNUSED;\n+@@\n+int\n+(\n+git_ident_config\n+|\n+urlmatch_collect_fn\n+|\n+write_one_config\n+|\n+forbid_remote_url\n+|\n+credential_config_callback\n+)\n+  (const char *C1, const char *C2,\n++ const struct config_context *ctx UNUSED,\n+  void *D) {...}\n+\n+@@\n+identifier C1, C2, D, D2, S, fn2;\n+@@\n+int\n+(\n+http_options\n+|\n+git_status_config\n+|\n+git_commit_config\n+|\n+git_default_core_config\n+|\n+grep_config\n+)\n+  (const char *C1, const char *C2,\n++ const struct config_context *ctx,\n+  void *D) {\n+<+...\n+(\n+fn2(C1, C2\n++ , ctx\n+, D2);\n+|\n+if(fn2(C1, C2\n++ , ctx\n+, D2) < 0) { ... }\n+|\n+return fn2(C1, C2\n++ , ctx\n+, D2);\n+|\n+S = fn2(C1, C2\n++ , ctx\n+, D2);\n+)\n+...+>\n+  }\ndiff --git a/convert.c b/convert.c\nindex 9ee79fe4699..8e96cf83030 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1015,7 +1015,9 @@ static int apply_filter(const char *path, const char *src, size_t len,\n \treturn 0;\n }\n \n-static int read_convert_config(const char *var, const char *value, void *cb UNUSED)\n+static int read_convert_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx UNUSED,\n+\t\t\t       void *cb UNUSED)\n {\n \tconst char *key, *name;\n \tsize_t namelen;\ndiff --git a/credential.c b/credential.c\nindex 8825c6f1320..d6647541634 100644\n--- a/credential.c\n+++ b/credential.c\n@@ -49,6 +49,7 @@ static int credential_from_potentially_partial_url(struct credential *c,\n \t\t\t\t\t\t   const char *url);\n \n static int credential_config_callback(const char *var, const char *value,\n+\t\t\t\t      const struct config_context *ctx UNUSED,\n \t\t\t\t      void *data)\n {\n \tstruct credential *c = data;\ndiff --git a/delta-islands.c b/delta-islands.c\nindex c824a5f6a42..5fc6ea6ff55 100644\n--- a/delta-islands.c\n+++ b/delta-islands.c\n@@ -341,7 +341,9 @@ static void free_remote_islands(kh_str_t *remote_islands)\n \tkh_destroy_str(remote_islands);\n }\n \n-static int island_config_callback(const char *k, const char *v, void *cb)\n+static int island_config_callback(const char *k, const char *v,\n+\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t  void *cb)\n {\n \tstruct island_load_data *ild = cb;\n \ndiff --git a/diff.c b/diff.c\nindex c106f8a4ffa..0e382c8f7f0 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -357,7 +357,8 @@ static unsigned parse_color_moved_ws(const char *arg)\n \treturn ret;\n }\n \n-int git_diff_ui_config(const char *var, const char *value, void *cb)\n+int git_diff_ui_config(const char *var, const char *value,\n+\t\t       const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"diff.color\") || !strcmp(var, \"color.diff\")) {\n \t\tdiff_use_color_default = git_config_colorbool(var, value);\n@@ -440,10 +441,11 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)\n \tif (git_color_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_diff_basic_config(var, value, cb);\n+\treturn git_diff_basic_config(var, value, ctx, cb);\n }\n \n-int git_diff_basic_config(const char *var, const char *value, void *cb)\n+int git_diff_basic_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tconst char *name;\n \n@@ -495,7 +497,7 @@ int git_diff_basic_config(const char *var, const char *value, void *cb)\n \tif (git_diff_heuristic_config(var, value, cb) < 0)\n \t\treturn -1;\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static char *quote_two(const char *one, const char *two)\ndiff --git a/diff.h b/diff.h\nindex 6c10ce289da..33a4f4d5988 100644\n--- a/diff.h\n+++ b/diff.h\n@@ -531,10 +531,13 @@ void free_diffstat_info(struct diffstat_t *diffstat);\n int parse_long_opt(const char *opt, const char **argv,\n \t\t   const char **optarg);\n \n-int git_diff_basic_config(const char *var, const char *value, void *cb);\n+struct config_context;\n+int git_diff_basic_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb);\n int git_diff_heuristic_config(const char *var, const char *value, void *cb);\n void init_diff_ui_defaults(void);\n-int git_diff_ui_config(const char *var, const char *value, void *cb);\n+int git_diff_ui_config(const char *var, const char *value,\n+\t\t       const struct config_context *ctx, void *cb);\n void repo_diff_setup(struct repository *, struct diff_options *);\n struct option *add_diff_options(const struct option *, struct diff_options *);\n int diff_opt_parse(struct diff_options *, const char **, int, const char *);\ndiff --git a/fetch-pack.c b/fetch-pack.c\nindex 0f71054fbae..5f3d40f3d09 100644\n--- a/fetch-pack.c\n+++ b/fetch-pack.c\n@@ -1860,7 +1860,8 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,\n \treturn ref;\n }\n \n-static int fetch_pack_config_cb(const char *var, const char *value, void *cb)\n+static int fetch_pack_config_cb(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx, void *cb)\n {\n \tif (strcmp(var, \"fetch.fsck.skiplist\") == 0) {\n \t\tconst char *path;\n@@ -1882,7 +1883,7 @@ static int fetch_pack_config_cb(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n static void fetch_pack_config(void)\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 5af0d4715ba..10137444321 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -20,7 +20,8 @@ static int use_branch_desc;\n static int suppress_dest_pattern_seen;\n static struct string_list suppress_dest_patterns = STRING_LIST_INIT_DUP;\n \n-int fmt_merge_msg_config(const char *key, const char *value, void *cb)\n+int fmt_merge_msg_config(const char *key, const char *value,\n+\t\t\t const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(key, \"merge.log\") || !strcmp(key, \"merge.summary\")) {\n \t\tint is_bool;\n@@ -40,7 +41,7 @@ int fmt_merge_msg_config(const char *key, const char *value, void *cb)\n \t\t\tstring_list_append(&suppress_dest_patterns, value);\n \t\tsuppress_dest_pattern_seen = 1;\n \t} else {\n-\t\treturn git_default_config(key, value, cb);\n+\t\treturn git_default_config(key, value, ctx, cb);\n \t}\n \treturn 0;\n }\ndiff --git a/fmt-merge-msg.h b/fmt-merge-msg.h\nindex 99054042dc5..73ca3e44652 100644\n--- a/fmt-merge-msg.h\n+++ b/fmt-merge-msg.h\n@@ -13,7 +13,8 @@ struct fmt_merge_msg_opts {\n };\n \n extern int merge_log_config;\n-int fmt_merge_msg_config(const char *key, const char *value, void *cb);\n+int fmt_merge_msg_config(const char *key, const char *value,\n+\t\t\t const struct config_context *ctx, void *cb);\n int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \t\t  struct fmt_merge_msg_opts *);\n \ndiff --git a/fsck.c b/fsck.c\nindex 3261ef9ec28..55b6a694853 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1163,7 +1163,9 @@ struct fsck_gitmodules_data {\n \tint ret;\n };\n \n-static int fsck_gitmodules_fn(const char *var, const char *value, void *vdata)\n+static int fsck_gitmodules_fn(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx UNUSED,\n+\t\t\t      void *vdata)\n {\n \tstruct fsck_gitmodules_data *data = vdata;\n \tconst char *subsection, *key;\n@@ -1373,7 +1375,8 @@ int fsck_finish(struct fsck_options *options)\n \treturn ret;\n }\n \n-int git_fsck_config(const char *var, const char *value, void *cb)\n+int git_fsck_config(const char *var, const char *value,\n+\t\t    const struct config_context *ctx, void *cb)\n {\n \tstruct fsck_options *options = cb;\n \tif (strcmp(var, \"fsck.skiplist\") == 0) {\n@@ -1394,7 +1397,7 @@ int git_fsck_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\n \n /*\ndiff --git a/fsck.h b/fsck.h\nindex e17730e9da9..6359ba359bd 100644\n--- a/fsck.h\n+++ b/fsck.h\n@@ -233,10 +233,12 @@ void fsck_put_object_name(struct fsck_options *options,\n const char *fsck_describe_object(struct fsck_options *options,\n \t\t\t\t const struct object_id *oid);\n \n+struct key_value_info;\n /*\n  * git_config() callback for use by fsck-y tools that want to support\n  * fsck.<msg> fsck.skipList etc.\n  */\n-int git_fsck_config(const char *var, const char *value, void *cb);\n+int git_fsck_config(const char *var, const char *value,\n+\t\t    const struct config_context *ctx, void *cb);\n \n #endif\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 5b2b99c17c5..14e8aacb957 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -440,8 +440,10 @@ typedef uintmax_t timestamp_t;\n #endif\n \n #ifndef platform_core_config\n+struct config_context;\n static inline int noop_core_config(const char *var UNUSED,\n \t\t\t\t   const char *value UNUSED,\n+\t\t\t\t   const struct config_context *ctx UNUSED,\n \t\t\t\t   void *cb UNUSED)\n {\n \treturn 0;\ndiff --git a/gpg-interface.c b/gpg-interface.c\nindex 19a3471a0b5..57c862a3a22 100644\n--- a/gpg-interface.c\n+++ b/gpg-interface.c\n@@ -14,7 +14,8 @@\n #include \"alias.h\"\n #include \"wrapper.h\"\n \n-static int git_gpg_config(const char *, const char *, void *);\n+static int git_gpg_config(const char *, const char *,\n+\t\t\t  const struct config_context *, void *);\n \n static void gpg_interface_lazy_init(void)\n {\n@@ -720,7 +721,9 @@ void set_signing_key(const char *key)\n \tconfigured_signing_key = xstrdup(key);\n }\n \n-static int git_gpg_config(const char *var, const char *value, void *cb UNUSED)\n+static int git_gpg_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t  void *cb UNUSED)\n {\n \tstruct gpg_format *fmt = NULL;\n \tchar *fmtname = NULL;\ndiff --git a/grep.c b/grep.c\nindex f00986c451a..fc22c3e2afb 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -56,7 +56,8 @@ define_list_config_array_extra(color_grep_slots, {\"match\"});\n  * Read the configuration file once and store it in\n  * the grep_defaults template.\n  */\n-int grep_config(const char *var, const char *value, void *cb)\n+int grep_config(const char *var, const char *value,\n+\t\tconst struct config_context *ctx, void *cb)\n {\n \tstruct grep_opt *opt = cb;\n \tconst char *slot;\n@@ -91,9 +92,9 @@ int grep_config(const char *var, const char *value, void *cb)\n \tif (!strcmp(var, \"color.grep\"))\n \t\topt->color = git_config_colorbool(var, value);\n \tif (!strcmp(var, \"color.grep.match\")) {\n-\t\tif (grep_config(\"color.grep.matchcontext\", value, cb) < 0)\n+\t\tif (grep_config(\"color.grep.matchcontext\", value, ctx, cb) < 0)\n \t\t\treturn -1;\n-\t\tif (grep_config(\"color.grep.matchselected\", value, cb) < 0)\n+\t\tif (grep_config(\"color.grep.matchselected\", value, ctx, cb) < 0)\n \t\t\treturn -1;\n \t} else if (skip_prefix(var, \"color.grep.\", &slot)) {\n \t\tint i = LOOKUP_CONFIG(color_grep_slots, slot);\ndiff --git a/grep.h b/grep.h\nindex c59592e3bdb..926c0875c42 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -202,7 +202,9 @@ struct grep_opt {\n \t.output = std_output, \\\n }\n \n-int grep_config(const char *var, const char *value, void *);\n+struct config_context;\n+int grep_config(const char *var, const char *value,\n+\t\tconst struct config_context *ctx, void *data);\n void grep_init(struct grep_opt *, struct repository *repo);\n \n void append_grep_pat(struct grep_opt *opt, const char *pat, size_t patlen, const char *origin, int no, enum grep_pat_token t);\ndiff --git a/help.c b/help.c\nindex 5d7637dce92..ac0ae5ac0dc 100644\n--- a/help.c\n+++ b/help.c\n@@ -309,7 +309,8 @@ void load_command_list(const char *prefix,\n \texclude_cmds(other_cmds, main_cmds);\n }\n \n-static int get_colopts(const char *var, const char *value, void *data)\n+static int get_colopts(const char *var, const char *value,\n+\t\t       const struct config_context *ctx UNUSED, void *data)\n {\n \tunsigned int *colopts = data;\n \n@@ -459,7 +460,8 @@ void list_developer_interfaces_help(void)\n \tputchar('\\n');\n }\n \n-static int get_alias(const char *var, const char *value, void *data)\n+static int get_alias(const char *var, const char *value,\n+\t\t     const struct config_context *ctx UNUSED, void *data)\n {\n \tstruct string_list *list = data;\n \n@@ -543,6 +545,7 @@ static struct cmdnames aliases;\n #define AUTOCORRECT_IMMEDIATELY (-1)\n \n static int git_unknown_cmd_config(const char *var, const char *value,\n+\t\t\t\t  const struct config_context *ctx UNUSED,\n \t\t\t\t  void *cb UNUSED)\n {\n \tconst char *p;\ndiff --git a/http.c b/http.c\nindex bb58bb3e6a3..762502828c9 100644\n--- a/http.c\n+++ b/http.c\n@@ -363,7 +363,8 @@ static void process_curl_messages(void)\n \t}\n }\n \n-static int http_options(const char *var, const char *value, void *cb)\n+static int http_options(const char *var, const char *value,\n+\t\t\tconst struct config_context *ctx, void *data)\n {\n \tif (!strcmp(\"http.version\", var)) {\n \t\treturn git_config_string(&curl_http_version, var, value);\n@@ -534,7 +535,7 @@ static int http_options(const char *var, const char *value, void *cb)\n \t}\n \n \t/* Fall back on the default ones */\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, data);\n }\n \n static int curl_empty_auth_enabled(void)\ndiff --git a/ident.c b/ident.c\nindex 8fad92d7007..08be4d0747d 100644\n--- a/ident.c\n+++ b/ident.c\n@@ -671,7 +671,9 @@ static int set_ident(const char *var, const char *value)\n \treturn 0;\n }\n \n-int git_ident_config(const char *var, const char *value, void *data UNUSED)\n+int git_ident_config(const char *var, const char *value,\n+\t\t     const struct config_context *ctx UNUSED,\n+\t\t     void *data UNUSED)\n {\n \tif (!strcmp(var, \"user.useconfigonly\")) {\n \t\tident_use_config_only = git_config_bool(var, value);\ndiff --git a/ident.h b/ident.h\nindex 96a64896a01..6a79febba15 100644\n--- a/ident.h\n+++ b/ident.h\n@@ -62,6 +62,8 @@ const char *fmt_name(enum want_ident);\n int committer_ident_sufficiently_given(void);\n int author_ident_sufficiently_given(void);\n \n-int git_ident_config(const char *, const char *, void *);\n+struct config_context;\n+int git_ident_config(const char *, const char *, const struct config_context *,\n+\t\t     void *);\n \n #endif\ndiff --git a/imap-send.c b/imap-send.c\nindex 7f5426177a1..47777e76861 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1323,7 +1323,8 @@ static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)\n \treturn 1;\n }\n \n-static int git_imap_config(const char *var, const char *val, void *cb)\n+static int git_imap_config(const char *var, const char *val,\n+\t\t\t   const struct config_context *ctx, void *cb)\n {\n \n \tif (!strcmp(\"imap.sslverify\", var))\n@@ -1357,7 +1358,7 @@ static int git_imap_config(const char *var, const char *val, void *cb)\n \t\t\tserver.host = xstrdup(val);\n \t\t}\n \t} else\n-\t\treturn git_default_config(var, val, cb);\n+\t\treturn git_default_config(var, val, ctx, cb);\n \n \treturn 0;\n }\ndiff --git a/ll-merge.c b/ll-merge.c\nindex 07ec16e8e5b..3936d112e00 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -254,6 +254,7 @@ static struct ll_merge_driver *ll_user_merge, **ll_user_merge_tail;\n static const char *default_ll_merge;\n \n static int read_merge_config(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t     void *cb UNUSED)\n {\n \tstruct ll_merge_driver *fn;\ndiff --git a/ls-refs.c b/ls-refs.c\nindex f385938b64c..a29c2364a59 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -137,6 +137,7 @@ static void send_possibly_unborn_head(struct ls_refs_data *data)\n }\n \n static int ls_refs_config(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx UNUSED,\n \t\t\t  void *cb_data)\n {\n \tstruct ls_refs_data *data = cb_data;\ndiff --git a/mailinfo.c b/mailinfo.c\nindex 2aeb20e5e62..931505363cd 100644\n--- a/mailinfo.c\n+++ b/mailinfo.c\n@@ -1241,12 +1241,13 @@ int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)\n \treturn 0;\n }\n \n-static int git_mailinfo_config(const char *var, const char *value, void *mi_)\n+static int git_mailinfo_config(const char *var, const char *value,\n+\t\t\t       const struct config_context *ctx, void *mi_)\n {\n \tstruct mailinfo *mi = mi_;\n \n \tif (!starts_with(var, \"mailinfo.\"))\n-\t\treturn git_default_config(var, value, NULL);\n+\t\treturn git_default_config(var, value, ctx, NULL);\n \tif (!strcmp(var, \"mailinfo.scissors\")) {\n \t\tmi->use_scissors = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/notes-utils.c b/notes-utils.c\nindex 4a793eb347f..97c031c26ec 100644\n--- a/notes-utils.c\n+++ b/notes-utils.c\n@@ -94,7 +94,9 @@ static combine_notes_fn parse_combine_notes_fn(const char *v)\n \t\treturn NULL;\n }\n \n-static int notes_rewrite_config(const char *k, const char *v, void *cb)\n+static int notes_rewrite_config(const char *k, const char *v,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *cb)\n {\n \tstruct notes_rewrite_cfg *c = cb;\n \tif (starts_with(k, \"notes.rewrite.\") && !strcmp(k+14, c->cmd)) {\ndiff --git a/notes.c b/notes.c\nindex f51a2d3630e..e68645a4b89 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -974,7 +974,9 @@ void string_list_add_refs_from_colon_sep(struct string_list *list,\n \tfree(globs_copy);\n }\n \n-static int notes_display_config(const char *k, const char *v, void *cb)\n+static int notes_display_config(const char *k, const char *v,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *cb)\n {\n \tint *load_refs = cb;\n \ndiff --git a/pager.c b/pager.c\nindex 63055d0873f..b8822a9381e 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -43,6 +43,7 @@ static void wait_for_pager_signal(int signo)\n }\n \n static int core_pager_config(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t     void *data UNUSED)\n {\n \tif (!strcmp(var, \"core.pager\"))\n@@ -228,7 +229,9 @@ struct pager_command_config_data {\n \tchar *value;\n };\n \n-static int pager_command_config(const char *var, const char *value, void *vdata)\n+static int pager_command_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *vdata)\n {\n \tstruct pager_command_config_data *data = vdata;\n \tconst char *cmd;\ndiff --git a/pretty.c b/pretty.c\nindex 0bb938021ba..87245353452 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -56,6 +56,7 @@ static void save_user_format(struct rev_info *rev, const char *cp, int is_tforma\n }\n \n static int git_pretty_formats_config(const char *var, const char *value,\n+\t\t\t\t     const struct config_context *ctx UNUSED,\n \t\t\t\t     void *cb UNUSED)\n {\n \tstruct cmt_fmt_map *commit_format = NULL;\ndiff --git a/promisor-remote.c b/promisor-remote.c\nindex 1adcd6fb0a5..c22abb85b15 100644\n--- a/promisor-remote.c\n+++ b/promisor-remote.c\n@@ -100,7 +100,9 @@ static void promisor_remote_move_to_tail(struct promisor_remote_config *config,\n \tconfig->promisors_tail = &r->next;\n }\n \n-static int promisor_remote_config(const char *var, const char *value, void *data)\n+static int promisor_remote_config(const char *var, const char *value,\n+\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t  void *data)\n {\n \tstruct promisor_remote_config *config = data;\n \tconst char *name;\ndiff --git a/remote.c b/remote.c\nindex 1bcd36e358a..241999c2842 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -349,7 +349,8 @@ static void read_branches_file(struct remote_state *remote_state,\n \tremote->fetch_tags = 1; /* always auto-follow */\n }\n \n-static int handle_config(const char *key, const char *value, void *cb)\n+static int handle_config(const char *key, const char *value,\n+\t\t\t const struct config_context *ctx UNUSED, void *cb)\n {\n \tconst char *name;\n \tsize_t namelen;\ndiff --git a/revision.c b/revision.c\nindex b33cc1d106a..87ed8ccd444 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -1572,7 +1572,9 @@ struct exclude_hidden_refs_cb {\n \tconst char *section;\n };\n \n-static int hide_refs_config(const char *var, const char *value, void *cb_data)\n+static int hide_refs_config(const char *var, const char *value,\n+\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    void *cb_data)\n {\n \tstruct exclude_hidden_refs_cb *cb = cb_data;\n \tcb->exclusions->hidden_refs_configured = 1;\ndiff --git a/scalar.c b/scalar.c\nindex 1326e1f6089..df7358f481c 100644\n--- a/scalar.c\n+++ b/scalar.c\n@@ -594,7 +594,9 @@ static int cmd_register(int argc, const char **argv)\n \treturn register_dir();\n }\n \n-static int get_scalar_repos(const char *key, const char *value, void *data)\n+static int get_scalar_repos(const char *key, const char *value,\n+\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    void *data)\n {\n \tstruct string_list *list = data;\n \ndiff --git a/sequencer.c b/sequencer.c\nindex bceb6abcb6c..34754d17596 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -219,7 +219,8 @@ static struct update_ref_record *init_update_ref_record(const char *ref)\n \treturn rec;\n }\n \n-static int git_sequencer_config(const char *k, const char *v, void *cb)\n+static int git_sequencer_config(const char *k, const char *v,\n+\t\t\t\tconst struct config_context *ctx, void *cb)\n {\n \tstruct replay_opts *opts = cb;\n \tint status;\n@@ -274,7 +275,7 @@ static int git_sequencer_config(const char *k, const char *v, void *cb)\n \tif (opts->action == REPLAY_REVERT && !strcmp(k, \"revert.reference\"))\n \t\topts->commit_use_reference = git_config_bool(k, v);\n \n-\treturn git_diff_basic_config(k, v, NULL);\n+\treturn git_diff_basic_config(k, v, ctx, NULL);\n }\n \n void sequencer_init_config(struct replay_opts *opts)\n@@ -2881,7 +2882,9 @@ static int git_config_string_dup(char **dest,\n \treturn 0;\n }\n \n-static int populate_opts_cb(const char *key, const char *value, void *data)\n+static int populate_opts_cb(const char *key, const char *value,\n+\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    void *data)\n {\n \tstruct replay_opts *opts = data;\n \tint error_flag = 1;\ndiff --git a/setup.c b/setup.c\nindex 6f6e92b96be..fadba5bab4b 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -517,7 +517,9 @@ no_prevention_needed:\n \tstartup_info->original_cwd = NULL;\n }\n \n-static int read_worktree_config(const char *var, const char *value, void *vdata)\n+static int read_worktree_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *vdata)\n {\n \tstruct repository_format *data = vdata;\n \n@@ -588,7 +590,8 @@ static enum extension_result handle_extension(const char *var,\n \treturn EXTENSION_UNKNOWN;\n }\n \n-static int check_repo_format(const char *var, const char *value, void *vdata)\n+static int check_repo_format(const char *var, const char *value,\n+\t\t\t     const struct config_context *ctx, void *vdata)\n {\n \tstruct repository_format *data = vdata;\n \tconst char *ext;\n@@ -617,7 +620,7 @@ static int check_repo_format(const char *var, const char *value, void *vdata)\n \t\t}\n \t}\n \n-\treturn read_worktree_config(var, value, vdata);\n+\treturn read_worktree_config(var, value, ctx, vdata);\n }\n \n static int check_repository_format_gently(const char *gitdir, struct repository_format *candidate, int *nongit_ok)\n@@ -1115,7 +1118,8 @@ struct safe_directory_data {\n \tint is_safe;\n };\n \n-static int safe_directory_cb(const char *key, const char *value, void *d)\n+static int safe_directory_cb(const char *key, const char *value,\n+\t\t\t     const struct config_context *ctx UNUSED, void *d)\n {\n \tstruct safe_directory_data *data = d;\n \n@@ -1171,7 +1175,9 @@ static int ensure_valid_ownership(const char *gitfile,\n \treturn data.is_safe;\n }\n \n-static int allowed_bare_repo_cb(const char *key, const char *value, void *d)\n+static int allowed_bare_repo_cb(const char *key, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *d)\n {\n \tenum allowed_bare_repo *allowed_bare_repo = d;\n \ndiff --git a/submodule-config.c b/submodule-config.c\nindex 7eb7a0d88d2..a38d4d49731 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -426,7 +426,8 @@ struct parse_config_parameter {\n  * config store (.git/config, etc).  Callers are responsible for\n  * checking for overrides in the main config store when appropriate.\n  */\n-static int parse_config(const char *var, const char *value, void *data)\n+static int parse_config(const char *var, const char *value,\n+\t\t\tconst struct config_context *ctx UNUSED, void *data)\n {\n \tstruct parse_config_parameter *me = data;\n \tstruct submodule *submodule;\n@@ -674,7 +675,8 @@ out:\n \t}\n }\n \n-static int gitmodules_cb(const char *var, const char *value, void *data)\n+static int gitmodules_cb(const char *var, const char *value,\n+\t\t\t const struct config_context *ctx, void *data)\n {\n \tstruct repository *repo = data;\n \tstruct parse_config_parameter parameter;\n@@ -684,7 +686,7 @@ static int gitmodules_cb(const char *var, const char *value, void *data)\n \tparameter.gitmodules_oid = null_oid();\n \tparameter.overwrite = 1;\n \n-\treturn parse_config(var, value, &parameter);\n+\treturn parse_config(var, value, ctx, &parameter);\n }\n \n void repo_read_gitmodules(struct repository *repo, int skip_if_read)\n@@ -801,7 +803,9 @@ void submodule_free(struct repository *r)\n \t\tsubmodule_cache_clear(r->submodule_cache);\n }\n \n-static int config_print_callback(const char *var, const char *value, void *cb_data)\n+static int config_print_callback(const char *var, const char *value,\n+\t\t\t\t const struct config_context *ctx UNUSED,\n+\t\t\t\t void *cb_data)\n {\n \tchar *wanted_key = cb_data;\n \n@@ -843,7 +847,9 @@ struct fetch_config {\n \tint *recurse_submodules;\n };\n \n-static int gitmodules_fetch_config(const char *var, const char *value, void *cb)\n+static int gitmodules_fetch_config(const char *var, const char *value,\n+\t\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t\t   void *cb)\n {\n \tstruct fetch_config *config = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\")) {\n@@ -871,6 +877,7 @@ void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)\n }\n \n static int gitmodules_update_clone_config(const char *var, const char *value,\n+\t\t\t\t\t  const struct config_context *ctx UNUSED,\n \t\t\t\t\t  void *cb)\n {\n \tint *max_jobs = cb;\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex ad78fc17683..85ad815358e 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -42,7 +42,9 @@\n  *\n  */\n \n-static int iterate_cb(const char *var, const char *value, void *data UNUSED)\n+static int iterate_cb(const char *var, const char *value,\n+\t\t      const struct config_context *ctx UNUSED,\n+\t\t      void *data UNUSED)\n {\n \tstatic int nr;\n \n@@ -59,7 +61,8 @@ static int iterate_cb(const char *var, const char *value, void *data UNUSED)\n \treturn 0;\n }\n \n-static int parse_int_cb(const char *var, const char *value, void *data)\n+static int parse_int_cb(const char *var, const char *value,\n+\t\t\tconst struct config_context *ctx UNUSED, void *data)\n {\n \tconst char *key_to_match = data;\n \n@@ -70,7 +73,9 @@ static int parse_int_cb(const char *var, const char *value, void *data)\n \treturn 0;\n }\n \n-static int early_config_cb(const char *var, const char *value, void *vdata)\n+static int early_config_cb(const char *var, const char *value,\n+\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t   void *vdata)\n {\n \tconst char *key = vdata;\n \ndiff --git a/t/helper/test-userdiff.c b/t/helper/test-userdiff.c\nindex 680124a6760..0ce31ce59f5 100644\n--- a/t/helper/test-userdiff.c\n+++ b/t/helper/test-userdiff.c\n@@ -12,7 +12,9 @@ static int driver_cb(struct userdiff_driver *driver,\n \treturn 0;\n }\n \n-static int cmd__userdiff_config(const char *var, const char *value, void *cb UNUSED)\n+static int cmd__userdiff_config(const char *var, const char *value,\n+\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\tvoid *cb UNUSED)\n {\n \tif (userdiff_config(var, value) < 0)\n \t\treturn -1;\ndiff --git a/trace2/tr2_cfg.c b/trace2/tr2_cfg.c\nindex 78cfc15d52d..83bc4fd109c 100644\n--- a/trace2/tr2_cfg.c\n+++ b/trace2/tr2_cfg.c\n@@ -99,7 +99,8 @@ struct tr2_cfg_data {\n /*\n  * See if the given config key matches any of our patterns of interest.\n  */\n-static int tr2_cfg_cb(const char *key, const char *value, void *d)\n+static int tr2_cfg_cb(const char *key, const char *value,\n+\t\t      const struct config_context *ctx UNUSED, void *d)\n {\n \tstruct strbuf **s;\n \tstruct tr2_cfg_data *data = (struct tr2_cfg_data *)d;\n@@ -142,8 +143,12 @@ void tr2_list_env_vars_fl(const char *file, int line)\n void tr2_cfg_set_fl(const char *file, int line, const char *key,\n \t\t    const char *value)\n {\n+\tstruct key_value_info kvi = KVI_INIT;\n+\tstruct config_context ctx = {\n+\t\t.kvi = &kvi,\n+\t};\n \tstruct tr2_cfg_data data = { file, line };\n \n \tif (tr2_cfg_load_patterns() > 0)\n-\t\ttr2_cfg_cb(key, value, &data);\n+\t\ttr2_cfg_cb(key, value, &ctx, &data);\n }\ndiff --git a/trace2/tr2_sysenv.c b/trace2/tr2_sysenv.c\nindex 069786cb927..f26ec95ab4d 100644\n--- a/trace2/tr2_sysenv.c\n+++ b/trace2/tr2_sysenv.c\n@@ -57,7 +57,8 @@ static struct tr2_sysenv_entry tr2_sysenv_settings[] = {\n };\n /* clang-format on */\n \n-static int tr2_sysenv_cb(const char *key, const char *value, void *d)\n+static int tr2_sysenv_cb(const char *key, const char *value,\n+\t\t\t const struct config_context *ctx UNUSED, void *d)\n {\n \tint k;\n \ndiff --git a/trailer.c b/trailer.c\nindex a2c3ed6f28c..06dc0b7f683 100644\n--- a/trailer.c\n+++ b/trailer.c\n@@ -482,6 +482,7 @@ static struct {\n };\n \n static int git_trailer_default_config(const char *conf_key, const char *value,\n+\t\t\t\t      const struct config_context *ctx UNUSED,\n \t\t\t\t      void *cb UNUSED)\n {\n \tconst char *trailer_item, *variable_name;\n@@ -514,6 +515,7 @@ static int git_trailer_default_config(const char *conf_key, const char *value,\n }\n \n static int git_trailer_config(const char *conf_key, const char *value,\n+\t\t\t      const struct config_context *ctx UNUSED,\n \t\t\t      void *cb UNUSED)\n {\n \tconst char *trailer_item, *variable_name;\ndiff --git a/upload-pack.c b/upload-pack.c\nindex d3312006a32..951fd1f9c25 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1309,7 +1309,9 @@ static int parse_object_filter_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-static int upload_pack_config(const char *var, const char *value, void *cb_data)\n+static int upload_pack_config(const char *var, const char *value,\n+\t\t\t      const struct config_context *ctx UNUSED,\n+\t\t\t      void *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n \n@@ -1350,7 +1352,9 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data)\n \treturn parse_hide_refs_config(var, value, \"uploadpack\", &data->hidden_refs);\n }\n \n-static int upload_pack_protected_config(const char *var, const char *value, void *cb_data)\n+static int upload_pack_protected_config(const char *var, const char *value,\n+\t\t\t\t\tconst struct config_context *ctx UNUSED,\n+\t\t\t\t\tvoid *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n \ndiff --git a/urlmatch.c b/urlmatch.c\nindex eba0bdd77fe..1c45f23adf2 100644\n--- a/urlmatch.c\n+++ b/urlmatch.c\n@@ -551,7 +551,8 @@ static int cmp_matches(const struct urlmatch_item *a,\n \treturn 0;\n }\n \n-int urlmatch_config_entry(const char *var, const char *value, void *cb)\n+int urlmatch_config_entry(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tstruct string_list_item *item;\n \tstruct urlmatch_config *collect = cb;\n@@ -565,7 +566,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)\n \n \tif (!skip_prefix(var, collect->section, &key) || *(key++) != '.') {\n \t\tif (collect->cascade_fn)\n-\t\t\treturn collect->cascade_fn(var, value, cb);\n+\t\t\treturn collect->cascade_fn(var, value, ctx, cb);\n \t\treturn 0; /* not interested */\n \t}\n \tdot = strrchr(key, '.');\n@@ -609,7 +610,7 @@ int urlmatch_config_entry(const char *var, const char *value, void *cb)\n \tstrbuf_addstr(&synthkey, collect->section);\n \tstrbuf_addch(&synthkey, '.');\n \tstrbuf_addstr(&synthkey, key);\n-\tretval = collect->collect_fn(synthkey.buf, value, collect->cb);\n+\tretval = collect->collect_fn(synthkey.buf, value, ctx, collect->cb);\n \n \tstrbuf_release(&synthkey);\n \treturn retval;\ndiff --git a/urlmatch.h b/urlmatch.h\nindex bee374a642c..5ba85cea139 100644\n--- a/urlmatch.h\n+++ b/urlmatch.h\n@@ -71,7 +71,8 @@ struct urlmatch_config {\n \t.vars = STRING_LIST_INIT_DUP, \\\n }\n \n-int urlmatch_config_entry(const char *var, const char *value, void *cb);\n+int urlmatch_config_entry(const char *var, const char *value,\n+\t\t\t  const struct config_context *ctx, void *cb);\n void urlmatch_config_release(struct urlmatch_config *config);\n \n #endif /* URL_MATCH_H */\ndiff --git a/xdiff-interface.c b/xdiff-interface.c\nindex 0460e03f5ed..dcbb5e09857 100644\n--- a/xdiff-interface.c\n+++ b/xdiff-interface.c\n@@ -307,7 +307,8 @@ int xdiff_compare_lines(const char *l1, long s1,\n \n int git_xmerge_style = -1;\n \n-int git_xmerge_config(const char *var, const char *value, void *cb)\n+int git_xmerge_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"merge.conflictstyle\")) {\n \t\tif (!value)\n@@ -327,5 +328,5 @@ int git_xmerge_config(const char *var, const char *value, void *cb)\n \t\t\t    value, var);\n \t\treturn 0;\n \t}\n-\treturn git_default_config(var, value, cb);\n+\treturn git_default_config(var, value, ctx, cb);\n }\ndiff --git a/xdiff-interface.h b/xdiff-interface.h\nindex 733c364d26c..e6f80df0462 100644\n--- a/xdiff-interface.h\n+++ b/xdiff-interface.h\n@@ -50,7 +50,9 @@ int buffer_is_binary(const char *ptr, unsigned long size);\n \n void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line, int cflags);\n void xdiff_clear_find_func(xdemitconf_t *xecfg);\n-int git_xmerge_config(const char *var, const char *value, void *cb);\n+struct config_context;\n+int git_xmerge_config(const char *var, const char *value,\n+\t\t      const struct config_context *ctx, void *cb);\n extern int git_xmerge_style;\n \n /*\n-- \ngitgitgadget\n\n"},{"id":"478980","messageId":"4776600e790d6d4ffcba8ff4cb648b7100e08c31.1687980393.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","subject":"[PATCH v5 09/11] config.c: remove config_reader from configsets","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-28T19:26:28Z","receivedAt":"2023-06-28T19:27:07Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nRemove the last usage of \"struct config_reader\" from configsets by\ncopying the \"kvi\" arg instead of recomputing \"kvi\" from\nconfig_reader.source. Since we no longer need to pass both \"struct\nconfig_reader\" and \"struct config_set\" in a single \"void *cb\", remove\n\"struct configset_add_data\" too.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 45 +++++++++++----------------------------------\n 1 file changed, 11 insertions(+), 34 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 4e3c4dcf415..50dbd89a6d6 100644\n--- a/config.c\n+++ b/config.c\n@@ -2311,8 +2311,7 @@ int config_with_options(config_fn_t fn, void *data,\n \treturn ret;\n }\n \n-static void configset_iter(struct config_reader *reader, struct config_set *set,\n-\t\t\t   config_fn_t fn, void *data)\n+static void configset_iter(struct config_set *set, config_fn_t fn, void *data)\n {\n \tint i, value_index;\n \tstruct string_list *values;\n@@ -2406,7 +2405,6 @@ static int configset_find_element(struct config_set *set, const char *key,\n }\n \n static int configset_add_value(const struct key_value_info *kvi_p,\n-\t\t\t       struct config_reader *reader,\n \t\t\t       struct config_set *set, const char *key,\n \t\t\t       const char *value)\n {\n@@ -2437,13 +2435,7 @@ static int configset_add_value(const struct key_value_info *kvi_p,\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (!reader->source)\n-\t\tBUG(\"configset_add_value has no source\");\n-\tif (reader->source->name) {\n-\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n-\t} else {\n-\t\tkvi_from_param(kv_info);\n-\t}\n+\t*kv_info = *kvi_p;\n \tsi->util = kv_info;\n \n \treturn 0;\n@@ -2491,28 +2483,18 @@ void git_configset_clear(struct config_set *set)\n \tset->list.items = NULL;\n }\n \n-struct configset_add_data {\n-\tstruct config_set *config_set;\n-\tstruct config_reader *config_reader;\n-};\n-#define CONFIGSET_ADD_INIT { 0 }\n-\n static int config_set_callback(const char *key, const char *value,\n \t\t\t       const struct config_context *ctx,\n \t\t\t       void *cb)\n {\n-\tstruct configset_add_data *data = cb;\n-\tconfigset_add_value(ctx->kvi, data->config_reader, data->config_set,\n-\t\t\t    key, value);\n+\tstruct config_set *set = cb;\n+\tconfigset_add_value(ctx->kvi, set, key, value);\n \treturn 0;\n }\n \n int git_configset_add_file(struct config_set *set, const char *filename)\n {\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n-\tdata.config_reader = &the_reader;\n-\tdata.config_set = set;\n-\treturn git_config_from_file(config_set_callback, filename, &data);\n+\treturn git_config_from_file(config_set_callback, filename, set);\n }\n \n int git_configset_get_value(struct config_set *set, const char *key,\n@@ -2678,7 +2660,6 @@ int git_configset_get_pathname(struct config_set *set, const char *key, const ch\n static void repo_read_config(struct repository *repo)\n {\n \tstruct config_options opts = { 0 };\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n \n \topts.respect_includes = 1;\n \topts.commondir = repo->commondir;\n@@ -2690,10 +2671,8 @@ static void repo_read_config(struct repository *repo)\n \t\tgit_configset_clear(repo->config);\n \n \tgit_configset_init(repo->config);\n-\tdata.config_set = repo->config;\n-\tdata.config_reader = &the_reader;\n-\n-\tif (config_with_options(config_set_callback, &data, NULL, repo, &opts) < 0)\n+\tif (config_with_options(config_set_callback, repo->config, NULL,\n+\t\t\t\trepo, &opts) < 0)\n \t\t/*\n \t\t * config_with_options() normally returns only\n \t\t * zero, as most errors are fatal, and\n@@ -2725,7 +2704,7 @@ static void repo_config_clear(struct repository *repo)\n void repo_config(struct repository *repo, config_fn_t fn, void *data)\n {\n \tgit_config_check_init(repo);\n-\tconfigset_iter(&the_reader, repo->config, fn, data);\n+\tconfigset_iter(repo->config, fn, data);\n }\n \n int repo_config_get(struct repository *repo, const char *key)\n@@ -2832,19 +2811,17 @@ static void read_protected_config(void)\n \t\t.ignore_worktree = 1,\n \t\t.system_gently = 1,\n \t};\n-\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n \n \tgit_configset_init(&protected_config);\n-\tdata.config_set = &protected_config;\n-\tdata.config_reader = &the_reader;\n-\tconfig_with_options(config_set_callback, &data, NULL, NULL, &opts);\n+\tconfig_with_options(config_set_callback, &protected_config, NULL,\n+\t\t\t    NULL, &opts);\n }\n \n void git_protected_config(config_fn_t fn, void *data)\n {\n \tif (!protected_config.hash_initialized)\n \t\tread_protected_config();\n-\tconfigset_iter(&the_reader, &protected_config, fn, data);\n+\tconfigset_iter(&protected_config, fn, data);\n }\n \n /* Functions used historically to read configuration from 'the_repository' */\n-- \ngitgitgadget\n\n"},{"id":"478982","messageId":"2b33977aba63ed21ecf74664da503750911c8dc8.1687980393.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","subject":"[PATCH v5 10/11] config: add kvi.path, use it to evaluate includes","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-28T19:26:29Z","receivedAt":"2023-06-28T19:27:09Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nInclude directives are evaluated using the path of the config file. To\nreduce the dependence on \"config_reader.source\", add a new\n\"key_value_info.path\" member and use that instead of\n\"config_source.path\". This allows us to remove a \"struct config_reader\n*\" field from \"struct config_include_data\", which will subsequently\nallow us to remove \"struct config_reader\" entirely.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 40 +++++++++++++++++++---------------------\n config.h |  2 ++\n 2 files changed, 21 insertions(+), 21 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 50dbd89a6d6..8d342d84255 100644\n--- a/config.c\n+++ b/config.c\n@@ -162,7 +162,6 @@ struct config_include_data {\n \tconst struct config_options *opts;\n \tstruct git_config_source *config_source;\n \tstruct repository *repo;\n-\tstruct config_reader *config_reader;\n \n \t/*\n \t * All remote URLs discovered when reading all config files.\n@@ -181,8 +180,7 @@ static const char include_depth_advice[] = N_(\n \"from\\n\"\n \"\t%s\\n\"\n \"This might be due to circular includes.\");\n-static int handle_path_include(struct config_source *cs,\n-\t\t\t       const struct key_value_info *kvi,\n+static int handle_path_include(const struct key_value_info *kvi,\n \t\t\t       const char *path,\n \t\t\t       struct config_include_data *inc)\n {\n@@ -205,14 +203,14 @@ static int handle_path_include(struct config_source *cs,\n \tif (!is_absolute_path(path)) {\n \t\tchar *slash;\n \n-\t\tif (!cs || !cs->path) {\n+\t\tif (!kvi || !kvi->path) {\n \t\t\tret = error(_(\"relative config includes must come from files\"));\n \t\t\tgoto cleanup;\n \t\t}\n \n-\t\tslash = find_last_dir_sep(cs->path);\n+\t\tslash = find_last_dir_sep(kvi->path);\n \t\tif (slash)\n-\t\t\tstrbuf_add(&buf, cs->path, slash - cs->path + 1);\n+\t\t\tstrbuf_add(&buf, kvi->path, slash - kvi->path + 1);\n \t\tstrbuf_addstr(&buf, path);\n \t\tpath = buf.buf;\n \t}\n@@ -220,8 +218,8 @@ static int handle_path_include(struct config_source *cs,\n \tif (!access_or_die(path, R_OK, 0)) {\n \t\tif (++inc->depth > MAX_INCLUDE_DEPTH)\n \t\t\tdie(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,\n-\t\t\t    !cs ? \"<unknown>\" :\n-\t\t\t    cs->name ? cs->name :\n+\t\t\t    !kvi ? \"<unknown>\" :\n+\t\t\t    kvi->filename ? kvi->filename :\n \t\t\t    \"the command line\");\n \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n \t\t\t\t\t\t\tkvi->scope, NULL);\n@@ -239,7 +237,7 @@ static void add_trailing_starstar_for_dir(struct strbuf *pat)\n \t\tstrbuf_addstr(pat, \"**\");\n }\n \n-static int prepare_include_condition_pattern(struct config_source *cs,\n+static int prepare_include_condition_pattern(const struct key_value_info *kvi,\n \t\t\t\t\t     struct strbuf *pat)\n {\n \tstruct strbuf path = STRBUF_INIT;\n@@ -256,11 +254,11 @@ static int prepare_include_condition_pattern(struct config_source *cs,\n \tif (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {\n \t\tconst char *slash;\n \n-\t\tif (!cs || !cs->path)\n+\t\tif (!kvi || !kvi->path)\n \t\t\treturn error(_(\"relative config include \"\n \t\t\t\t       \"conditionals must come from files\"));\n \n-\t\tstrbuf_realpath(&path, cs->path, 1);\n+\t\tstrbuf_realpath(&path, kvi->path, 1);\n \t\tslash = find_last_dir_sep(path.buf);\n \t\tif (!slash)\n \t\t\tBUG(\"how is this possible?\");\n@@ -275,7 +273,7 @@ static int prepare_include_condition_pattern(struct config_source *cs,\n \treturn prefix;\n }\n \n-static int include_by_gitdir(struct config_source *cs,\n+static int include_by_gitdir(const struct key_value_info *kvi,\n \t\t\t     const struct config_options *opts,\n \t\t\t     const char *cond, size_t cond_len, int icase)\n {\n@@ -292,7 +290,7 @@ static int include_by_gitdir(struct config_source *cs,\n \n \tstrbuf_realpath(&text, git_dir, 1);\n \tstrbuf_add(&pattern, cond, cond_len);\n-\tprefix = prepare_include_condition_pattern(cs, &pattern);\n+\tprefix = prepare_include_condition_pattern(kvi, &pattern);\n \n again:\n \tif (prefix < 0)\n@@ -428,16 +426,16 @@ static int include_by_remote_url(struct config_include_data *inc,\n \t\t\t\t\t     inc->remote_urls);\n }\n \n-static int include_condition_is_true(struct config_source *cs,\n+static int include_condition_is_true(const struct key_value_info *kvi,\n \t\t\t\t     struct config_include_data *inc,\n \t\t\t\t     const char *cond, size_t cond_len)\n {\n \tconst struct config_options *opts = inc->opts;\n \n \tif (skip_prefix_mem(cond, cond_len, \"gitdir:\", &cond, &cond_len))\n-\t\treturn include_by_gitdir(cs, opts, cond, cond_len, 0);\n+\t\treturn include_by_gitdir(kvi, opts, cond, cond_len, 0);\n \telse if (skip_prefix_mem(cond, cond_len, \"gitdir/i:\", &cond, &cond_len))\n-\t\treturn include_by_gitdir(cs, opts, cond, cond_len, 1);\n+\t\treturn include_by_gitdir(kvi, opts, cond, cond_len, 1);\n \telse if (skip_prefix_mem(cond, cond_len, \"onbranch:\", &cond, &cond_len))\n \t\treturn include_by_branch(cond, cond_len);\n \telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.*.url:\", &cond,\n@@ -453,7 +451,6 @@ static int git_config_include(const char *var, const char *value,\n \t\t\t      void *data)\n {\n \tstruct config_include_data *inc = data;\n-\tstruct config_source *cs = inc->config_reader->source;\n \tconst char *cond, *key;\n \tsize_t cond_len;\n \tint ret;\n@@ -467,16 +464,16 @@ static int git_config_include(const char *var, const char *value,\n \t\treturn ret;\n \n \tif (!strcmp(var, \"include.path\"))\n-\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n+\t\tret = handle_path_include(ctx->kvi, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n-\t    cond && include_condition_is_true(cs, inc, cond, cond_len) &&\n+\t    cond && include_condition_is_true(ctx->kvi, inc, cond, cond_len) &&\n \t    !strcmp(key, \"path\")) {\n \t\tconfig_fn_t old_fn = inc->fn;\n \n \t\tif (inc->opts->unconditional_remote_url)\n \t\t\tinc->fn = forbid_remote_url;\n-\t\tret = handle_path_include(cs, ctx->kvi, value, inc);\n+\t\tret = handle_path_include(ctx->kvi, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -661,6 +658,7 @@ void kvi_from_param(struct key_value_info *out)\n \tout->linenr = -1;\n \tout->origin_type = CONFIG_ORIGIN_CMDLINE;\n \tout->scope = CONFIG_SCOPE_COMMAND;\n+\tout->path = NULL;\n }\n \n int git_config_parse_parameter(const char *text,\n@@ -1064,6 +1062,7 @@ static void kvi_from_source(struct config_source *cs,\n \tout->origin_type = cs->origin_type;\n \tout->linenr = cs->linenr;\n \tout->scope = scope;\n+\tout->path = cs->path;\n }\n \n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n@@ -2282,7 +2281,6 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tinc.opts = opts;\n \t\tinc.repo = repo;\n \t\tinc.config_source = config_source;\n-\t\tinc.config_reader = &the_reader;\n \t\tfn = git_config_include;\n \t\tdata = &inc;\n \t}\ndiff --git a/config.h b/config.h\nindex bd366ddb5ef..b5573e67a00 100644\n--- a/config.h\n+++ b/config.h\n@@ -116,12 +116,14 @@ struct key_value_info {\n \tint linenr;\n \tenum config_origin_type origin_type;\n \tenum config_scope scope;\n+\tconst char *path;\n };\n #define KVI_INIT { \\\n \t.filename = NULL, \\\n \t.linenr = -1, \\\n \t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n \t.scope = CONFIG_SCOPE_UNKNOWN, \\\n+\t.path = NULL, \\\n }\n \n /* Captures additional information that a config callback can use. */\n-- \ngitgitgadget\n\n"},{"id":"478983","messageId":"8347d3c9b80542b5d7a5b3342dcf884448968398.1687980393.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","subject":"[PATCH v5 11/11] config: pass source to config_parser_event_fn_t","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-28T19:26:30Z","receivedAt":"2023-06-28T19:27:12Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\n..so that the callback can use a \"struct config_source\" parameter\ninstead of \"config_reader.source\". \"struct config_source\" is internal to\nconfig.c, so we are adding a pointer to a struct defined in config.c\ninto a public function signature defined in config.h, but this is okay\nbecause this function has only ever been (and probably ever will be)\nused internally by config.c.\n\nAs a result, the_reader isn't used anywhere, so \"struct config_reader\"\nis obsolete (it was only intended to be used with the_reader). Remove\nthem.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 77 ++++++++++----------------------------------------------\n config.h |  6 +++++\n 2 files changed, 19 insertions(+), 64 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 8d342d84255..b3de01239d6 100644\n--- a/config.c\n+++ b/config.c\n@@ -66,40 +66,6 @@ struct config_source {\n };\n #define CONFIG_SOURCE_INIT { 0 }\n \n-struct config_reader {\n-\t/*\n-\t * These members record the \"current\" config source, which can be\n-\t * accessed by parsing callbacks.\n-\t *\n-\t * The \"source\" variable will be non-NULL only when we are actually\n-\t * parsing a real config source (file, blob, cmdline, etc).\n-\t */\n-\tstruct config_source *source;\n-};\n-/*\n- * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n- * \"the_reader\". \"the_reader\" should only be used if that is infeasible, e.g. in\n- * a public function.\n- */\n-static struct config_reader the_reader;\n-\n-static inline void config_reader_push_source(struct config_reader *reader,\n-\t\t\t\t\t     struct config_source *top)\n-{\n-\ttop->prev = reader->source;\n-\treader->source = top;\n-}\n-\n-static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n-{\n-\tstruct config_source *ret;\n-\tif (!reader->source)\n-\t\tBUG(\"tried to pop config source, but we weren't reading config\");\n-\tret = reader->source;\n-\treader->source = reader->source->prev;\n-\treturn ret;\n-}\n-\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -752,14 +718,9 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tstruct strvec to_free = STRVEC_INIT;\n \tint ret = 0;\n \tchar *envw = NULL;\n-\tstruct config_source source = CONFIG_SOURCE_INIT;\n \tstruct key_value_info kvi = KVI_INIT;\n \n-\tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n-\tconfig_reader_push_source(&the_reader, &source);\n-\n \tkvi_from_param(&kvi);\n-\n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n \t\tunsigned long count;\n@@ -816,7 +777,6 @@ out:\n \tstrbuf_release(&envvar);\n \tstrvec_clear(&to_free);\n \tfree(envw);\n-\tconfig_reader_pop_source(&the_reader);\n \treturn ret;\n }\n \n@@ -1045,7 +1005,7 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \n \tif (data->previous_type != CONFIG_EVENT_EOF &&\n \t    data->opts->event_fn(data->previous_type, data->previous_offset,\n-\t\t\t\t offset, data->opts->event_fn_data) < 0)\n+\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n \t\treturn -1;\n \n \tdata->previous_type = type;\n@@ -2002,8 +1962,7 @@ int git_default_config(const char *var, const char *value,\n  * fgetc, ungetc, ftell of top need to be initialized before calling\n  * this function.\n  */\n-static int do_config_from(struct config_reader *reader,\n-\t\t\t  struct config_source *top, config_fn_t fn,\n+static int do_config_from(struct config_source *top, config_fn_t fn,\n \t\t\t  void *data, enum config_scope scope,\n \t\t\t  const struct config_options *opts)\n {\n@@ -2016,21 +1975,17 @@ static int do_config_from(struct config_reader *reader,\n \ttop->total_len = 0;\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n-\tconfig_reader_push_source(reader, top);\n \tkvi_from_source(top, scope, &kvi);\n \n \tret = git_parse_source(top, fn, &kvi, data, opts);\n \n-\t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n \tstrbuf_release(&top->var);\n-\tconfig_reader_pop_source(reader);\n \n \treturn ret;\n }\n \n-static int do_config_from_file(struct config_reader *reader,\n-\t\t\t       config_fn_t fn,\n+static int do_config_from_file(config_fn_t fn,\n \t\t\t       const enum config_origin_type origin_type,\n \t\t\t       const char *name, const char *path, FILE *f,\n \t\t\t       void *data, enum config_scope scope,\n@@ -2049,7 +2004,7 @@ static int do_config_from_file(struct config_reader *reader,\n \ttop.do_ftell = config_file_ftell;\n \n \tflockfile(f);\n-\tret = do_config_from(reader, &top, fn, data, scope, opts);\n+\tret = do_config_from(&top, fn, data, scope, opts);\n \tfunlockfile(f);\n \treturn ret;\n }\n@@ -2057,8 +2012,8 @@ static int do_config_from_file(struct config_reader *reader,\n static int git_config_from_stdin(config_fn_t fn, void *data,\n \t\t\t\t enum config_scope scope)\n {\n-\treturn do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, \"\",\n-\t\t\t\t   NULL, stdin, data, scope, NULL);\n+\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n+\t\t\t\t   data, scope, NULL);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2072,9 +2027,8 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \t\tBUG(\"filename cannot be NULL\");\n \tf = fopen_or_warn(filename, \"r\");\n \tif (f) {\n-\t\tret = do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_FILE,\n-\t\t\t\t\t  filename, filename, f, data, scope,\n-\t\t\t\t\t  opts);\n+\t\tret = do_config_from_file(fn, CONFIG_ORIGIN_FILE, filename,\n+\t\t\t\t\t  filename, f, data, scope, opts);\n \t\tfclose(f);\n \t}\n \treturn ret;\n@@ -2105,7 +2059,7 @@ int git_config_from_mem(config_fn_t fn,\n \ttop.do_ungetc = config_buf_ungetc;\n \ttop.do_ftell = config_buf_ftell;\n \n-\treturn do_config_from(&the_reader, &top, fn, data, scope, opts);\n+\treturn do_config_from(&top, fn, data, scope, opts);\n }\n \n int git_config_from_blob_oid(config_fn_t fn,\n@@ -2198,8 +2152,7 @@ int git_config_system(void)\n \treturn !git_env_bool(\"GIT_CONFIG_NOSYSTEM\", 0);\n }\n \n-static int do_git_config_sequence(struct config_reader *reader,\n-\t\t\t\t  const struct config_options *opts,\n+static int do_git_config_sequence(const struct config_options *opts,\n \t\t\t\t  const struct repository *repo,\n \t\t\t\t  config_fn_t fn, void *data)\n {\n@@ -2299,7 +2252,7 @@ int config_with_options(config_fn_t fn, void *data,\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n \t\t\t\t\t       data, config_source->scope);\n \t} else {\n-\t\tret = do_git_config_sequence(&the_reader, opts, repo, fn, data);\n+\t\tret = do_git_config_sequence(opts, repo, fn, data);\n \t}\n \n \tif (inc.remote_urls) {\n@@ -3009,7 +2962,6 @@ void git_die_config(const char *key, const char *err, ...)\n  */\n \n struct config_store_data {\n-\tstruct config_reader *config_reader;\n \tsize_t baselen;\n \tchar *key;\n \tint do_not_match;\n@@ -3055,11 +3007,10 @@ static int matches(const char *key, const char *value,\n \t\t(value && !regexec(store->value_pattern, value, 0, NULL, 0));\n }\n \n-static int store_aux_event(enum config_event_t type,\n-\t\t\t   size_t begin, size_t end, void *data)\n+static int store_aux_event(enum config_event_t type, size_t begin, size_t end,\n+\t\t\t   struct config_source *cs, void *data)\n {\n \tstruct config_store_data *store = data;\n-\tstruct config_source *cs = store->config_reader->source;\n \n \tALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);\n \tstore->parsed[store->parsed_nr].begin = begin;\n@@ -3380,8 +3331,6 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \tsize_t contents_sz;\n \tstruct config_store_data store = CONFIG_STORE_INIT;\n \n-\tstore.config_reader = &the_reader;\n-\n \t/* parse-key returns negative; flip the sign to feed exit(3) */\n \tret = 0 - git_config_parse_key(key, &store.key, &store.baselen);\n \tif (ret)\ndiff --git a/config.h b/config.h\nindex b5573e67a00..6332d749047 100644\n--- a/config.h\n+++ b/config.h\n@@ -72,6 +72,7 @@ enum config_event_t {\n \tCONFIG_EVENT_ERROR\n };\n \n+struct config_source;\n /*\n  * The parser event function (if not NULL) is called with the event type and\n  * the begin/end offsets of the parsed elements.\n@@ -81,6 +82,7 @@ enum config_event_t {\n  */\n typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n \t\t\t\t\tsize_t begin_offset, size_t end_offset,\n+\t\t\t\t\tstruct config_source *cs,\n \t\t\t\t\tvoid *event_fn_data);\n \n struct config_options {\n@@ -100,6 +102,10 @@ struct config_options {\n \n \tconst char *commondir;\n \tconst char *git_dir;\n+\t/*\n+\t * event_fn and event_fn_data are for internal use only. Handles events\n+\t * emitted by the config parser.\n+\t */\n \tconfig_parser_event_fn_t event_fn;\n \tvoid *event_fn_data;\n \tenum config_error_action {\n-- \ngitgitgadget\n"},{"id":"478984","messageId":"9e71c10ca0a4685535fdf05628e161d18d6989ba.1687980393.git.gitgitgadget@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","subject":"[PATCH v5 08/11] config: pass kvi to die_bad_number()","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-06-28T19:26:27Z","receivedAt":"2023-06-28T19:27:22Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nPlumb \"struct key_value_info\" through all code paths that end in\ndie_bad_number(), which lets us remove the helper functions that read\nanalogous values from \"struct config_reader\". As a result, nothing reads\nconfig_reader.config_kvi any more, so remove that too.\n\nIn config.c, this requires changing the signature of\ngit_configset_get_value() to 'return' \"kvi\" in an out parameter so that\ngit_configset_get_<type>() can pass it to git_config_<type>(). Only\nnumeric types will use \"kvi\", so for non-numeric types (e.g.\ngit_configset_get_string()), pass NULL to indicate that the out\nparameter isn't needed.\n\nOutside of config.c, config callbacks now need to pass \"ctx->kvi\" to any\nof the git_config_<type>() functions that parse a config string into a\nnumber type. Included is a .cocci patch to make that refactor.\n\nThe only exceptional case is builtin/config.c, where git_config_<type>()\nis called outside of a config callback (namely, on user-provided input),\nso config source information has never been available. In this case,\ndie_bad_number() defaults to a generic, but perfectly descriptive\nmessage. Let's provide a safe, non-NULL for \"kvi\" anyway, but make sure\nnot to change the message.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n archive-tar.c                              |   4 +-\n builtin/commit-graph.c                     |   4 +-\n builtin/commit.c                           |  10 +-\n builtin/config.c                           |  21 +--\n builtin/fetch.c                            |   4 +-\n builtin/fsmonitor--daemon.c                |   6 +-\n builtin/grep.c                             |   2 +-\n builtin/index-pack.c                       |   4 +-\n builtin/log.c                              |   2 +-\n builtin/pack-objects.c                     |  14 +-\n builtin/receive-pack.c                     |  10 +-\n builtin/submodule--helper.c                |   4 +-\n config.c                                   | 156 ++++++++-------------\n config.h                                   |  17 ++-\n contrib/coccinelle/git_config_number.cocci |  27 ++++\n diff.c                                     |   9 +-\n fmt-merge-msg.c                            |   2 +-\n help.c                                     |   4 +-\n http.c                                     |  10 +-\n imap-send.c                                |   2 +-\n sequencer.c                                |  22 +--\n setup.c                                    |   2 +-\n submodule-config.c                         |  13 +-\n submodule-config.h                         |   3 +-\n t/helper/test-config.c                     |   6 +-\n upload-pack.c                              |  12 +-\n worktree.c                                 |   2 +-\n 27 files changed, 190 insertions(+), 182 deletions(-)\n create mode 100644 contrib/coccinelle/git_config_number.cocci\n\ndiff --git a/archive-tar.c b/archive-tar.c\nindex ef06e516b1f..3df8af6d1b1 100644\n--- a/archive-tar.c\n+++ b/archive-tar.c\n@@ -412,14 +412,14 @@ static int tar_filter_config(const char *var, const char *value,\n }\n \n static int git_tar_config(const char *var, const char *value,\n-\t\t\t  const struct config_context *ctx UNUSED, void *cb)\n+\t\t\t  const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, \"tar.umask\")) {\n \t\tif (value && !strcmp(value, \"user\")) {\n \t\t\ttar_umask = umask(0);\n \t\t\tumask(tar_umask);\n \t\t} else {\n-\t\t\ttar_umask = git_config_int(var, value);\n+\t\t\ttar_umask = git_config_int(var, value, ctx->kvi);\n \t\t}\n \t\treturn 0;\n \t}\ndiff --git a/builtin/commit-graph.c b/builtin/commit-graph.c\nindex 1185c49239a..b071c5ab646 100644\n--- a/builtin/commit-graph.c\n+++ b/builtin/commit-graph.c\n@@ -186,11 +186,11 @@ static int write_option_max_new_filters(const struct option *opt,\n }\n \n static int git_commit_graph_write_config(const char *var, const char *value,\n-\t\t\t\t\t const struct config_context *ctx UNUSED,\n+\t\t\t\t\t const struct config_context *ctx,\n \t\t\t\t\t void *cb UNUSED)\n {\n \tif (!strcmp(var, \"commitgraph.maxnewfilters\"))\n-\t\twrite_opts.max_new_filters = git_config_int(var, value);\n+\t\twrite_opts.max_new_filters = git_config_int(var, value, ctx->kvi);\n \t/*\n \t * No need to fall-back to 'git_default_config', since this was already\n \t * called in 'cmd_commit_graph()'.\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 6a2b2503328..9fe691470a3 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1415,7 +1415,8 @@ static int git_status_config(const char *k, const char *v,\n \t\treturn git_column_config(k, v, \"status\", &s->colopts);\n \tif (!strcmp(k, \"status.submodulesummary\")) {\n \t\tint is_bool;\n-\t\ts->submodule_summary = git_config_bool_or_int(k, v, &is_bool);\n+\t\ts->submodule_summary = git_config_bool_or_int(k, v, ctx->kvi,\n+\t\t\t\t\t\t\t      &is_bool);\n \t\tif (is_bool && s->submodule_summary)\n \t\t\ts->submodule_summary = -1;\n \t\treturn 0;\n@@ -1475,11 +1476,11 @@ static int git_status_config(const char *k, const char *v,\n \t}\n \tif (!strcmp(k, \"diff.renamelimit\")) {\n \t\tif (s->rename_limit == -1)\n-\t\t\ts->rename_limit = git_config_int(k, v);\n+\t\t\ts->rename_limit = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"status.renamelimit\")) {\n-\t\ts->rename_limit = git_config_int(k, v);\n+\t\ts->rename_limit = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"diff.renames\")) {\n@@ -1625,7 +1626,8 @@ static int git_commit_config(const char *k, const char *v,\n \t}\n \tif (!strcmp(k, \"commit.verbose\")) {\n \t\tint is_bool;\n-\t\tconfig_commit_verbose = git_config_bool_or_int(k, v, &is_bool);\n+\t\tconfig_commit_verbose = git_config_bool_or_int(k, v, ctx->kvi,\n+\t\t\t\t\t\t\t       &is_bool);\n \t\treturn 0;\n \t}\n \ndiff --git a/builtin/config.c b/builtin/config.c\nindex 9b9f5527311..680269d263c 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -262,13 +262,14 @@ static int format_config(struct strbuf *buf, const char *key_,\n \n \t\tif (type == TYPE_INT)\n \t\t\tstrbuf_addf(buf, \"%\"PRId64,\n-\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\"));\n+\t\t\t\t    git_config_int64(key_, value_ ? value_ : \"\", kvi));\n \t\telse if (type == TYPE_BOOL)\n \t\t\tstrbuf_addstr(buf, git_config_bool(key_, value_) ?\n \t\t\t\t      \"true\" : \"false\");\n \t\telse if (type == TYPE_BOOL_OR_INT) {\n \t\t\tint is_bool, v;\n-\t\t\tv = git_config_bool_or_int(key_, value_, &is_bool);\n+\t\t\tv = git_config_bool_or_int(key_, value_, kvi,\n+\t\t\t\t\t\t   &is_bool);\n \t\t\tif (is_bool)\n \t\t\t\tstrbuf_addstr(buf, v ? \"true\" : \"false\");\n \t\t\telse\n@@ -424,7 +425,8 @@ free_strings:\n \treturn ret;\n }\n \n-static char *normalize_value(const char *key, const char *value)\n+static char *normalize_value(const char *key, const char *value,\n+\t\t\t     struct key_value_info *kvi)\n {\n \tif (!value)\n \t\treturn NULL;\n@@ -439,12 +441,12 @@ static char *normalize_value(const char *key, const char *value)\n \t\t */\n \t\treturn xstrdup(value);\n \tif (type == TYPE_INT)\n-\t\treturn xstrfmt(\"%\"PRId64, git_config_int64(key, value));\n+\t\treturn xstrfmt(\"%\"PRId64, git_config_int64(key, value, kvi));\n \tif (type == TYPE_BOOL)\n \t\treturn xstrdup(git_config_bool(key, value) ?  \"true\" : \"false\");\n \tif (type == TYPE_BOOL_OR_INT) {\n \t\tint is_bool, v;\n-\t\tv = git_config_bool_or_int(key, value, &is_bool);\n+\t\tv = git_config_bool_or_int(key, value, kvi, &is_bool);\n \t\tif (!is_bool)\n \t\t\treturn xstrfmt(\"%d\", v);\n \t\telse\n@@ -674,6 +676,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \tchar *value = NULL;\n \tint flags = 0;\n \tint ret = 0;\n+\tstruct key_value_info default_kvi = KVI_INIT;\n \n \tgiven_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT));\n \n@@ -891,7 +894,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_SET) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 2);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n \t\tret = git_config_set_in_file_gently(given_config_source.file, argv[0], value);\n \t\tif (ret == CONFIG_NOTHING_SET)\n \t\t\terror(_(\"cannot overwrite multiple values with a single value\\n\"\n@@ -900,7 +903,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_SET_ALL) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 3);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value, argv[2],\n \t\t\t\t\t\t\t     flags);\n@@ -908,7 +911,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_ADD) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 2);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value,\n \t\t\t\t\t\t\t     CONFIG_REGEX_NONE,\n@@ -917,7 +920,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)\n \telse if (actions == ACTION_REPLACE_ALL) {\n \t\tcheck_write();\n \t\tcheck_argc(argc, 2, 3);\n-\t\tvalue = normalize_value(argv[0], argv[1]);\n+\t\tvalue = normalize_value(argv[0], argv[1], &default_kvi);\n \t\tret = git_config_set_multivar_in_file_gently(given_config_source.file,\n \t\t\t\t\t\t\t     argv[0], value, argv[2],\n \t\t\t\t\t\t\t     flags | CONFIG_FLAGS_MULTI_REPLACE);\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex a4aa0fbb8f5..fe86dfeb62a 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -137,7 +137,7 @@ static int git_fetch_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"submodule.fetchjobs\")) {\n-\t\tfetch_config->submodule_fetch_jobs = parse_submodule_fetchjobs(k, v);\n+\t\tfetch_config->submodule_fetch_jobs = parse_submodule_fetchjobs(k, v, ctx->kvi);\n \t\treturn 0;\n \t} else if (!strcmp(k, \"fetch.recursesubmodules\")) {\n \t\tfetch_config->recurse_submodules = parse_fetch_recurse_submodules_arg(k, v);\n@@ -145,7 +145,7 @@ static int git_fetch_config(const char *k, const char *v,\n \t}\n \n \tif (!strcmp(k, \"fetch.parallel\")) {\n-\t\tfetch_config->parallel = git_config_int(k, v);\n+\t\tfetch_config->parallel = git_config_int(k, v, ctx->kvi);\n \t\tif (fetch_config->parallel < 0)\n \t\t\tdie(_(\"fetch.parallel cannot be negative\"));\n \t\tif (!fetch_config->parallel)\ndiff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c\nindex 91a776e2f17..6d2826b07da 100644\n--- a/builtin/fsmonitor--daemon.c\n+++ b/builtin/fsmonitor--daemon.c\n@@ -41,7 +41,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(var, FSMONITOR__IPC_THREADS)) {\n-\t\tint i = git_config_int(var, value);\n+\t\tint i = git_config_int(var, value, ctx->kvi);\n \t\tif (i < 1)\n \t\t\treturn error(_(\"value of '%s' out of range: %d\"),\n \t\t\t\t     FSMONITOR__IPC_THREADS, i);\n@@ -50,7 +50,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, FSMONITOR__START_TIMEOUT)) {\n-\t\tint i = git_config_int(var, value);\n+\t\tint i = git_config_int(var, value, ctx->kvi);\n \t\tif (i < 0)\n \t\t\treturn error(_(\"value of '%s' out of range: %d\"),\n \t\t\t\t     FSMONITOR__START_TIMEOUT, i);\n@@ -60,7 +60,7 @@ static int fsmonitor_config(const char *var, const char *value,\n \n \tif (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) {\n \t\tint is_bool;\n-\t\tint i = git_config_bool_or_int(var, value, &is_bool);\n+\t\tint i = git_config_bool_or_int(var, value, ctx->kvi, &is_bool);\n \t\tif (i < 0)\n \t\t\treturn error(_(\"value of '%s' not bool or int: %d\"),\n \t\t\t\t     var, i);\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 757d52b94ec..3a464e6faab 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -301,7 +301,7 @@ static int grep_cmd_config(const char *var, const char *value,\n \t\tst = -1;\n \n \tif (!strcmp(var, \"grep.threads\")) {\n-\t\tnum_threads = git_config_int(var, value);\n+\t\tnum_threads = git_config_int(var, value, ctx->kvi);\n \t\tif (num_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d) for %s\"),\n \t\t\t    num_threads, var);\ndiff --git a/builtin/index-pack.c b/builtin/index-pack.c\nindex de8884ea5c2..e428f6d9a4a 100644\n--- a/builtin/index-pack.c\n+++ b/builtin/index-pack.c\n@@ -1587,13 +1587,13 @@ static int git_index_pack_config(const char *k, const char *v,\n \tstruct pack_idx_option *opts = cb;\n \n \tif (!strcmp(k, \"pack.indexversion\")) {\n-\t\topts->version = git_config_int(k, v);\n+\t\topts->version = git_config_int(k, v, ctx->kvi);\n \t\tif (opts->version > 2)\n \t\t\tdie(_(\"bad pack.indexVersion=%\"PRIu32), opts->version);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.threads\")) {\n-\t\tnr_threads = git_config_int(k, v);\n+\t\tnr_threads = git_config_int(k, v, ctx->kvi);\n \t\tif (nr_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d)\"),\n \t\t\t    nr_threads);\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 09d6a13075b..c7337354aaf 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -574,7 +574,7 @@ static int git_log_config(const char *var, const char *value,\n \tif (!strcmp(var, \"format.subjectprefix\"))\n \t\treturn git_config_string(&fmt_patch_subject_prefix, var, value);\n \tif (!strcmp(var, \"format.filenamemaxlength\")) {\n-\t\tfmt_patch_name_max = git_config_int(var, value);\n+\t\tfmt_patch_name_max = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"format.encodeemailheaders\")) {\ndiff --git a/builtin/pack-objects.c b/builtin/pack-objects.c\nindex 34aa0b483a0..38054a38b2b 100644\n--- a/builtin/pack-objects.c\n+++ b/builtin/pack-objects.c\n@@ -3139,23 +3139,23 @@ static int git_pack_config(const char *k, const char *v,\n \t\t\t   const struct config_context *ctx, void *cb)\n {\n \tif (!strcmp(k, \"pack.window\")) {\n-\t\twindow = git_config_int(k, v);\n+\t\twindow = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.windowmemory\")) {\n-\t\twindow_memory_limit = git_config_ulong(k, v);\n+\t\twindow_memory_limit = git_config_ulong(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.depth\")) {\n-\t\tdepth = git_config_int(k, v);\n+\t\tdepth = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.deltacachesize\")) {\n-\t\tmax_delta_cache_size = git_config_int(k, v);\n+\t\tmax_delta_cache_size = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.deltacachelimit\")) {\n-\t\tcache_max_small_delta_size = git_config_int(k, v);\n+\t\tcache_max_small_delta_size = git_config_int(k, v, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.writebitmaphashcache\")) {\n@@ -3181,7 +3181,7 @@ static int git_pack_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.threads\")) {\n-\t\tdelta_search_threads = git_config_int(k, v);\n+\t\tdelta_search_threads = git_config_int(k, v, ctx->kvi);\n \t\tif (delta_search_threads < 0)\n \t\t\tdie(_(\"invalid number of threads specified (%d)\"),\n \t\t\t    delta_search_threads);\n@@ -3192,7 +3192,7 @@ static int git_pack_config(const char *k, const char *v,\n \t\treturn 0;\n \t}\n \tif (!strcmp(k, \"pack.indexversion\")) {\n-\t\tpack_idx_opts.version = git_config_int(k, v);\n+\t\tpack_idx_opts.version = git_config_int(k, v, ctx->kvi);\n \t\tif (pack_idx_opts.version > 2)\n \t\t\tdie(_(\"bad pack.indexVersion=%\"PRIu32),\n \t\t\t    pack_idx_opts.version);\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 94d9898aff7..98f6f0038f0 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -158,12 +158,12 @@ static int receive_pack_config(const char *var, const char *value,\n \t}\n \n \tif (strcmp(var, \"receive.unpacklimit\") == 0) {\n-\t\treceive_unpack_limit = git_config_int(var, value);\n+\t\treceive_unpack_limit = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (strcmp(var, \"transfer.unpacklimit\") == 0) {\n-\t\ttransfer_unpack_limit = git_config_int(var, value);\n+\t\ttransfer_unpack_limit = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n@@ -231,7 +231,7 @@ static int receive_pack_config(const char *var, const char *value,\n \t\treturn git_config_string(&cert_nonce_seed, var, value);\n \n \tif (strcmp(var, \"receive.certnonceslop\") == 0) {\n-\t\tnonce_stamp_slop_limit = git_config_ulong(var, value);\n+\t\tnonce_stamp_slop_limit = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n@@ -246,12 +246,12 @@ static int receive_pack_config(const char *var, const char *value,\n \t}\n \n \tif (strcmp(var, \"receive.keepalive\") == 0) {\n-\t\tkeepalive_in_sec = git_config_int(var, value);\n+\t\tkeepalive_in_sec = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (strcmp(var, \"receive.maxinputsize\") == 0) {\n-\t\tmax_input_size = git_config_int64(var, value);\n+\t\tmax_input_size = git_config_int64(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \ndiff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c\nindex f8e9d85e77a..57185cf5f3d 100644\n--- a/builtin/submodule--helper.c\n+++ b/builtin/submodule--helper.c\n@@ -2192,13 +2192,13 @@ static int update_clone_task_finished(int result,\n }\n \n static int git_update_clone_config(const char *var, const char *value,\n-\t\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t\t   const struct config_context *ctx,\n \t\t\t\t   void *cb)\n {\n \tint *max_jobs = cb;\n \n \tif (!strcmp(var, \"submodule.fetchjobs\"))\n-\t\t*max_jobs = parse_submodule_fetchjobs(var, value);\n+\t\t*max_jobs = parse_submodule_fetchjobs(var, value, ctx->kvi);\n \treturn 0;\n }\n \ndiff --git a/config.c b/config.c\nindex d10faba56d3..4e3c4dcf415 100644\n--- a/config.c\n+++ b/config.c\n@@ -73,18 +73,8 @@ struct config_reader {\n \t *\n \t * The \"source\" variable will be non-NULL only when we are actually\n \t * parsing a real config source (file, blob, cmdline, etc).\n-\t *\n-\t * The \"config_kvi\" variable will be non-NULL only when we are feeding\n-\t * cached config from a configset into a callback.\n-\t *\n-\t * They cannot be non-NULL at the same time. If they are both NULL, then\n-\t * we aren't parsing anything (and depending on the function looking at\n-\t * the variables, it's either a bug for it to be called in the first\n-\t * place, or it's a function which can be reused for non-config\n-\t * purposes, and should fall back to some sane behavior).\n \t */\n \tstruct config_source *source;\n-\tstruct key_value_info *config_kvi;\n };\n /*\n  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n@@ -96,8 +86,6 @@ static struct config_reader the_reader;\n static inline void config_reader_push_source(struct config_reader *reader,\n \t\t\t\t\t     struct config_source *top)\n {\n-\tif (reader->config_kvi)\n-\t\tBUG(\"source should not be set while iterating a config set\");\n \ttop->prev = reader->source;\n \treader->source = top;\n }\n@@ -112,12 +100,6 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\n \treturn ret;\n }\n \n-static inline void config_reader_set_kvi(struct config_reader *reader,\n-\t\t\t\t\t struct key_value_info *kvi)\n-{\n-\treader->config_kvi = kvi;\n-}\n-\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -1346,80 +1328,78 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)\n \treturn 1;\n }\n \n-static int reader_config_name(struct config_reader *reader, const char **out);\n-static int reader_origin_type(struct config_reader *reader,\n-\t\t\t      enum config_origin_type *type);\n NORETURN\n-static void die_bad_number(struct config_reader *reader, const char *name,\n-\t\t\t   const char *value)\n+static void die_bad_number(const char *name, const char *value,\n+\t\t\t   const struct key_value_info *kvi)\n {\n \tconst char *error_type = (errno == ERANGE) ?\n \t\tN_(\"out of range\") : N_(\"invalid unit\");\n \tconst char *bad_numeric = N_(\"bad numeric config value '%s' for '%s': %s\");\n-\tconst char *config_name = NULL;\n-\tenum config_origin_type config_origin = CONFIG_ORIGIN_UNKNOWN;\n+\n+\tif (!kvi)\n+\t\tBUG(\"kvi should not be NULL\");\n \n \tif (!value)\n \t\tvalue = \"\";\n \n-\t/* Ignoring the return value is okay since we handle missing values. */\n-\treader_config_name(reader, &config_name);\n-\treader_origin_type(reader, &config_origin);\n-\n-\tif (!config_name)\n+\tif (!kvi->filename)\n \t\tdie(_(bad_numeric), value, name, _(error_type));\n \n-\tswitch (config_origin) {\n+\tswitch (kvi->origin_type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in blob %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_FILE:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in file %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_STDIN:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in standard input: %s\"),\n \t\t    value, name, _(error_type));\n \tcase CONFIG_ORIGIN_SUBMODULE_BLOB:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in submodule-blob %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tcase CONFIG_ORIGIN_CMDLINE:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in command line %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \tdefault:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in %s: %s\"),\n-\t\t    value, name, config_name, _(error_type));\n+\t\t    value, name, kvi->filename, _(error_type));\n \t}\n }\n \n-int git_config_int(const char *name, const char *value)\n+int git_config_int(const char *name, const char *value,\n+\t\t   const struct key_value_info *kvi)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-int64_t git_config_int64(const char *name, const char *value)\n+int64_t git_config_int64(const char *name, const char *value,\n+\t\t\t const struct key_value_info *kvi)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-unsigned long git_config_ulong(const char *name, const char *value)\n+unsigned long git_config_ulong(const char *name, const char *value,\n+\t\t\t       const struct key_value_info *kvi)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n-ssize_t git_config_ssize_t(const char *name, const char *value)\n+ssize_t git_config_ssize_t(const char *name, const char *value,\n+\t\t\t   const struct key_value_info *kvi)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(&the_reader, name, value);\n+\t\tdie_bad_number(name, value, kvi);\n \treturn ret;\n }\n \n@@ -1524,7 +1504,8 @@ int git_parse_maybe_bool(const char *value)\n \treturn -1;\n }\n \n-int git_config_bool_or_int(const char *name, const char *value, int *is_bool)\n+int git_config_bool_or_int(const char *name, const char *value,\n+\t\t\t   const struct key_value_info *kvi, int *is_bool)\n {\n \tint v = git_parse_maybe_bool_text(value);\n \tif (0 <= v) {\n@@ -1532,7 +1513,7 @@ int git_config_bool_or_int(const char *name, const char *value, int *is_bool)\n \t\treturn v;\n \t}\n \t*is_bool = 0;\n-\treturn git_config_int(name, value);\n+\treturn git_config_int(name, value, kvi);\n }\n \n int git_config_bool(const char *name, const char *value)\n@@ -1658,7 +1639,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t\telse if (!git_parse_maybe_bool_text(value))\n \t\t\tdefault_abbrev = the_hash_algo->hexsz;\n \t\telse {\n-\t\t\tint abbrev = git_config_int(var, value);\n+\t\t\tint abbrev = git_config_int(var, value, ctx->kvi);\n \t\t\tif (abbrev < minimum_abbrev || abbrev > the_hash_algo->hexsz)\n \t\t\t\treturn error(_(\"abbrev length out of range: %d\"), abbrev);\n \t\t\tdefault_abbrev = abbrev;\n@@ -1670,7 +1651,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t\treturn set_disambiguate_hint_config(var, value);\n \n \tif (!strcmp(var, \"core.loosecompression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, ctx->kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -1681,7 +1662,7 @@ static int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.compression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, ctx->kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -1695,7 +1676,7 @@ static int git_default_core_config(const char *var, const char *value,\n \n \tif (!strcmp(var, \"core.packedgitwindowsize\")) {\n \t\tint pgsz_x2 = getpagesize() * 2;\n-\t\tpacked_git_window_size = git_config_ulong(var, value);\n+\t\tpacked_git_window_size = git_config_ulong(var, value, ctx->kvi);\n \n \t\t/* This value must be multiple of (pagesize * 2) */\n \t\tpacked_git_window_size /= pgsz_x2;\n@@ -1706,17 +1687,17 @@ static int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.bigfilethreshold\")) {\n-\t\tbig_file_threshold = git_config_ulong(var, value);\n+\t\tbig_file_threshold = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"core.packedgitlimit\")) {\n-\t\tpacked_git_limit = git_config_ulong(var, value);\n+\t\tpacked_git_limit = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"core.deltabasecachelimit\")) {\n-\t\tdelta_base_cache_limit = git_config_ulong(var, value);\n+\t\tdelta_base_cache_limit = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n@@ -1995,12 +1976,12 @@ int git_default_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"pack.packsizelimit\")) {\n-\t\tpack_size_limit_cfg = git_config_ulong(var, value);\n+\t\tpack_size_limit_cfg = git_config_ulong(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n \tif (!strcmp(var, \"pack.compression\")) {\n-\t\tint level = git_config_int(var, value);\n+\t\tint level = git_config_int(var, value, ctx->kvi);\n \t\tif (level == -1)\n \t\t\tlevel = Z_DEFAULT_COMPRESSION;\n \t\telse if (level < 0 || level > Z_BEST_COMPRESSION)\n@@ -2344,13 +2325,11 @@ static void configset_iter(struct config_reader *reader, struct config_set *set,\n \t\tvalue_index = list->items[i].value_index;\n \t\tvalues = &entry->value_list;\n \n-\t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n \t\tctx.kvi = values->items[value_index].util;\n \t\tif (fn(entry->key, values->items[value_index].string, &ctx, data) < 0)\n \t\t\tgit_die_config_linenr(entry->key,\n \t\t\t\t\t      ctx.kvi->filename,\n \t\t\t\t\t      ctx.kvi->linenr);\n-\t\tconfig_reader_set_kvi(reader, NULL);\n \t}\n }\n \n@@ -2536,11 +2515,12 @@ int git_configset_add_file(struct config_set *set, const char *filename)\n \treturn git_config_from_file(config_set_callback, filename, &data);\n }\n \n-int git_configset_get_value(struct config_set *set, const char *key, const char **value)\n+int git_configset_get_value(struct config_set *set, const char *key,\n+\t\t\t    const char **value, struct key_value_info *kvi)\n {\n \tconst struct string_list *values = NULL;\n \tint ret;\n-\n+\tstruct string_list_item item;\n \t/*\n \t * Follows \"last one wins\" semantic, i.e., if there are multiple matches for the\n \t * queried key in the files of the configset, the value returned will be the last\n@@ -2550,7 +2530,10 @@ int git_configset_get_value(struct config_set *set, const char *key, const char\n \t\treturn ret;\n \n \tassert(values->nr > 0);\n-\t*value = values->items[values->nr - 1].string;\n+\titem = values->items[values->nr - 1];\n+\t*value = item.string;\n+\tif (kvi)\n+\t\t*kvi = *((struct key_value_info *)item.util);\n \treturn 0;\n }\n \n@@ -2603,7 +2586,7 @@ int git_configset_get(struct config_set *set, const char *key)\n int git_configset_get_string(struct config_set *set, const char *key, char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value))\n+\tif (!git_configset_get_value(set, key, &value, NULL))\n \t\treturn git_config_string((const char **)dest, key, value);\n \telse\n \t\treturn 1;\n@@ -2613,7 +2596,7 @@ static int git_configset_get_string_tmp(struct config_set *set, const char *key,\n \t\t\t\t\tconst char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(key);\n \t\t*dest = value;\n@@ -2626,8 +2609,10 @@ static int git_configset_get_string_tmp(struct config_set *set, const char *key,\n int git_configset_get_int(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_int(key, value);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_int(key, value, &kvi);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2636,8 +2621,10 @@ int git_configset_get_int(struct config_set *set, const char *key, int *dest)\n int git_configset_get_ulong(struct config_set *set, const char *key, unsigned long *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_ulong(key, value);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_ulong(key, value, &kvi);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2646,7 +2633,7 @@ int git_configset_get_ulong(struct config_set *set, const char *key, unsigned lo\n int git_configset_get_bool(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\t*dest = git_config_bool(key, value);\n \t\treturn 0;\n \t} else\n@@ -2657,8 +2644,10 @@ int git_configset_get_bool_or_int(struct config_set *set, const char *key,\n \t\t\t\tint *is_bool, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n-\t\t*dest = git_config_bool_or_int(key, value, is_bool);\n+\tstruct key_value_info kvi;\n+\n+\tif (!git_configset_get_value(set, key, &value, &kvi)) {\n+\t\t*dest = git_config_bool_or_int(key, value, &kvi, is_bool);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n@@ -2667,7 +2656,7 @@ int git_configset_get_bool_or_int(struct config_set *set, const char *key,\n int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value, NULL)) {\n \t\t*dest = git_parse_maybe_bool(value);\n \t\tif (*dest == -1)\n \t\t\treturn -1;\n@@ -2679,7 +2668,7 @@ int git_configset_get_maybe_bool(struct config_set *set, const char *key, int *d\n int git_configset_get_pathname(struct config_set *set, const char *key, const char **dest)\n {\n \tconst char *value;\n-\tif (!git_configset_get_value(set, key, &value))\n+\tif (!git_configset_get_value(set, key, &value, NULL))\n \t\treturn git_config_pathname(dest, key, value);\n \telse\n \t\treturn 1;\n@@ -2749,7 +2738,7 @@ int repo_config_get_value(struct repository *repo,\n \t\t\t  const char *key, const char **value)\n {\n \tgit_config_check_init(repo);\n-\treturn git_configset_get_value(repo->config, key, value);\n+\treturn git_configset_get_value(repo->config, key, value, NULL);\n }\n \n int repo_config_get_value_multi(struct repository *repo, const char *key,\n@@ -3989,18 +3978,6 @@ int parse_config_key(const char *var,\n \treturn 0;\n }\n \n-static int reader_origin_type(struct config_reader *reader,\n-\t\t\t      enum config_origin_type *type)\n-{\n-\tif (the_reader.config_kvi)\n-\t\t*type = reader->config_kvi->origin_type;\n-\telse if(the_reader.source)\n-\t\t*type = reader->source->origin_type;\n-\telse\n-\t\treturn 1;\n-\treturn 0;\n-}\n-\n const char *config_origin_type_name(enum config_origin_type type)\n {\n \tswitch (type) {\n@@ -4039,17 +4016,6 @@ const char *config_scope_name(enum config_scope scope)\n \t}\n }\n \n-static int reader_config_name(struct config_reader *reader, const char **out)\n-{\n-\tif (the_reader.config_kvi)\n-\t\t*out = reader->config_kvi->filename;\n-\telse if (the_reader.source)\n-\t\t*out = reader->source->name;\n-\telse\n-\t\treturn 1;\n-\treturn 0;\n-}\n-\n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n {\n \tint i;\ndiff --git a/config.h b/config.h\nindex 82eeba94e71..bd366ddb5ef 100644\n--- a/config.h\n+++ b/config.h\n@@ -249,22 +249,26 @@ int git_parse_maybe_bool(const char *);\n  * Parse the string to an integer, including unit factors. Dies on error;\n  * otherwise, returns the parsed result.\n  */\n-int git_config_int(const char *, const char *);\n+int git_config_int(const char *, const char *, const struct key_value_info *);\n \n-int64_t git_config_int64(const char *, const char *);\n+int64_t git_config_int64(const char *, const char *,\n+\t\t\t const struct key_value_info *);\n \n /**\n  * Identical to `git_config_int`, but for unsigned longs.\n  */\n-unsigned long git_config_ulong(const char *, const char *);\n+unsigned long git_config_ulong(const char *, const char *,\n+\t\t\t       const struct key_value_info *);\n \n-ssize_t git_config_ssize_t(const char *, const char *);\n+ssize_t git_config_ssize_t(const char *, const char *,\n+\t\t\t   const struct key_value_info *);\n \n /**\n  * Same as `git_config_bool`, except that integers are returned as-is, and\n  * an `is_bool` flag is unset.\n  */\n-int git_config_bool_or_int(const char *, const char *, int *);\n+int git_config_bool_or_int(const char *, const char *,\n+\t\t\t   const struct key_value_info *, int *);\n \n /**\n  * Parse a string into a boolean value, respecting keywords like \"true\" and\n@@ -529,7 +533,8 @@ int git_configset_get(struct config_set *cs, const char *key);\n  * touching `value`. The caller should not free or modify `value`, as it\n  * is owned by the cache.\n  */\n-int git_configset_get_value(struct config_set *cs, const char *key, const char **dest);\n+int git_configset_get_value(struct config_set *cs, const char *key,\n+\t\t\t    const char **dest, struct key_value_info *kvi);\n \n int git_configset_get_string(struct config_set *cs, const char *key, char **dest);\n int git_configset_get_int(struct config_set *cs, const char *key, int *dest);\ndiff --git a/contrib/coccinelle/git_config_number.cocci b/contrib/coccinelle/git_config_number.cocci\nnew file mode 100644\nindex 00000000000..7b57dceefe6\n--- /dev/null\n+++ b/contrib/coccinelle/git_config_number.cocci\n@@ -0,0 +1,27 @@\n+@@\n+identifier C1, C2, C3;\n+@@\n+(\n+(\n+git_config_int\n+|\n+git_config_int64\n+|\n+git_config_ulong\n+|\n+git_config_ssize_t\n+)\n+  (C1, C2\n++ , ctx->kvi\n+  )\n+|\n+(\n+git_configset_get_value\n+|\n+git_config_bool_or_int\n+)\n+  (C1, C2\n++ , ctx->kvi\n+ , C3\n+  )\n+)\ndiff --git a/diff.c b/diff.c\nindex 0e382c8f7f0..460211e7d40 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -379,13 +379,14 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.context\")) {\n-\t\tdiff_context_default = git_config_int(var, value);\n+\t\tdiff_context_default = git_config_int(var, value, ctx->kvi);\n \t\tif (diff_context_default < 0)\n \t\t\treturn -1;\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.interhunkcontext\")) {\n-\t\tdiff_interhunk_context_default = git_config_int(var, value);\n+\t\tdiff_interhunk_context_default = git_config_int(var, value,\n+\t\t\t\t\t\t\t\tctx->kvi);\n \t\tif (diff_interhunk_context_default < 0)\n \t\t\treturn -1;\n \t\treturn 0;\n@@ -411,7 +412,7 @@ int git_diff_ui_config(const char *var, const char *value,\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.statgraphwidth\")) {\n-\t\tdiff_stat_graph_width = git_config_int(var, value);\n+\t\tdiff_stat_graph_width = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"diff.external\"))\n@@ -450,7 +451,7 @@ int git_diff_basic_config(const char *var, const char *value,\n \tconst char *name;\n \n \tif (!strcmp(var, \"diff.renamelimit\")) {\n-\t\tdiff_rename_limit_default = git_config_int(var, value);\n+\t\tdiff_rename_limit_default = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 10137444321..3ae59bde2f2 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -25,7 +25,7 @@ int fmt_merge_msg_config(const char *key, const char *value,\n {\n \tif (!strcmp(key, \"merge.log\") || !strcmp(key, \"merge.summary\")) {\n \t\tint is_bool;\n-\t\tmerge_log_config = git_config_bool_or_int(key, value, &is_bool);\n+\t\tmerge_log_config = git_config_bool_or_int(key, value, ctx->kvi, &is_bool);\n \t\tif (!is_bool && merge_log_config < 0)\n \t\t\treturn error(\"%s: negative length %s\", key, value);\n \t\tif (is_bool && merge_log_config)\ndiff --git a/help.c b/help.c\nindex ac0ae5ac0dc..389382b1482 100644\n--- a/help.c\n+++ b/help.c\n@@ -545,7 +545,7 @@ static struct cmdnames aliases;\n #define AUTOCORRECT_IMMEDIATELY (-1)\n \n static int git_unknown_cmd_config(const char *var, const char *value,\n-\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t  const struct config_context *ctx,\n \t\t\t\t  void *cb UNUSED)\n {\n \tconst char *p;\n@@ -560,7 +560,7 @@ static int git_unknown_cmd_config(const char *var, const char *value,\n \t\t} else if (!strcmp(value, \"prompt\")) {\n \t\t\tautocorrect = AUTOCORRECT_PROMPT;\n \t\t} else {\n-\t\t\tint v = git_config_int(var, value);\n+\t\t\tint v = git_config_int(var, value, ctx->kvi);\n \t\t\tautocorrect = (v < 0)\n \t\t\t\t? AUTOCORRECT_IMMEDIATELY : v;\n \t\t}\ndiff --git a/http.c b/http.c\nindex 762502828c9..b12b234bde1 100644\n--- a/http.c\n+++ b/http.c\n@@ -414,21 +414,21 @@ static int http_options(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(\"http.minsessions\", var)) {\n-\t\tmin_curl_sessions = git_config_int(var, value);\n+\t\tmin_curl_sessions = git_config_int(var, value, ctx->kvi);\n \t\tif (min_curl_sessions > 1)\n \t\t\tmin_curl_sessions = 1;\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.maxrequests\", var)) {\n-\t\tmax_requests = git_config_int(var, value);\n+\t\tmax_requests = git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.lowspeedlimit\", var)) {\n-\t\tcurl_low_speed_limit = (long)git_config_int(var, value);\n+\t\tcurl_low_speed_limit = (long)git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \tif (!strcmp(\"http.lowspeedtime\", var)) {\n-\t\tcurl_low_speed_time = (long)git_config_int(var, value);\n+\t\tcurl_low_speed_time = (long)git_config_int(var, value, ctx->kvi);\n \t\treturn 0;\n \t}\n \n@@ -464,7 +464,7 @@ static int http_options(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(\"http.postbuffer\", var)) {\n-\t\thttp_post_buffer = git_config_ssize_t(var, value);\n+\t\thttp_post_buffer = git_config_ssize_t(var, value, ctx->kvi);\n \t\tif (http_post_buffer < 0)\n \t\t\twarning(_(\"negative value for http.postBuffer; defaulting to %d\"), LARGE_PACKET_MAX);\n \t\tif (http_post_buffer < LARGE_PACKET_MAX)\ndiff --git a/imap-send.c b/imap-send.c\nindex 47777e76861..3518a4ace6c 100644\n--- a/imap-send.c\n+++ b/imap-send.c\n@@ -1342,7 +1342,7 @@ static int git_imap_config(const char *var, const char *val,\n \telse if (!strcmp(\"imap.authmethod\", var))\n \t\treturn git_config_string(&server.auth_method, var, val);\n \telse if (!strcmp(\"imap.port\", var))\n-\t\tserver.port = git_config_int(var, val);\n+\t\tserver.port = git_config_int(var, val, ctx->kvi);\n \telse if (!strcmp(\"imap.host\", var)) {\n \t\tif (!val) {\n \t\t\tgit_die_config(\"imap.host\", \"Missing value for 'imap.host'\");\ndiff --git a/sequencer.c b/sequencer.c\nindex 34754d17596..a0aaee3056d 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2883,7 +2883,7 @@ static int git_config_string_dup(char **dest,\n }\n \n static int populate_opts_cb(const char *key, const char *value,\n-\t\t\t    const struct config_context *ctx UNUSED,\n+\t\t\t    const struct config_context *ctx,\n \t\t\t    void *data)\n {\n \tstruct replay_opts *opts = data;\n@@ -2892,26 +2892,26 @@ static int populate_opts_cb(const char *key, const char *value,\n \tif (!value)\n \t\terror_flag = 0;\n \telse if (!strcmp(key, \"options.no-commit\"))\n-\t\topts->no_commit = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->no_commit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.edit\"))\n-\t\topts->edit = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->edit = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-empty\"))\n \t\topts->allow_empty =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-empty-message\"))\n \t\topts->allow_empty_message =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.keep-redundant-commits\"))\n \t\topts->keep_redundant_commits =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag);\n+\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.signoff\"))\n-\t\topts->signoff = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->signoff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.record-origin\"))\n-\t\topts->record_origin = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->record_origin = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.allow-ff\"))\n-\t\topts->allow_ff = git_config_bool_or_int(key, value, &error_flag);\n+\t\topts->allow_ff = git_config_bool_or_int(key, value, ctx->kvi, &error_flag);\n \telse if (!strcmp(key, \"options.mainline\"))\n-\t\topts->mainline = git_config_int(key, value);\n+\t\topts->mainline = git_config_int(key, value, ctx->kvi);\n \telse if (!strcmp(key, \"options.strategy\"))\n \t\tgit_config_string_dup(&opts->strategy, key, value);\n \telse if (!strcmp(key, \"options.gpg-sign\"))\n@@ -2920,7 +2920,7 @@ static int populate_opts_cb(const char *key, const char *value,\n \t\tstrvec_push(&opts->xopts, value);\n \t} else if (!strcmp(key, \"options.allow-rerere-auto\"))\n \t\topts->allow_rerere_auto =\n-\t\t\tgit_config_bool_or_int(key, value, &error_flag) ?\n+\t\t\tgit_config_bool_or_int(key, value, ctx->kvi, &error_flag) ?\n \t\t\t\tRERERE_AUTOUPDATE : RERERE_NOAUTOUPDATE;\n \telse if (!strcmp(key, \"options.default-msg-cleanup\")) {\n \t\topts->explicit_cleanup = 1;\ndiff --git a/setup.c b/setup.c\nindex fadba5bab4b..ca80fb464bb 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -597,7 +597,7 @@ static int check_repo_format(const char *var, const char *value,\n \tconst char *ext;\n \n \tif (strcmp(var, \"core.repositoryformatversion\") == 0)\n-\t\tdata->version = git_config_int(var, value);\n+\t\tdata->version = git_config_int(var, value, ctx->kvi);\n \telse if (skip_prefix(var, \"extensions.\", &ext)) {\n \t\tswitch (handle_extension_v0(var, value, ext, data)) {\n \t\tcase EXTENSION_ERROR:\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 3f25bd13674..54be580e2a9 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -304,9 +304,10 @@ static int parse_fetch_recurse(const char *opt, const char *arg,\n \t}\n }\n \n-int parse_submodule_fetchjobs(const char *var, const char *value)\n+int parse_submodule_fetchjobs(const char *var, const char *value,\n+\t\t\t      const struct key_value_info *kvi)\n {\n-\tint fetchjobs = git_config_int(var, value);\n+\tint fetchjobs = git_config_int(var, value, kvi);\n \tif (fetchjobs < 0)\n \t\tdie(_(\"negative values not allowed for submodule.fetchJobs\"));\n \tif (!fetchjobs)\n@@ -849,14 +850,14 @@ struct fetch_config {\n };\n \n static int gitmodules_fetch_config(const char *var, const char *value,\n-\t\t\t\t   const struct config_context *ctx UNUSED,\n+\t\t\t\t   const struct config_context *ctx,\n \t\t\t\t   void *cb)\n {\n \tstruct fetch_config *config = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\")) {\n \t\tif (config->max_children)\n \t\t\t*(config->max_children) =\n-\t\t\t\tparse_submodule_fetchjobs(var, value);\n+\t\t\t\tparse_submodule_fetchjobs(var, value, ctx->kvi);\n \t\treturn 0;\n \t} else if (!strcmp(var, \"fetch.recursesubmodules\")) {\n \t\tif (config->recurse_submodules)\n@@ -878,12 +879,12 @@ void fetch_config_from_gitmodules(int *max_children, int *recurse_submodules)\n }\n \n static int gitmodules_update_clone_config(const char *var, const char *value,\n-\t\t\t\t\t  const struct config_context *ctx UNUSED,\n+\t\t\t\t\t  const struct config_context *ctx,\n \t\t\t\t\t  void *cb)\n {\n \tint *max_jobs = cb;\n \tif (!strcmp(var, \"submodule.fetchjobs\"))\n-\t\t*max_jobs = parse_submodule_fetchjobs(var, value);\n+\t\t*max_jobs = parse_submodule_fetchjobs(var, value, ctx->kvi);\n \treturn 0;\n }\n \ndiff --git a/submodule-config.h b/submodule-config.h\nindex c2045875bbb..2a37689cc27 100644\n--- a/submodule-config.h\n+++ b/submodule-config.h\n@@ -50,7 +50,8 @@ struct repository;\n \n void submodule_cache_free(struct submodule_cache *cache);\n \n-int parse_submodule_fetchjobs(const char *var, const char *value);\n+int parse_submodule_fetchjobs(const char *var, const char *value,\n+\t\t\t      const struct key_value_info *kvi);\n int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);\n struct option;\n int option_fetch_parse_recurse_submodules(const struct option *opt,\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 3f4c3678318..ed444ca4c25 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -63,12 +63,12 @@ static int iterate_cb(const char *var, const char *value,\n }\n \n static int parse_int_cb(const char *var, const char *value,\n-\t\t\tconst struct config_context *ctx UNUSED, void *data)\n+\t\t\tconst struct config_context *ctx, void *data)\n {\n \tconst char *key_to_match = data;\n \n \tif (!strcmp(key_to_match, var)) {\n-\t\tint parsed = git_config_int(value, value);\n+\t\tint parsed = git_config_int(value, value, ctx->kvi);\n \t\tprintf(\"%d\\n\", parsed);\n \t}\n \treturn 0;\n@@ -182,7 +182,7 @@ int cmd__config(int argc, const char **argv)\n \t\t\t\tgoto exit2;\n \t\t\t}\n \t\t}\n-\t\tif (!git_configset_get_value(&cs, argv[2], &v)) {\n+\t\tif (!git_configset_get_value(&cs, argv[2], &v, NULL)) {\n \t\t\tif (!v)\n \t\t\t\tprintf(\"(NULL)\\n\");\n \t\t\telse\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 951fd1f9c25..c03415a5460 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1275,7 +1275,8 @@ static int find_symref(const char *refname,\n }\n \n static int parse_object_filter_config(const char *var, const char *value,\n-\t\t\t\t       struct upload_pack_data *data)\n+\t\t\t\t      const struct key_value_info *kvi,\n+\t\t\t\t      struct upload_pack_data *data)\n {\n \tstruct strbuf buf = STRBUF_INIT;\n \tconst char *sub, *key;\n@@ -1302,7 +1303,8 @@ static int parse_object_filter_config(const char *var, const char *value,\n \t\t}\n \t\tstring_list_insert(&data->allowed_filters, buf.buf)->util =\n \t\t\t(void *)(intptr_t)1;\n-\t\tdata->tree_filter_max_depth = git_config_ulong(var, value);\n+\t\tdata->tree_filter_max_depth = git_config_ulong(var, value,\n+\t\t\t\t\t\t\t       kvi);\n \t}\n \n \tstrbuf_release(&buf);\n@@ -1310,7 +1312,7 @@ static int parse_object_filter_config(const char *var, const char *value,\n }\n \n static int upload_pack_config(const char *var, const char *value,\n-\t\t\t      const struct config_context *ctx UNUSED,\n+\t\t\t      const struct config_context *ctx,\n \t\t\t      void *cb_data)\n {\n \tstruct upload_pack_data *data = cb_data;\n@@ -1331,7 +1333,7 @@ static int upload_pack_config(const char *var, const char *value,\n \t\telse\n \t\t\tdata->allow_uor &= ~ALLOW_ANY_SHA1;\n \t} else if (!strcmp(\"uploadpack.keepalive\", var)) {\n-\t\tdata->keepalive = git_config_int(var, value);\n+\t\tdata->keepalive = git_config_int(var, value, ctx->kvi);\n \t\tif (!data->keepalive)\n \t\t\tdata->keepalive = -1;\n \t} else if (!strcmp(\"uploadpack.allowfilter\", var)) {\n@@ -1346,7 +1348,7 @@ static int upload_pack_config(const char *var, const char *value,\n \t\tdata->advertise_sid = git_config_bool(var, value);\n \t}\n \n-\tif (parse_object_filter_config(var, value, data) < 0)\n+\tif (parse_object_filter_config(var, value, ctx->kvi, data) < 0)\n \t\treturn -1;\n \n \treturn parse_hide_refs_config(var, value, \"uploadpack\", &data->hidden_refs);\ndiff --git a/worktree.c b/worktree.c\nindex c448fecd4b3..af277ec9010 100644\n--- a/worktree.c\n+++ b/worktree.c\n@@ -835,7 +835,7 @@ int init_worktree_config(struct repository *r)\n \t * Relocate that value to avoid breaking all worktrees with this\n \t * upgrade to worktree config.\n \t */\n-\tif (!git_configset_get_value(&cs, \"core.worktree\", &core_worktree)) {\n+\tif (!git_configset_get_value(&cs, \"core.worktree\", &core_worktree, NULL)) {\n \t\tif ((res = move_config_setting(\"core.worktree\", core_worktree,\n \t\t\t\t\t       common_config_file,\n \t\t\t\t\t       main_worktree_file)))\n-- \ngitgitgadget\n\n"},{"id":"478996","messageId":"20230628222326.116535-1-jonathantanmy@google.com","threadId":"59629","inReplyTo":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 00/11] config: remove global state from config iteration","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-06-28T22:23:26Z","receivedAt":"2023-06-28T22:23:33Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> As promised, this version addresses the comments on v3.\n> \n> = Changes since v4\n> \n> - Squash 6-7/12 since `test_must_fail` doesn't catch BUG()\n> - Move a hunk to later in the series where it belongs\n> - Replace a memcpy with `*a = *b`\n> \n> = Changes since v3\n> \n> - Rebase onto newer 'master'\n> - Move the 'remove UNUSED from tr2_cfg_cb' hunk from 9/12 -> 8/12. It should\n>   have been there all along; v3 8/12 didn't build at all.\n\nLooks like all my comments have been addressed, so this patch set looks\ngood to me.\n"},{"id":"478999","messageId":"xmqqa5wjb3w1.fsf@gitster.g","threadId":"59629","inReplyTo":"20230628222326.116535-1-jonathantanmy@google.com","subject":"Re: [PATCH v5 00/11] config: remove global state from config iteration","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-06-28T22:47:42Z","receivedAt":"2023-06-28T22:51:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> \"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>> As promised, this version addresses the comments on v3.\n>> \n>> = Changes since v4\n>> \n>> - Squash 6-7/12 since `test_must_fail` doesn't catch BUG()\n>> - Move a hunk to later in the series where it belongs\n>> - Replace a memcpy with `*a = *b`\n>> \n>> = Changes since v3\n>> \n>> - Rebase onto newer 'master'\n>> - Move the 'remove UNUSED from tr2_cfg_cb' hunk from 9/12 -> 8/12. It should\n>>   have been there all along; v3 8/12 didn't build at all.\n>\n> Looks like all my comments have been addressed, so this patch set looks\n> good to me.\n\nThanks.  Queued.\n"},{"id":"479391","messageId":"00194850-b881-2b01-9b9b-fc7d7422e804@gmail.com","threadId":"59629","inReplyTo":"pull.1497.v5.git.git.1687980390.gitgitgadget@gmail.com","subject":"Re: [PATCH v5 00/11] config: remove global state from config iteration","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2023-07-11T18:41:28Z","receivedAt":"2023-07-11T18:41:36Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Glen\n\nSorry it has taken me so long to look at these patches. I see they're in \nmaster now but I have just had a quick look through anyway and they \nlooked good. It would definitely be nice to fix the hacky handling of \n\"git checkout --conflict\" at some point but that is a long standing \nproblem and not the fault of this series.\n\nBest Wishes\n\nPhillip\n\nOn 28/06/2023 20:26, Glen Choo via GitGitGadget wrote:\n> As promised, this version addresses the comments on v3.\n> \n> = Changes since v4\n> \n> - Squash 6-7/12 since `test_must_fail` doesn't catch BUG()\n> - Move a hunk to later in the series where it belongs\n> - Replace a memcpy with `*a = *b`\n> \n> = Changes since v3\n> \n> - Rebase onto newer 'master'\n> - Move the 'remove UNUSED from tr2_cfg_cb' hunk from 9/12 -> 8/12. It should\n>    have been there all along; v3 8/12 didn't build at all.\n> \n> \n> Glen Choo (11):\n>    config: inline git_color_default_config\n>    urlmatch.h: use config_fn_t type\n>    config: add ctx arg to config_fn_t\n>    config.c: pass ctx in configsets\n>    config: pass ctx with config files\n>    config.c: pass ctx with CLI config\n>    trace2: plumb config kvi\n>    config: pass kvi to die_bad_number()\n>    config.c: remove config_reader from configsets\n>    config: add kvi.path, use it to evaluate includes\n>    config: pass source to config_parser_event_fn_t\n> \n>   alias.c                                       |   3 +-\n>   archive-tar.c                                 |   5 +-\n>   archive-zip.c                                 |   1 +\n>   builtin/add.c                                 |   8 +-\n>   builtin/blame.c                               |   5 +-\n>   builtin/branch.c                              |   8 +-\n>   builtin/cat-file.c                            |   5 +-\n>   builtin/checkout.c                            |  12 +-\n>   builtin/clean.c                               |   9 +-\n>   builtin/clone.c                               |  11 +-\n>   builtin/column.c                              |   3 +-\n>   builtin/commit-graph.c                        |   3 +-\n>   builtin/commit.c                              |  20 +-\n>   builtin/config.c                              |  72 ++-\n>   builtin/difftool.c                            |   5 +-\n>   builtin/fetch.c                               |  13 +-\n>   builtin/fsmonitor--daemon.c                   |  11 +-\n>   builtin/grep.c                                |  12 +-\n>   builtin/help.c                                |   5 +-\n>   builtin/index-pack.c                          |   9 +-\n>   builtin/log.c                                 |  12 +-\n>   builtin/merge.c                               |   7 +-\n>   builtin/multi-pack-index.c                    |   1 +\n>   builtin/pack-objects.c                        |  19 +-\n>   builtin/patch-id.c                            |   5 +-\n>   builtin/pull.c                                |   5 +-\n>   builtin/push.c                                |   5 +-\n>   builtin/read-tree.c                           |   5 +-\n>   builtin/rebase.c                              |   5 +-\n>   builtin/receive-pack.c                        |  15 +-\n>   builtin/reflog.c                              |   7 +-\n>   builtin/remote.c                              |  15 +-\n>   builtin/repack.c                              |   5 +-\n>   builtin/reset.c                               |   5 +-\n>   builtin/send-pack.c                           |   5 +-\n>   builtin/show-branch.c                         |   8 +-\n>   builtin/stash.c                               |   5 +-\n>   builtin/submodule--helper.c                   |   3 +-\n>   builtin/tag.c                                 |   9 +-\n>   builtin/var.c                                 |   5 +-\n>   builtin/worktree.c                            |   5 +-\n>   bundle-uri.c                                  |   9 +-\n>   color.c                                       |   8 -\n>   color.h                                       |   6 +-\n>   compat/mingw.c                                |   3 +-\n>   compat/mingw.h                                |   4 +-\n>   config.c                                      | 552 +++++++-----------\n>   config.h                                      |  80 ++-\n>   connect.c                                     |   4 +-\n>   .../coccinelle/config_fn_ctx.pending.cocci    | 144 +++++\n>   contrib/coccinelle/git_config_number.cocci    |  27 +\n>   convert.c                                     |   4 +-\n>   credential.c                                  |   1 +\n>   delta-islands.c                               |   4 +-\n>   diff.c                                        |  19 +-\n>   diff.h                                        |   7 +-\n>   fetch-pack.c                                  |   5 +-\n>   fmt-merge-msg.c                               |   7 +-\n>   fmt-merge-msg.h                               |   3 +-\n>   fsck.c                                        |  12 +-\n>   fsck.h                                        |   4 +-\n>   git-compat-util.h                             |   2 +\n>   gpg-interface.c                               |   7 +-\n>   grep.c                                        |   7 +-\n>   grep.h                                        |   4 +-\n>   help.c                                        |   9 +-\n>   http.c                                        |  15 +-\n>   ident.c                                       |   4 +-\n>   ident.h                                       |   4 +-\n>   imap-send.c                                   |   7 +-\n>   ll-merge.c                                    |   1 +\n>   ls-refs.c                                     |   1 +\n>   mailinfo.c                                    |   5 +-\n>   notes-utils.c                                 |   4 +-\n>   notes.c                                       |   4 +-\n>   pager.c                                       |   5 +-\n>   pretty.c                                      |   1 +\n>   promisor-remote.c                             |   4 +-\n>   remote.c                                      |   8 +-\n>   revision.c                                    |   4 +-\n>   scalar.c                                      |   4 +-\n>   sequencer.c                                   |  29 +-\n>   setup.c                                       |  18 +-\n>   submodule-config.c                            |  31 +-\n>   submodule-config.h                            |   3 +-\n>   t/helper/test-config.c                        |  24 +-\n>   t/helper/test-userdiff.c                      |   4 +-\n>   t/t1300-config.sh                             |  27 +\n>   trace2.c                                      |   4 +-\n>   trace2.h                                      |   3 +-\n>   trace2/tr2_cfg.c                              |  16 +-\n>   trace2/tr2_sysenv.c                           |   3 +-\n>   trace2/tr2_tgt.h                              |   4 +-\n>   trace2/tr2_tgt_event.c                        |   4 +-\n>   trace2/tr2_tgt_normal.c                       |   4 +-\n>   trace2/tr2_tgt_perf.c                         |   4 +-\n>   trailer.c                                     |   2 +\n>   upload-pack.c                                 |  18 +-\n>   urlmatch.c                                    |   7 +-\n>   urlmatch.h                                    |   8 +-\n>   worktree.c                                    |   2 +-\n>   xdiff-interface.c                             |   5 +-\n>   xdiff-interface.h                             |   4 +-\n>   103 files changed, 960 insertions(+), 638 deletions(-)\n>   create mode 100644 contrib/coccinelle/config_fn_ctx.pending.cocci\n>   create mode 100644 contrib/coccinelle/git_config_number.cocci\n> \n> \n> base-commit: 6ff334181cfb6485d3ba50843038209a2a253907\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1497%2Fchooglen%2Fconfig%2Fno-global-v5\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1497/chooglen/config/no-global-v5\n> Pull-Request: https://github.com/git/git/pull/1497\n> \n> Range-diff vs v4:\n> \n>    1:  7bfffb454c5 =  1:  7bfffb454c5 config: inline git_color_default_config\n>    2:  739c519ce62 =  2:  739c519ce62 urlmatch.h: use config_fn_t type\n>    3:  a9a0a50f32a =  3:  a9a0a50f32a config: add ctx arg to config_fn_t\n>    4:  39b2e291f86 =  4:  39b2e291f86 config.c: pass ctx in configsets\n>    5:  bfc6d2833c5 =  5:  bfc6d2833c5 config: pass ctx with config files\n>    6:  897bdc759b5 <  -:  ----------- builtin/config.c: test misuse of format_config()\n>    7:  33e4437737d !  6:  7b24eefbcf3 config.c: pass ctx with CLI config\n>       @@ Commit message\n>            * git_config_parse_parameter() hasn't been setting config source\n>              information, so plumb \"kvi\" there too.\n>        \n>       -    * \"git config --get-urlmatch --show-scope\" iterates config to collect\n>       -      values, but then attempts to display the scope after config iteration.\n>       -      Fix this by copying the \"kvi\" value in the collection phase so that it\n>       -      can be read back later. This means that we can now support \"git config\n>       -      --get-urlmatch --show-origin\" (we don't allow this combination of args\n>       -      because of this bug), but that is left unchanged for now.\n>       +    * Several sites in builtin/config.c have been calling current_config_*()\n>       +      functions outside of config callbacks (indirectly, via the\n>       +      format_config() helper), which means they're reading state that isn't\n>       +      set correctly:\n>        \n>       -    * \"git config --default\" doesn't have config source metadata when\n>       -      displaying the default value. Fix this by treating the default value\n>       -      as if it came from the command line (e.g. like we do with \"git -c\" or\n>       -      \"git config --file\"), using kvi_from_param().\n>       +      * \"git config --get-urlmatch --show-scope\" iterates config to collect\n>       +        values, but then attempts to display the scope after config\n>       +        iteration, causing the \"unknown\" scope to be shown instead of the\n>       +        config file's scope. It's clear that this wasn't intended: we knew\n>       +        that \"--get-urlmatch\" couldn't show config source metadata, which is\n>       +        why \"--show-origin\" was marked incompatible with \"--get-urlmatch\"\n>       +        when it was introduced [1]. It was most likely a mistake that we\n>       +        allowed \"--show-scope\" to sneak through.\n>       +\n>       +        Fix this by copying the \"kvi\" value in the collection phase so that\n>       +        it can be read back later. This means that we can now support \"git\n>       +        config --get-urlmatch --show-origin\", but that is left unchanged\n>       +        for now.\n>       +\n>       +      * \"git config --default\" doesn't have config source metadata when\n>       +        displaying the default value, so \"--show-scope\" also results in\n>       +        \"unknown\", and \"--show-origin\" results in a BUG(). Fix this by\n>       +        treating the default value as if it came from the command line (e.g.\n>       +        like we do with \"git -c\" or \"git config --file\"), using\n>       +        kvi_from_param().\n>       +\n>       +    [1] https://lore.kernel.org/git/20160205112001.GA13397@sigill.intra.peff.net/\n>        \n>            Signed-off-by: Glen Choo <chooglen@google.com>\n>        \n>       @@ config.c: static int configset_find_element(struct config_set *set, const char *\n>         \t\t\t       const char *value)\n>         {\n>        @@ config.c: static int configset_add_value(struct config_reader *reader,\n>       - \tl_item->e = e;\n>       - \tl_item->value_index = e->value_list.nr - 1;\n>       -\n>       --\tif (!reader->source)\n>       --\t\tBUG(\"configset_add_value has no source\");\n>       + \tif (!reader->source)\n>       + \t\tBUG(\"configset_add_value has no source\");\n>         \tif (reader->source->name) {\n>        -\t\tkvi_from_source(reader->source, current_config_scope(), kv_info);\n>        +\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n>       @@ config.h: void git_global_config(char **user, char **xdg);\n>          * Match and parse a config key of the form:\n>        \n>         ## t/t1300-config.sh ##\n>       -@@ t/t1300-config.sh: test_expect_success 'urlmatch with --show-scope' '\n>       - \tEOF\n>       +@@ t/t1300-config.sh: test_expect_success 'urlmatch' '\n>       + \ttest_cmp expect actual\n>       + '\n>         \n>       - \tcat >expect <<-EOF &&\n>       --\tunknown\thttp.cookiefile /tmp/cookie.txt\n>       --\tunknown\thttp.sslverify false\n>       ++test_expect_success 'urlmatch with --show-scope' '\n>       ++\tcat >.git/config <<-\\EOF &&\n>       ++\t[http \"https://weak.example.com\"]\n>       ++\t\tsslVerify = false\n>       ++\t\tcookieFile = /tmp/cookie.txt\n>       ++\tEOF\n>       ++\n>       ++\tcat >expect <<-EOF &&\n>        +\tlocal\thttp.cookiefile /tmp/cookie.txt\n>        +\tlocal\thttp.sslverify false\n>       - \tEOF\n>       - \tgit config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&\n>       - \ttest_cmp expect actual\n>       ++\tEOF\n>       ++\tgit config --get-urlmatch --show-scope HTTP https://weak.example.com >actual &&\n>       ++\ttest_cmp expect actual\n>       ++'\n>       ++\n>       + test_expect_success 'urlmatch favors more specific URLs' '\n>       + \tcat >.git/config <<-\\EOF &&\n>       + \t[http \"https://example.com/\"]\n>        @@ t/t1300-config.sh: test_expect_success '--show-origin blob ref' '\n>       + \ttest_cmp expect output\n>         '\n>         \n>       - test_expect_success '--show-origin with --default' '\n>       --\ttest_must_fail git config --show-origin --default foo some.key\n>       ++test_expect_success '--show-origin with --default' '\n>        +\tgit config --show-origin --default foo some.key >actual &&\n>        +\techo \"command line:\tfoo\" >expect &&\n>        +\ttest_cmp expect actual\n>       - '\n>       -\n>       ++'\n>       ++\n>         test_expect_success '--show-scope with --list' '\n>       + \tcat >expect <<-EOF &&\n>       + \tglobal\tuser.global=true\n>        @@ t/t1300-config.sh: test_expect_success '--show-scope with --show-origin' '\n>       -\n>       - test_expect_success '--show-scope with --default' '\n>       - \tgit config --show-scope --default foo some.key >actual &&\n>       --\techo \"unknown\tfoo\" >expect &&\n>       -+\techo \"command\tfoo\" >expect &&\n>       - \ttest_cmp expect actual\n>       + \ttest_cmp expect output\n>         '\n>         \n>       ++test_expect_success '--show-scope with --default' '\n>       ++\tgit config --show-scope --default foo some.key >actual &&\n>       ++\techo \"command\tfoo\" >expect &&\n>       ++\ttest_cmp expect actual\n>       ++'\n>       ++\n>       + test_expect_success 'override global and system config' '\n>       + \ttest_when_finished rm -f \\\"\\$HOME\\\"/.gitconfig &&\n>       + \tcat >\"$HOME\"/.gitconfig <<-EOF &&\n>    8:  9bd5f60282c =  7:  7d64dcbdade trace2: plumb config kvi\n>    9:  114723ee4a7 =  8:  9e71c10ca0a config: pass kvi to die_bad_number()\n>   10:  807057b6d7f !  9:  4776600e790 config.c: remove config_reader from configsets\n>       @@ config.c: static int configset_add_value(const struct key_value_info *kvi_p,\n>         \tl_item->e = e;\n>         \tl_item->value_index = e->value_list.nr - 1;\n>         \n>       +-\tif (!reader->source)\n>       +-\t\tBUG(\"configset_add_value has no source\");\n>        -\tif (reader->source->name) {\n>        -\t\tkvi_from_source(reader->source, kvi_p->scope, kv_info);\n>        -\t} else {\n>        -\t\tkvi_from_param(kv_info);\n>        -\t}\n>       -+\tmemcpy(kv_info, kvi_p, sizeof(struct key_value_info));\n>       ++\t*kv_info = *kvi_p;\n>         \tsi->util = kv_info;\n>         \n>         \treturn 0;\n>   11:  3f0f84df972 = 10:  2b33977aba6 config: add kvi.path, use it to evaluate includes\n>   12:  fe2f154fe8b = 11:  8347d3c9b80 config: pass source to config_parser_event_fn_t\n> \n"}]}