{"thread":{"id":"59327","subject":"[PATCH 0/6] [RFC] config.c: use struct for config reading state","startedAt":"2023-03-01T00:38:24Z","lastAt":"2023-03-30T17:51:34Z","messageCount":72,"participants":["Glen Choo via GitGitGadget","Junio C Hamano","Jonathan Tan","Calvin Wan","Glen Choo","Ævar Arnfjörð Bjarmason"],"isPatch":true,"patchVersion":1,"patchTotal":6},"messages":[{"id":"472876","messageId":"pull.1463.git.git.1677631097.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":null,"subject":"[PATCH 0/6] [RFC] config.c: use struct for config reading state","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-01T00:38:11Z","receivedAt":"2023-03-01T00:38:24Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"This RFC is preparation for config.[ch] to be libified as as part of the\nlibification effort that Emily described in [1]. One of the first goals is\nto read config from a file, but the trouble with how config.c is written\ntoday is that all reading operations rely on global state, so before turning\nthat into a library, we'd want to make that state non-global.\n\nThis series gets us about halfway there; it does enough plumbing for a\nworkable-but-kinda-ugly library interface, but with a little bit more work,\nI think we can get rid of global state in-tree as well. That requires a fair\namount of work though, so I'd like to get thoughts on that before starting\nwork.\n\n= Description\n\nThis series extracts the global config reading state into \"struct\nconfig_reader\" and plumbs it through the config reading machinery. It's very\nsimilar to how we've plumbed \"struct repository\" and other 'context objects'\nin the past, except:\n\n * The global state (named \"the_reader\") for the git process lives in a\n   config.c static variable, and not on \"the_repository\". See 3/6 for the\n   rationale.\n\n * I've stopped short of adding \"struct config_reader\" to config.h public\n   functions, since that would affect non-config.c callers.\n\nIf we stop right here, it's quite easy to extend it to a future config-lib.h\nwithout having to adjust the config.h interface:\n\n * Move the core config reading functionality from config.c to config-lib.c.\n\n * Have config-lib.h accept \"struct config_reader\" as an arg.\n\n * Have config.h call config-lib.h while passing \"the_reader\".\n\nand I have some WIP patches that do just that [3], but I think they can be\nsignificantly improved if we go a bit further...\n\n= Leftover bits and RFC\n\nWith a bit more work on the config machinery, we could make it so that\nconfig reading stops being global even without adjusting non-config.c\ncallers. The idea is pretty simple: have the config machinery initialize an\ninternal \"struct config_reader\" every time we read config and expose that\nstate to the config callbacks (instead of, in this series, asking the caller\nto initialize \"struct config_reader\" themselves). I believe that only config\ncallbacks are accessing this state, e.g. because they use the low-level\ninformation (like builtin/config.c printing the filename and scope of the\nvalue) or for error reporting (like git_parse_int() reporting the filename\nand line number of the value it failed to parse), and only config callbacks\nshould be accessing this state anyway.\n\nThe catch (aka the reason I stopped halfway through) is that I couldn't find\na way to expose \"struct config_reader\" state without some fairly big\nchanges, complexity-wise or LoC-wise, e.g.\n\n * We could add \"struct config_reader\" to \"config_fn_t\", i.e.\n   \n   -typedef int (*config_fn_t)(const char *var, const char *val, void\n   *data); +typedef int (*config_fn_t)(const struct config_reader *reader,\n   const char *var, const char *val, void *data);\n   \n   which isn't complex at all, except that there are ~100 config_fn_t\n   implementations [3] and a good number of them may never reference\n   \"reader\". If the churn is tolerable, I think this a good way forward.\n\n * We could create a new kind of \"config_fn_t\" that accepts \"struct\n   config_reader\", e.g.\n   \n   typedef int (*config_fn_t)(const char *var, const char *val, void *data);\n   +typedef int (*config_state_fn_t)(const struct config_reader *reader,\n   const char *var, const char *val, void *data);\n   \n   and only adjust the callers that would actually reference \"reader\". This\n   is less churn, but I couldn't find a great way to do this kind of\n   'switching between config callback types' elegantly.\n\n * We could smuggle \"struct config_reader\" to callback functions in a way\n   that interested callers could see it, but uninterested callers could\n   ignore. One trick that Jonathan Tan came up with (though not necessarily\n   endorsed) would be to allocate a struct for the config value + \"struct\n   config_reader\", then, interested callers could use \"offset_of\" to recover\n   the \"struct config_reader\". It's a little hacky, but it's low-churn.\n\n= Questions\n\n * Is this worth merging without the extra work? There are some cleanups in\n   this series that could make it valuable, but there are also some hacks\n   (see 4/6) that aren't so great.\n * Is the extra work even worth it?\n * Do any of the ideas seem more promising than the others? Are there other\n   ideas I'm missing?\n\n[1]\nhttps://lore.kernel.org/git/CAJoAoZ=Cig_kLocxKGax31sU7Xe4==BGzC__Bg2_pr7krNq6MA@mail.gmail.com\n[2]\nhttps://github.com/chooglen/git/compare/config/structify-reading...chooglen:git:config/read-without-globals\n[3] This is a rough estimate based on \"git grep\"-ing callers of the config.h\nfunctions. I vaguely recall callbacks being called \"old-style\", with the\nsuggestion that we should replace them with the \"new-style\" constant time\ngit_config_get_*() family of functions. That would decrease the number of\nconfig callbacks significantly.\n\nGlen Choo (6):\n  config.c: plumb config_source through static fns\n  config.c: don't assign to \"cf\" directly\n  config.c: create config_reader and the_reader\n  config.c: plumb the_reader through callbacks\n  config.c: remove current_config_kvi\n  config.c: remove current_parsing_scope\n\n config.c | 489 ++++++++++++++++++++++++++++++++-----------------------\n 1 file changed, 287 insertions(+), 202 deletions(-)\n\n\nbase-commit: dadc8e6dacb629f46aee39bde90b6f09b73722eb\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1463%2Fchooglen%2Fconfig%2Fstructify-reading-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1463/chooglen/config/structify-reading-v1\nPull-Request: https://github.com/git/git/pull/1463\n-- \ngitgitgadget\n"},{"id":"472877","messageId":"9f72cbb8d78265c85c3e6405a5a61cf35db5ebca.1677631097.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.git.git.1677631097.gitgitgadget@gmail.com","subject":"[PATCH 2/6] config.c: don't assign to \"cf\" directly","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-01T00:38:13Z","receivedAt":"2023-03-01T00:38: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\nTo make \"cf\" easier to remove, replace all direct assignments to it with\nfunction calls. This refactor has an additional maintainability benefit:\nall of these functions were manually implementing stack pop/push\nsemantics on \"struct config_source\", so replacing them with function\ncalls allows us to only implement this logic once.\n\nIn this process, perform some now-obvious clean ups:\n\n- Drop some unnecessary \"cf\" assignments in populate_remote_urls().\n  Since it was introduced in 399b198489 (config: include file if remote\n  URL matches a glob, 2022-01-18), it has stored and restored the value\n  of \"cf\" to ensure that it doesn't get accidentally mutated. However,\n  this was never necessary since \"do_config_from()\" already pushes/pops\n  \"cf\" further down the call chain.\n\n- Zero out every \"struct config_source\" with a dedicated initializer.\n  This matters because the \"struct config_source\" is assigned to \"cf\"\n  and we later 'pop the stack' by assigning \"cf = cf->prev\", but\n  \"cf->prev\" could be pointing to uninitialized garbage.\n\n  Fortunately, this has never bothered us since we never try to read\n  \"cf\" except while iterating through config, in which case, \"cf\" is\n  either set to a sensible value (when parsing a file), or it is ignored\n  (when iterating a configset). Later in the series, zero-ing out memory\n  will also let us enforce the constraint that \"cf\" and\n  \"current_config_kvi\" are never non-NULL together.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 40 ++++++++++++++++++++++++++--------------\n 1 file changed, 26 insertions(+), 14 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 84ae97741ac..1f89addc771 100644\n--- a/config.c\n+++ b/config.c\n@@ -49,6 +49,7 @@ struct config_source {\n \tint (*do_ungetc)(int c, struct config_source *conf);\n \tlong (*do_ftell)(struct config_source *c);\n };\n+#define CONFIG_SOURCE_INIT { 0 }\n \n /*\n  * These variables record the \"current\" config source, which\n@@ -78,6 +79,23 @@ static struct key_value_info *current_config_kvi;\n  */\n static enum config_scope current_parsing_scope;\n \n+static inline void config_state_push_source(struct config_source *top)\n+{\n+\tif (cf)\n+\t\ttop->prev = cf;\n+\tcf = top;\n+}\n+\n+static inline struct config_source *config_state_pop_source()\n+{\n+\tstruct config_source *ret;\n+\tif (!cf)\n+\t\tBUG(\"tried to pop config source, but we weren't reading config\");\n+\tret = cf;\n+\tcf = cf->prev;\n+\treturn ret;\n+}\n+\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -345,14 +363,12 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tstruct config_source *store_cf = cf;\n \tstruct key_value_info *store_kvi = current_config_kvi;\n \tenum config_scope store_scope = current_parsing_scope;\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tcf = NULL;\n \tcurrent_config_kvi = NULL;\n \tcurrent_parsing_scope = 0;\n \n@@ -360,7 +376,6 @@ static void populate_remote_urls(struct config_include_data *inc)\n \tstring_list_init_dup(inc->remote_urls);\n \tconfig_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);\n \n-\tcf = store_cf;\n \tcurrent_config_kvi = store_kvi;\n \tcurrent_parsing_scope = store_scope;\n }\n@@ -714,12 +729,10 @@ 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;\n+\tstruct config_source source = CONFIG_SOURCE_INIT;\n \n-\tmemset(&source, 0, sizeof(source));\n-\tsource.prev = cf;\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n-\tcf = &source;\n+\tconfig_state_push_source(&source);\n \n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n@@ -777,7 +790,7 @@ out:\n \tstrbuf_release(&envvar);\n \tstrvec_clear(&to_free);\n \tfree(envw);\n-\tcf = source.prev;\n+\tconfig_state_pop_source();\n \treturn ret;\n }\n \n@@ -1948,20 +1961,19 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,\n \tint ret;\n \n \t/* push config-file parsing state stack */\n-\ttop->prev = cf;\n \ttop->linenr = 1;\n \ttop->eof = 0;\n \ttop->total_len = 0;\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n-\tcf = top;\n+\tconfig_state_push_source(top);\n \n-\tret = git_parse_source(cf, fn, data, opts);\n+\tret = git_parse_source(top, fn, data, opts);\n \n \t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n \tstrbuf_release(&top->var);\n-\tcf = top->prev;\n+\tconfig_state_pop_source();\n \n \treturn ret;\n }\n@@ -1971,7 +1983,7 @@ static int do_config_from_file(config_fn_t fn,\n \t\tconst char *name, const char *path, FILE *f,\n \t\tvoid *data, const struct config_options *opts)\n {\n-\tstruct config_source top;\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n \n \ttop.u.file = f;\n@@ -2023,7 +2035,7 @@ int git_config_from_mem(config_fn_t fn,\n \t\t\tconst char *name, const char *buf, size_t len,\n \t\t\tvoid *data, const struct config_options *opts)\n {\n-\tstruct config_source top;\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n \n \ttop.u.buf.buf = buf;\n \ttop.u.buf.len = len;\n-- \ngitgitgadget\n\n"},{"id":"472878","messageId":"ad513d832d8267f9e4805db5e7a9e8915fc62a23.1677631097.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.git.git.1677631097.gitgitgadget@gmail.com","subject":"[PATCH 1/6] config.c: plumb config_source through static fns","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-01T00:38:12Z","receivedAt":"2023-03-01T00:38:28Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThis reduces the direct dependence on the \"cf\" static variable, which\nwill make it easier to remove in a later commit. The plumbed arg is\nnamed \"cs\" to differentiate between it and the static variable.\n\nIn some cases (public functions and config callback functions), there\nisn't an obvious way to plumb \"struct config_source\" through function\nargs. As a workaround, add references to \"cf\" that we'll address in\nlater commits.\n\nThe remaining references to \"cf\" are direct assignments to \"cf\", which\nwe'll also address in a later commit.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 192 +++++++++++++++++++++++++++++--------------------------\n 1 file changed, 100 insertions(+), 92 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 00090a32fc3..84ae97741ac 100644\n--- a/config.c\n+++ b/config.c\n@@ -156,7 +156,8 @@ 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(const char *path, struct config_include_data *inc)\n+static int handle_path_include(struct config_source *cs, const char *path,\n+\t\t\t       struct config_include_data *inc)\n {\n \tint ret = 0;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -177,14 +178,14 @@ static int handle_path_include(const char *path, struct config_include_data *inc\n \tif (!is_absolute_path(path)) {\n \t\tchar *slash;\n \n-\t\tif (!cf || !cf->path) {\n+\t\tif (!cs || !cs->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(cf->path);\n+\t\tslash = find_last_dir_sep(cs->path);\n \t\tif (slash)\n-\t\t\tstrbuf_add(&buf, cf->path, slash - cf->path + 1);\n+\t\t\tstrbuf_add(&buf, cs->path, slash - cs->path + 1);\n \t\tstrbuf_addstr(&buf, path);\n \t\tpath = buf.buf;\n \t}\n@@ -192,8 +193,8 @@ static int handle_path_include(const char *path, struct config_include_data *inc\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    !cf ? \"<unknown>\" :\n-\t\t\t    cf->name ? cf->name :\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\tinc->depth--;\n@@ -210,7 +211,8 @@ 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 strbuf *pat)\n+static int prepare_include_condition_pattern(struct config_source *cs,\n+\t\t\t\t\t     struct strbuf *pat)\n {\n \tstruct strbuf path = STRBUF_INIT;\n \tchar *expanded;\n@@ -226,11 +228,11 @@ static int prepare_include_condition_pattern(struct strbuf *pat)\n \tif (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {\n \t\tconst char *slash;\n \n-\t\tif (!cf || !cf->path)\n+\t\tif (!cs || !cs->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, cf->path, 1);\n+\t\tstrbuf_realpath(&path, cs->path, 1);\n \t\tslash = find_last_dir_sep(path.buf);\n \t\tif (!slash)\n \t\t\tBUG(\"how is this possible?\");\n@@ -245,7 +247,8 @@ static int prepare_include_condition_pattern(struct strbuf *pat)\n \treturn prefix;\n }\n \n-static int include_by_gitdir(const struct config_options *opts,\n+static int include_by_gitdir(struct config_source *cs,\n+\t\t\t     const struct config_options *opts,\n \t\t\t     const char *cond, size_t cond_len, int icase)\n {\n \tstruct strbuf text = STRBUF_INIT;\n@@ -261,7 +264,7 @@ static int include_by_gitdir(const struct config_options *opts,\n \n \tstrbuf_realpath(&text, git_dir, 1);\n \tstrbuf_add(&pattern, cond, cond_len);\n-\tprefix = prepare_include_condition_pattern(&pattern);\n+\tprefix = prepare_include_condition_pattern(cs, &pattern);\n \n again:\n \tif (prefix < 0)\n@@ -406,15 +409,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_include_data *inc,\n+static int include_condition_is_true(struct config_source *cs,\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(opts, cond, cond_len, 0);\n+\t\treturn include_by_gitdir(cs, 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(opts, cond, cond_len, 1);\n+\t\treturn include_by_gitdir(cs, 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@@ -441,16 +445,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(value, inc);\n+\t\tret = handle_path_include(cf, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n-\t    cond && include_condition_is_true(inc, cond, cond_len) &&\n+\t    cond && include_condition_is_true(cf, 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(value, inc);\n+\t\tret = handle_path_include(cf, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -777,21 +781,21 @@ out:\n \treturn ret;\n }\n \n-static int get_next_char(void)\n+static int get_next_char(struct config_source *cs)\n {\n-\tint c = cf->do_fgetc(cf);\n+\tint c = cs->do_fgetc(cs);\n \n \tif (c == '\\r') {\n \t\t/* DOS like systems */\n-\t\tc = cf->do_fgetc(cf);\n+\t\tc = cs->do_fgetc(cs);\n \t\tif (c != '\\n') {\n \t\t\tif (c != EOF)\n-\t\t\t\tcf->do_ungetc(c, cf);\n+\t\t\t\tcs->do_ungetc(c, cs);\n \t\t\tc = '\\r';\n \t\t}\n \t}\n \n-\tif (c != EOF && ++cf->total_len > INT_MAX) {\n+\tif (c != EOF && ++cs->total_len > INT_MAX) {\n \t\t/*\n \t\t * This is an absurdly long config file; refuse to parse\n \t\t * further in order to protect downstream code from integer\n@@ -799,38 +803,38 @@ static int get_next_char(void)\n \t\t * but we can mark EOF and put trash in the return value,\n \t\t * which will trigger a parse error.\n \t\t */\n-\t\tcf->eof = 1;\n+\t\tcs->eof = 1;\n \t\treturn 0;\n \t}\n \n \tif (c == '\\n')\n-\t\tcf->linenr++;\n+\t\tcs->linenr++;\n \tif (c == EOF) {\n-\t\tcf->eof = 1;\n-\t\tcf->linenr++;\n+\t\tcs->eof = 1;\n+\t\tcs->linenr++;\n \t\tc = '\\n';\n \t}\n \treturn c;\n }\n \n-static char *parse_value(void)\n+static char *parse_value(struct config_source *cs)\n {\n \tint quote = 0, comment = 0, space = 0;\n \n-\tstrbuf_reset(&cf->value);\n+\tstrbuf_reset(&cs->value);\n \tfor (;;) {\n-\t\tint c = get_next_char();\n+\t\tint c = get_next_char(cs);\n \t\tif (c == '\\n') {\n \t\t\tif (quote) {\n-\t\t\t\tcf->linenr--;\n+\t\t\t\tcs->linenr--;\n \t\t\t\treturn NULL;\n \t\t\t}\n-\t\t\treturn cf->value.buf;\n+\t\t\treturn cs->value.buf;\n \t\t}\n \t\tif (comment)\n \t\t\tcontinue;\n \t\tif (isspace(c) && !quote) {\n-\t\t\tif (cf->value.len)\n+\t\t\tif (cs->value.len)\n \t\t\t\tspace++;\n \t\t\tcontinue;\n \t\t}\n@@ -841,9 +845,9 @@ static char *parse_value(void)\n \t\t\t}\n \t\t}\n \t\tfor (; space; space--)\n-\t\t\tstrbuf_addch(&cf->value, ' ');\n+\t\t\tstrbuf_addch(&cs->value, ' ');\n \t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char();\n+\t\t\tc = get_next_char(cs);\n \t\t\tswitch (c) {\n \t\t\tcase '\\n':\n \t\t\t\tcontinue;\n@@ -863,18 +867,19 @@ static char *parse_value(void)\n \t\t\tdefault:\n \t\t\t\treturn NULL;\n \t\t\t}\n-\t\t\tstrbuf_addch(&cf->value, c);\n+\t\t\tstrbuf_addch(&cs->value, c);\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '\"') {\n \t\t\tquote = 1-quote;\n \t\t\tcontinue;\n \t\t}\n-\t\tstrbuf_addch(&cf->value, c);\n+\t\tstrbuf_addch(&cs->value, c);\n \t}\n }\n \n-static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n+static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n+\t\t     struct strbuf *name)\n {\n \tint c;\n \tchar *value;\n@@ -882,8 +887,8 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n \n \t/* Get the full name */\n \tfor (;;) {\n-\t\tc = get_next_char();\n-\t\tif (cf->eof)\n+\t\tc = get_next_char(cs);\n+\t\tif (cs->eof)\n \t\t\tbreak;\n \t\tif (!iskeychar(c))\n \t\t\tbreak;\n@@ -891,13 +896,13 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n \t}\n \n \twhile (c == ' ' || c == '\\t')\n-\t\tc = get_next_char();\n+\t\tc = get_next_char(cs);\n \n \tvalue = NULL;\n \tif (c != '\\n') {\n \t\tif (c != '=')\n \t\t\treturn -1;\n-\t\tvalue = parse_value();\n+\t\tvalue = parse_value(cs);\n \t\tif (!value)\n \t\t\treturn -1;\n \t}\n@@ -906,20 +911,21 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n \t * the line we just parsed during the call to fn to get\n \t * accurate line number in error messages.\n \t */\n-\tcf->linenr--;\n+\tcs->linenr--;\n \tret = fn(name->buf, value, data);\n \tif (ret >= 0)\n-\t\tcf->linenr++;\n+\t\tcs->linenr++;\n \treturn ret;\n }\n \n-static int get_extended_base_var(struct strbuf *name, int c)\n+static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n+\t\t\t\t int c)\n {\n-\tcf->subsection_case_sensitive = 0;\n+\tcs->subsection_case_sensitive = 0;\n \tdo {\n \t\tif (c == '\\n')\n \t\t\tgoto error_incomplete_line;\n-\t\tc = get_next_char();\n+\t\tc = get_next_char(cs);\n \t} while (isspace(c));\n \n \t/* We require the format to be '[base \"extension\"]' */\n@@ -928,13 +934,13 @@ static int get_extended_base_var(struct strbuf *name, int c)\n \tstrbuf_addch(name, '.');\n \n \tfor (;;) {\n-\t\tint c = get_next_char();\n+\t\tint c = get_next_char(cs);\n \t\tif (c == '\\n')\n \t\t\tgoto error_incomplete_line;\n \t\tif (c == '\"')\n \t\t\tbreak;\n \t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char();\n+\t\t\tc = get_next_char(cs);\n \t\t\tif (c == '\\n')\n \t\t\t\tgoto error_incomplete_line;\n \t\t}\n@@ -942,25 +948,25 @@ static int get_extended_base_var(struct strbuf *name, int c)\n \t}\n \n \t/* Final ']' */\n-\tif (get_next_char() != ']')\n+\tif (get_next_char(cs) != ']')\n \t\treturn -1;\n \treturn 0;\n error_incomplete_line:\n-\tcf->linenr--;\n+\tcs->linenr--;\n \treturn -1;\n }\n \n-static int get_base_var(struct strbuf *name)\n+static int get_base_var(struct config_source *cs, struct strbuf *name)\n {\n-\tcf->subsection_case_sensitive = 1;\n+\tcs->subsection_case_sensitive = 1;\n \tfor (;;) {\n-\t\tint c = get_next_char();\n-\t\tif (cf->eof)\n+\t\tint c = get_next_char(cs);\n+\t\tif (cs->eof)\n \t\t\treturn -1;\n \t\tif (c == ']')\n \t\t\treturn 0;\n \t\tif (isspace(c))\n-\t\t\treturn get_extended_base_var(name, c);\n+\t\t\treturn get_extended_base_var(cs, name, c);\n \t\tif (!iskeychar(c) && c != '.')\n \t\t\treturn -1;\n \t\tstrbuf_addch(name, tolower(c));\n@@ -973,7 +979,8 @@ struct parse_event_data {\n \tconst struct config_options *opts;\n };\n \n-static int do_event(enum config_event_t type, struct parse_event_data *data)\n+static int do_event(struct config_source *cs, enum config_event_t type,\n+\t\t    struct parse_event_data *data)\n {\n \tsize_t offset;\n \n@@ -984,7 +991,7 @@ static int do_event(enum config_event_t type, struct parse_event_data *data)\n \t    data->previous_type == type)\n \t\treturn 0;\n \n-\toffset = cf->do_ftell(cf);\n+\toffset = cs->do_ftell(cs);\n \t/*\n \t * At EOF, the parser always \"inserts\" an extra '\\n', therefore\n \t * the end offset of the event is the current file position, otherwise\n@@ -1004,12 +1011,12 @@ static int do_event(enum config_event_t type, struct parse_event_data *data)\n \treturn 0;\n }\n \n-static int git_parse_source(config_fn_t fn, void *data,\n-\t\t\t    const struct config_options *opts)\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 {\n \tint comment = 0;\n \tsize_t baselen = 0;\n-\tstruct strbuf *var = &cf->var;\n+\tstruct strbuf *var = &cs->var;\n \tint error_return = 0;\n \tchar *error_msg = NULL;\n \n@@ -1024,7 +1031,7 @@ static int git_parse_source(config_fn_t fn, void *data,\n \tfor (;;) {\n \t\tint c;\n \n-\t\tc = get_next_char();\n+\t\tc = get_next_char(cs);\n \t\tif (bomptr && *bomptr) {\n \t\t\t/* We are at the file beginning; skip UTF8-encoded BOM\n \t\t\t * if present. Sane editors won't put this in on their\n@@ -1041,12 +1048,12 @@ static int git_parse_source(config_fn_t fn, void *data,\n \t\t\t}\n \t\t}\n \t\tif (c == '\\n') {\n-\t\t\tif (cf->eof) {\n-\t\t\t\tif (do_event(CONFIG_EVENT_EOF, &event_data) < 0)\n+\t\t\tif (cs->eof) {\n+\t\t\t\tif (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\t\treturn 0;\n \t\t\t}\n-\t\t\tif (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n+\t\t\tif (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n \t\t\t\treturn -1;\n \t\t\tcomment = 0;\n \t\t\tcontinue;\n@@ -1054,23 +1061,23 @@ static int git_parse_source(config_fn_t fn, void *data,\n \t\tif (comment)\n \t\t\tcontinue;\n \t\tif (isspace(c)) {\n-\t\t\tif (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n+\t\t\tif (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '#' || c == ';') {\n-\t\t\tif (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0)\n+\t\t\tif (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\tcomment = 1;\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '[') {\n-\t\t\tif (do_event(CONFIG_EVENT_SECTION, &event_data) < 0)\n+\t\t\tif (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \n \t\t\t/* Reset prior to determining a new stem */\n \t\t\tstrbuf_reset(var);\n-\t\t\tif (get_base_var(var) < 0 || var->len < 1)\n+\t\t\tif (get_base_var(cs, var) < 0 || var->len < 1)\n \t\t\t\tbreak;\n \t\t\tstrbuf_addch(var, '.');\n \t\t\tbaselen = var->len;\n@@ -1079,7 +1086,7 @@ static int git_parse_source(config_fn_t fn, void *data,\n \t\tif (!isalpha(c))\n \t\t\tbreak;\n \n-\t\tif (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0)\n+\t\tif (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)\n \t\t\treturn -1;\n \n \t\t/*\n@@ -1089,42 +1096,42 @@ static int git_parse_source(config_fn_t fn, void *data,\n \t\t */\n \t\tstrbuf_setlen(var, baselen);\n \t\tstrbuf_addch(var, tolower(c));\n-\t\tif (get_value(fn, data, var) < 0)\n+\t\tif (get_value(cs, fn, data, var) < 0)\n \t\t\tbreak;\n \t}\n \n-\tif (do_event(CONFIG_EVENT_ERROR, &event_data) < 0)\n+\tif (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)\n \t\treturn -1;\n \n-\tswitch (cf->origin_type) {\n+\tswitch (cs->origin_type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in blob %s\"),\n-\t\t\t\t      cf->linenr, cf->name);\n+\t\t\t\t      cs->linenr, cs->name);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_FILE:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in file %s\"),\n-\t\t\t\t      cf->linenr, cf->name);\n+\t\t\t\t      cs->linenr, cs->name);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_STDIN:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in standard input\"),\n-\t\t\t\t      cf->linenr);\n+\t\t\t\t      cs->linenr);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_SUBMODULE_BLOB:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in submodule-blob %s\"),\n-\t\t\t\t       cf->linenr, cf->name);\n+\t\t\t\t       cs->linenr, cs->name);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_CMDLINE:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in command line %s\"),\n-\t\t\t\t       cf->linenr, cf->name);\n+\t\t\t\t       cs->linenr, cs->name);\n \t\tbreak;\n \tdefault:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in %s\"),\n-\t\t\t\t      cf->linenr, cf->name);\n+\t\t\t\t      cs->linenr, cs->name);\n \t}\n \n \tswitch (opts && opts->error_action ?\n \t\topts->error_action :\n-\t\tcf->default_error_action) {\n+\t\tcs->default_error_action) {\n \tcase CONFIG_ERROR_DIE:\n \t\tdie(\"%s\", error_msg);\n \t\tbreak;\n@@ -1266,7 +1273,8 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)\n }\n \n NORETURN\n-static void die_bad_number(const char *name, const char *value)\n+static void die_bad_number(struct config_source *cs, const char *name,\n+\t\t\t   const char *value)\n {\n \tconst char *error_type = (errno == ERANGE) ?\n \t\tN_(\"out of range\") : N_(\"invalid unit\");\n@@ -1275,28 +1283,28 @@ static void die_bad_number(const char *name, const char *value)\n \tif (!value)\n \t\tvalue = \"\";\n \n-\tif (!(cf && cf->name))\n+\tif (!(cs && cs->name))\n \t\tdie(_(bad_numeric), value, name, _(error_type));\n \n-\tswitch (cf->origin_type) {\n+\tswitch (cs->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, cf->name, _(error_type));\n+\t\t    value, name, cs->name, _(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, cf->name, _(error_type));\n+\t\t    value, name, cs->name, _(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, cf->name, _(error_type));\n+\t\t    value, name, cs->name, _(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, cf->name, _(error_type));\n+\t\t    value, name, cs->name, _(error_type));\n \tdefault:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in %s: %s\"),\n-\t\t    value, name, cf->name, _(error_type));\n+\t\t    value, name, cs->name, _(error_type));\n \t}\n }\n \n@@ -1304,7 +1312,7 @@ int git_config_int(const char *name, const char *value)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(name, value);\n+\t\tdie_bad_number(cf, name, value);\n \treturn ret;\n }\n \n@@ -1312,7 +1320,7 @@ int64_t git_config_int64(const char *name, const char *value)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(name, value);\n+\t\tdie_bad_number(cf, name, value);\n \treturn ret;\n }\n \n@@ -1320,7 +1328,7 @@ unsigned long git_config_ulong(const char *name, const char *value)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(name, value);\n+\t\tdie_bad_number(cf, name, value);\n \treturn ret;\n }\n \n@@ -1328,7 +1336,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(name, value);\n+\t\tdie_bad_number(cf, name, value);\n \treturn ret;\n }\n \n@@ -1948,7 +1956,7 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,\n \tstrbuf_init(&top->var, 1024);\n \tcf = top;\n \n-\tret = git_parse_source(fn, data, opts);\n+\tret = git_parse_source(cf, fn, data, opts);\n \n \t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n-- \ngitgitgadget\n\n"},{"id":"472879","messageId":"751ce3e927d392530038006a3f1e3a9538ce2127.1677631097.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.git.git.1677631097.gitgitgadget@gmail.com","subject":"[PATCH 3/6] config.c: create config_reader and the_reader","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-01T00:38:14Z","receivedAt":"2023-03-01T00:38: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\nCreate \"struct config_reader\" to hold the state of the config source\ncurrently being read. Then, create a static instance of it,\n\"the_reader\", and use \"the_reader.source\" to replace references to \"cf\"\nin public functions.\n\nThis doesn't create much immediate benefit (since we're mostly replacing\nstatic variables with a bigger static variable), but it prepares us for\na future where this state doesn't have to be global; the \"struct\nconfig_reader\" could be provided by the caller, or constructed\ninternally by a function like \"do_config_from()\".\n\nA more typical approach would be to put this struct on \"the_repository\",\nbut that's a worse fit for this use case since config reading is not\nscoped to a repository. E.g. we can read config before the repository is\nknown (\"read_very_early_config()\"), blatantly ignore the repo\n(\"read_protected_config()\"), or read only from a file\n(\"git_config_from_file()\"). This is especially evident in t5318 and\nt9210, where test-tool and scalar parse config but don't fully\ninitialize \"the_repository\".\n\nWe could have also replaced the references to \"cf\" in callback functions\n(which are the only ones left), but we'll eventually plumb \"the_reader\"\nthrough the callback \"*data\" arg, which will allow us to rename \"cf\" to\n\"cs\" without changing line lengths. Until we remove \"cf\" altogether,\nadd logic to \"config_reader_*_source()\" to keep \"cf\" and\n\"the_reader.source\" in sync.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 80 +++++++++++++++++++++++++++++++++-----------------------\n 1 file changed, 48 insertions(+), 32 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 1f89addc771..866cd54dd40 100644\n--- a/config.c\n+++ b/config.c\n@@ -51,6 +51,12 @@ struct config_source {\n };\n #define CONFIG_SOURCE_INIT { 0 }\n \n+struct config_reader {\n+\tstruct config_source *source;\n+};\n+/* Only public functions should reference the_reader. */\n+static struct config_reader the_reader;\n+\n /*\n  * These variables record the \"current\" config source, which\n  * can be accessed by parsing callbacks.\n@@ -66,6 +72,9 @@ struct config_source {\n  * at the variables, it's either a bug for it to be called in the first place,\n  * or it's a function which can be reused for non-config purposes, and should\n  * fall back to some sane behavior).\n+ *\n+ * FIXME \"cf\" has been replaced by \"the_reader.source\", remove\n+ * \"cf\" once we plumb \"the_reader\" through all of the callback functions.\n  */\n static struct config_source *cf;\n static struct key_value_info *current_config_kvi;\n@@ -79,20 +88,25 @@ static struct key_value_info *current_config_kvi;\n  */\n static enum config_scope current_parsing_scope;\n \n-static inline void config_state_push_source(struct config_source *top)\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 (cf)\n-\t\ttop->prev = cf;\n-\tcf = top;\n+\tif (reader->source)\n+\t\ttop->prev = reader->source;\n+\treader->source = top;\n+\t/* FIXME remove this when cf is removed. */\n+\tcf = reader->source;\n }\n \n-static inline struct config_source *config_state_pop_source()\n+static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n {\n \tstruct config_source *ret;\n-\tif (!cf)\n+\tif (!reader->source)\n \t\tBUG(\"tried to pop config source, but we weren't reading config\");\n-\tret = cf;\n-\tcf = cf->prev;\n+\tret = reader->source;\n+\treader->source = reader->source->prev;\n+\t/* FIXME remove this when cf is removed. */\n+\tcf = reader->source;\n \treturn ret;\n }\n \n@@ -732,7 +746,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tstruct config_source source = CONFIG_SOURCE_INIT;\n \n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n-\tconfig_state_push_source(&source);\n+\tconfig_reader_push_source(&the_reader, &source);\n \n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n@@ -790,7 +804,7 @@ out:\n \tstrbuf_release(&envvar);\n \tstrvec_clear(&to_free);\n \tfree(envw);\n-\tconfig_state_pop_source();\n+\tconfig_reader_pop_source(&the_reader);\n \treturn ret;\n }\n \n@@ -1325,7 +1339,7 @@ int git_config_int(const char *name, const char *value)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(cf, name, value);\n+\t\tdie_bad_number(the_reader.source, name, value);\n \treturn ret;\n }\n \n@@ -1333,7 +1347,7 @@ int64_t git_config_int64(const char *name, const char *value)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(cf, name, value);\n+\t\tdie_bad_number(the_reader.source, name, value);\n \treturn ret;\n }\n \n@@ -1341,7 +1355,7 @@ unsigned long git_config_ulong(const char *name, const char *value)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(cf, name, value);\n+\t\tdie_bad_number(the_reader.source, name, value);\n \treturn ret;\n }\n \n@@ -1349,7 +1363,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(cf, name, value);\n+\t\tdie_bad_number(the_reader.source, name, value);\n \treturn ret;\n }\n \n@@ -1955,7 +1969,8 @@ 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_source *top, config_fn_t fn, void *data,\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  const struct config_options *opts)\n {\n \tint ret;\n@@ -1966,22 +1981,23 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,\n \ttop->total_len = 0;\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n-\tconfig_state_push_source(top);\n+\tconfig_reader_push_source(reader, top);\n \n \tret = git_parse_source(top, fn, data, opts);\n \n \t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n \tstrbuf_release(&top->var);\n-\tconfig_state_pop_source();\n+\tconfig_reader_pop_source(reader);\n \n \treturn ret;\n }\n \n-static int do_config_from_file(config_fn_t fn,\n-\t\tconst enum config_origin_type origin_type,\n-\t\tconst char *name, const char *path, FILE *f,\n-\t\tvoid *data, const struct config_options *opts)\n+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 {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n@@ -1996,15 +2012,15 @@ static int do_config_from_file(config_fn_t fn,\n \ttop.do_ftell = config_file_ftell;\n \n \tflockfile(f);\n-\tret = do_config_from(&top, fn, data, opts);\n+\tret = do_config_from(reader, &top, fn, data, opts);\n \tfunlockfile(f);\n \treturn ret;\n }\n \n static int git_config_from_stdin(config_fn_t fn, void *data)\n {\n-\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n-\t\t\t\t   data, NULL);\n+\treturn do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, \"\",\n+\t\t\t\t   NULL, stdin, data, NULL);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2018,8 +2034,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(fn, CONFIG_ORIGIN_FILE, filename,\n-\t\t\t\t\t  filename, f, data, opts);\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\tfclose(f);\n \t}\n \treturn ret;\n@@ -2048,7 +2064,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(&top, fn, data, opts);\n+\treturn do_config_from(&the_reader, &top, fn, data, opts);\n }\n \n int git_config_from_blob_oid(config_fn_t fn,\n@@ -3791,8 +3807,8 @@ const char *current_config_origin_type(void)\n \tint type;\n \tif (current_config_kvi)\n \t\ttype = current_config_kvi->origin_type;\n-\telse if(cf)\n-\t\ttype = cf->origin_type;\n+\telse if(the_reader.source)\n+\t\ttype = the_reader.source->origin_type;\n \telse\n \t\tBUG(\"current_config_origin_type called outside config callback\");\n \n@@ -3837,8 +3853,8 @@ const char *current_config_name(void)\n \tconst char *name;\n \tif (current_config_kvi)\n \t\tname = current_config_kvi->filename;\n-\telse if (cf)\n-\t\tname = cf->name;\n+\telse if (the_reader.source)\n+\t\tname = the_reader.source->name;\n \telse\n \t\tBUG(\"current_config_name called outside config callback\");\n \treturn name ? name : \"\";\n@@ -3857,7 +3873,7 @@ int current_config_line(void)\n \tif (current_config_kvi)\n \t\treturn current_config_kvi->linenr;\n \telse\n-\t\treturn cf->linenr;\n+\t\treturn the_reader.source->linenr;\n }\n \n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n-- \ngitgitgadget\n\n"},{"id":"472880","messageId":"c05b33ab29dbce931f9d0ad968fe9eeedc653456.1677631097.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.git.git.1677631097.gitgitgadget@gmail.com","subject":"[PATCH 5/6] config.c: remove current_config_kvi","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-01T00:38:16Z","receivedAt":"2023-03-01T00:38: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\nAdd \".config_kvi\" to \"struct config_reader\" and replace\n\"current_config_kvi\" with \"the_reader.config_kvi\", either in-place (in\npublic functions) or by passing \"the_reader\" to the \"*data\" arg of\ncallback functions.\n\nAlso, introduce a setter function for \".config_kvi\", which allows us to\nenforce the contraint that only one of \".source\" and \".config_kvi\" can\nbe set at a time (as documented in the comments). Because of this\nconstraint, we know that \"populate_remote_urls()\" was never touching\n\"current_config_kvi\" when iterating through config files, so it doesn't\nneed to store and restore that value.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 103 ++++++++++++++++++++++++++++++++-----------------------\n 1 file changed, 61 insertions(+), 42 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 9676734b1b7..c7995148165 100644\n--- a/config.c\n+++ b/config.c\n@@ -52,32 +52,28 @@ 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 /* Only public functions should reference the_reader. */\n static struct config_reader the_reader;\n \n-/*\n- * FIXME The comments are temporarily out of date since \"cf\" been moved to\n- * the_reader, but not current_*.\n- *\n- * These variables record the \"current\" config source, which\n- * can be accessed by parsing callbacks.\n- *\n- * The \"cf\" variable will be non-NULL only when we are actually parsing a real\n- * config source (file, blob, cmdline, etc).\n- *\n- * The \"current_config_kvi\" variable will be non-NULL only when we are feeding\n- * cached config from a configset into a callback.\n- *\n- * They should generally never be non-NULL at the same time. If they are both\n- * NULL, then we aren't parsing anything (and depending on the function looking\n- * at the variables, it's either a bug for it to be called in the first place,\n- * or it's a function which can be reused for non-config purposes, and should\n- * fall back to some sane behavior).\n- */\n-static struct key_value_info *current_config_kvi;\n-\n /*\n  * Similar to the variables above, this gives access to the \"scope\" of the\n  * current value (repo, global, etc). For cached values, it can be found via\n@@ -90,6 +86,8 @@ static enum config_scope current_parsing_scope;\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 only be set when parsing a config source\");\n \tif (reader->source)\n \t\ttop->prev = reader->source;\n \treader->source = top;\n@@ -105,6 +103,14 @@ 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+\tif (kvi && reader->source)\n+\t\tBUG(\"kvi should only be set when iterating through configset\");\n+\treader->config_kvi = kvi;\n+}\n+\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -373,20 +379,17 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tstruct key_value_info *store_kvi = current_config_kvi;\n \tenum config_scope store_scope = current_parsing_scope;\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tcurrent_config_kvi = NULL;\n \tcurrent_parsing_scope = 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-\tcurrent_config_kvi = store_kvi;\n \tcurrent_parsing_scope = store_scope;\n }\n \n@@ -2253,26 +2256,34 @@ int config_with_options(config_fn_t fn, void *data,\n \treturn ret;\n }\n \n+struct configset_iter_data {\n+\tstruct config_reader *config_reader;\n+\tvoid *inner;\n+};\n+#define CONFIGSET_ITER_INIT { 0 }\n+\n static void configset_iter(struct config_set *cs, 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 = &cs->list;\n+\tstruct configset_iter_data *iter_data = data;\n \n \tfor (i = 0; i < list->nr; i++) {\n+\t\tstruct key_value_info *kvi;\n \t\tentry = list->items[i].e;\n \t\tvalue_index = list->items[i].value_index;\n \t\tvalues = &entry->value_list;\n \n-\t\tcurrent_config_kvi = values->items[value_index].util;\n+\t\tkvi = values->items[value_index].util;\n+\t\tconfig_reader_set_kvi(iter_data->config_reader, kvi);\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      current_config_kvi->filename,\n-\t\t\t\t\t      current_config_kvi->linenr);\n+\t\tif (fn(entry->key, values->items[value_index].string, iter_data->inner) < 0)\n+\t\t\tgit_die_config_linenr(entry->key, kvi->filename,\n+\t\t\t\t\t      kvi->linenr);\n \n-\t\tcurrent_config_kvi = NULL;\n+\t\tconfig_reader_set_kvi(iter_data->config_reader, NULL);\n \t}\n }\n \n@@ -2607,10 +2618,14 @@ static void repo_config_clear(struct repository *repo)\n \tgit_configset_clear(repo->config);\n }\n \n-void repo_config(struct repository *repo, config_fn_t fn, void *data)\n+void repo_config(struct repository *repo, config_fn_t fn, void *data_inner)\n {\n+\tstruct configset_iter_data data = CONFIGSET_ITER_INIT;\n+\tdata.inner = data_inner;\n+\tdata.config_reader = &the_reader;\n+\n \tgit_config_check_init(repo);\n-\tconfigset_iter(repo->config, fn, data);\n+\tconfigset_iter(repo->config, fn, &data);\n }\n \n int repo_config_get_value(struct repository *repo,\n@@ -2712,11 +2727,15 @@ static void read_protected_config(void)\n \tconfig_with_options(config_set_callback, &data, NULL, &opts);\n }\n \n-void git_protected_config(config_fn_t fn, void *data)\n+void git_protected_config(config_fn_t fn, void *data_inner)\n {\n+\tstruct configset_iter_data data = CONFIGSET_ITER_INIT;\n \tif (!protected_config.hash_initialized)\n \t\tread_protected_config();\n-\tconfigset_iter(&protected_config, fn, data);\n+\tdata.inner = data_inner;\n+\tdata.config_reader = &the_reader;\n+\n+\tconfigset_iter(&protected_config, fn, &data);\n }\n \n /* Functions used historically to read configuration from 'the_repository' */\n@@ -3823,8 +3842,8 @@ int parse_config_key(const char *var,\n const char *current_config_origin_type(void)\n {\n \tint type;\n-\tif (current_config_kvi)\n-\t\ttype = current_config_kvi->origin_type;\n+\tif (the_reader.config_kvi)\n+\t\ttype = the_reader.config_kvi->origin_type;\n \telse if(the_reader.source)\n \t\ttype = the_reader.source->origin_type;\n \telse\n@@ -3869,8 +3888,8 @@ const char *config_scope_name(enum config_scope scope)\n const char *current_config_name(void)\n {\n \tconst char *name;\n-\tif (current_config_kvi)\n-\t\tname = current_config_kvi->filename;\n+\tif (the_reader.config_kvi)\n+\t\tname = the_reader.config_kvi->filename;\n \telse if (the_reader.source)\n \t\tname = the_reader.source->name;\n \telse\n@@ -3880,16 +3899,16 @@ const char *current_config_name(void)\n \n enum config_scope current_config_scope(void)\n {\n-\tif (current_config_kvi)\n-\t\treturn current_config_kvi->scope;\n+\tif (the_reader.config_kvi)\n+\t\treturn the_reader.config_kvi->scope;\n \telse\n \t\treturn current_parsing_scope;\n }\n \n int current_config_line(void)\n {\n-\tif (current_config_kvi)\n-\t\treturn current_config_kvi->linenr;\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-- \ngitgitgadget\n\n"},{"id":"472881","messageId":"74a63fed7054da8049d4a32ecdb582726368c5a8.1677631097.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.git.git.1677631097.gitgitgadget@gmail.com","subject":"[PATCH 4/6] config.c: plumb the_reader through callbacks","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-01T00:38:15Z","receivedAt":"2023-03-01T00:38: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\nThe remaining references to \"cf\" are in config callback functions.\nRemove them by plumbing \"struct config_reader\" via the \"*data\" arg.\n\n**RFC NOTE** If we had a way to expose \"struct config_reader\" to the\nconfig callback functions (the 'extra work' in the cover letter), we\nwouldn't need to also pass it via the \"*data\" arg. This is more of a\nhack to avoid doing that work now.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 78 ++++++++++++++++++++++++++++++++++----------------------\n 1 file changed, 48 insertions(+), 30 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 866cd54dd40..9676734b1b7 100644\n--- a/config.c\n+++ b/config.c\n@@ -58,6 +58,9 @@ struct config_reader {\n static struct config_reader the_reader;\n \n /*\n+ * FIXME The comments are temporarily out of date since \"cf\" been moved to\n+ * the_reader, but not current_*.\n+ *\n  * These variables record the \"current\" config source, which\n  * can be accessed by parsing callbacks.\n  *\n@@ -72,11 +75,7 @@ static struct config_reader the_reader;\n  * at the variables, it's either a bug for it to be called in the first place,\n  * or it's a function which can be reused for non-config purposes, and should\n  * fall back to some sane behavior).\n- *\n- * FIXME \"cf\" has been replaced by \"the_reader.source\", remove\n- * \"cf\" once we plumb \"the_reader\" through all of the callback functions.\n  */\n-static struct config_source *cf;\n static struct key_value_info *current_config_kvi;\n \n /*\n@@ -94,8 +93,6 @@ static inline void config_reader_push_source(struct config_reader *reader,\n \tif (reader->source)\n \t\ttop->prev = reader->source;\n \treader->source = top;\n-\t/* FIXME remove this when cf is removed. */\n-\tcf = reader->source;\n }\n \n static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n@@ -105,8 +102,6 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\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-\t/* FIXME remove this when cf is removed. */\n-\tcf = reader->source;\n \treturn ret;\n }\n \n@@ -171,6 +166,7 @@ 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@@ -461,6 +457,7 @@ static int include_condition_is_true(struct config_source *cs,\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 \tconst char *cond, *key;\n \tsize_t cond_len;\n \tint ret;\n@@ -474,16 +471,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(cf, value, inc);\n+\t\tret = handle_path_include(cs, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n-\t    cond && include_condition_is_true(cf, inc, cond, cond_len) &&\n+\t    cond && include_condition_is_true(cs, 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(cf, value, inc);\n+\t\tret = handle_path_include(cs, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -2224,6 +2221,7 @@ 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@@ -2344,7 +2342,9 @@ static struct config_set_element *configset_find_element(struct config_set *cs,\n \treturn found_entry;\n }\n \n-static int configset_add_value(struct config_set *cs, const char *key, const char *value)\n+static int configset_add_value(struct config_reader *reader,\n+\t\t\t       struct config_set *cs, const char *key,\n+\t\t\t       const char *value)\n {\n \tstruct config_set_element *e;\n \tstruct string_list_item *si;\n@@ -2370,12 +2370,12 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (!cf)\n+\tif (!reader->source)\n \t\tBUG(\"configset_add_value has no source\");\n-\tif (cf->name) {\n-\t\tkv_info->filename = strintern(cf->name);\n-\t\tkv_info->linenr = cf->linenr;\n-\t\tkv_info->origin_type = cf->origin_type;\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} else {\n \t\t/* for values read from `git_config_from_parameters()` */\n \t\tkv_info->filename = NULL;\n@@ -2430,16 +2430,25 @@ void git_configset_clear(struct config_set *cs)\n \tcs->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, void *cb)\n {\n-\tstruct config_set *cs = cb;\n-\tconfigset_add_value(cs, key, value);\n+\tstruct configset_add_data *data = cb;\n+\tconfigset_add_value(data->config_reader, data->config_set, key, value);\n \treturn 0;\n }\n \n int git_configset_add_file(struct config_set *cs, const char *filename)\n {\n-\treturn git_config_from_file(config_set_callback, filename, cs);\n+\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n+\tdata.config_reader = &the_reader;\n+\tdata.config_set = cs;\n+\treturn git_config_from_file(config_set_callback, filename, &data);\n }\n \n int git_configset_get_value(struct config_set *cs, const char *key, const char **value)\n@@ -2554,6 +2563,7 @@ int git_configset_get_pathname(struct config_set *cs, const char *key, const cha\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@@ -2565,8 +2575,10 @@ 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, repo->config, NULL, &opts) < 0)\n+\tif (config_with_options(config_set_callback, &data, 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@@ -2692,9 +2704,12 @@ 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-\tconfig_with_options(config_set_callback, &protected_config,\n-\t\t\t    NULL, &opts);\n+\tdata.config_set = &protected_config;\n+\tdata.config_reader = &the_reader;\n+\tconfig_with_options(config_set_callback, &data, NULL, &opts);\n }\n \n void git_protected_config(config_fn_t fn, void *data)\n@@ -2879,6 +2894,7 @@ 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@@ -2893,6 +2909,7 @@ struct config_store_data {\n \tunsigned int parsed_nr, parsed_alloc, *seen, seen_nr, seen_alloc;\n \tunsigned int key_seen:1, section_seen:1, is_keys_section:1;\n };\n+#define CONFIG_STORE_INIT { 0 }\n \n static void config_store_data_clear(struct config_store_data *store)\n {\n@@ -2927,6 +2944,7 @@ static int store_aux_event(enum config_event_t type,\n \t\t\t   size_t begin, size_t end, 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@@ -2936,10 +2954,10 @@ static int store_aux_event(enum config_event_t type,\n \tif (type == CONFIG_EVENT_SECTION) {\n \t\tint (*cmpfn)(const char *, const char *, size_t);\n \n-\t\tif (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')\n-\t\t\treturn error(_(\"invalid section name '%s'\"), cf->var.buf);\n+\t\tif (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')\n+\t\t\treturn error(_(\"invalid section name '%s'\"), cs->var.buf);\n \n-\t\tif (cf->subsection_case_sensitive)\n+\t\tif (cs->subsection_case_sensitive)\n \t\t\tcmpfn = strncasecmp;\n \t\telse\n \t\t\tcmpfn = strncmp;\n@@ -2947,8 +2965,8 @@ static int store_aux_event(enum config_event_t type,\n \t\t/* Is this the section we were looking for? */\n \t\tstore->is_keys_section =\n \t\t\tstore->parsed[store->parsed_nr].is_keys_section =\n-\t\t\tcf->var.len - 1 == store->baselen &&\n-\t\t\t!cmpfn(cf->var.buf, store->key, store->baselen);\n+\t\t\tcs->var.len - 1 == store->baselen &&\n+\t\t\t!cmpfn(cs->var.buf, store->key, store->baselen);\n \t\tif (store->is_keys_section) {\n \t\t\tstore->section_seen = 1;\n \t\t\tALLOC_GROW(store->seen, store->seen_nr + 1,\n@@ -3244,9 +3262,9 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \tchar *filename_buf = NULL;\n \tchar *contents = NULL;\n \tsize_t contents_sz;\n-\tstruct config_store_data store;\n+\tstruct config_store_data store = CONFIG_STORE_INIT;\n \n-\tmemset(&store, 0, sizeof(store));\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-- \ngitgitgadget\n\n"},{"id":"472882","messageId":"b1e866f92161a38e255d0d6a6a4640dea880ebe4.1677631097.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.git.git.1677631097.gitgitgadget@gmail.com","subject":"[PATCH 6/6] config.c: remove current_parsing_scope","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-01T00:38:17Z","receivedAt":"2023-03-01T00:38: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\nAdd \".parsing_scope\" to \"struct config_reader\" and replace\n\"current_parsing_scope\" with \"the_reader.parsing_scope. Adjust the\ncomment slightly to make it clearer that the scope applies to the config\nsource (not the current value), and should only be set when parsing a\nconfig source.\n\nAs such, \".parsing_scope\" (only set when parsing config sources) and\n\".config_kvi\" (only set when iterating a config set) should not be\nset together, so enforce this with a setter function.\n\nUnlike previous commits, \"populate_remote_urls()\" still needs to store\nand restore the 'scope' value because it could have touched\n\"current_parsing_scope\" (\"config_with_options()\" can set the scope).\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 62 +++++++++++++++++++++++++++++++++-----------------------\n 1 file changed, 37 insertions(+), 25 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex c7995148165..19bab84c47f 100644\n--- a/config.c\n+++ b/config.c\n@@ -70,19 +70,20 @@ 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 /* Only public functions should reference the_reader. */\n static struct config_reader the_reader;\n \n-/*\n- * Similar to the variables above, this gives access to the \"scope\" of the\n- * current value (repo, global, etc). For cached values, it can be found via\n- * the current_config_kvi as above. During parsing, the current value can be\n- * found in this variable. It's not part of \"cf\" because it transcends a single\n- * file (i.e., a file included from .git/config is still in \"repo\" scope).\n- */\n-static enum config_scope current_parsing_scope;\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@@ -106,11 +107,19 @@ 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)\n+\tif (kvi && (reader->source || reader->parsing_scope))\n \t\tBUG(\"kvi should only be set when iterating through configset\");\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@@ -379,18 +388,18 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tenum config_scope store_scope = current_parsing_scope;\n+\tenum config_scope store_scope = inc->config_reader->parsing_scope;\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tcurrent_parsing_scope = 0;\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-\tcurrent_parsing_scope = store_scope;\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@@ -2155,7 +2164,8 @@ int git_config_system(void)\n \treturn !git_env_bool(\"GIT_CONFIG_NOSYSTEM\", 0);\n }\n \n-static int do_git_config_sequence(const struct config_options *opts,\n+static int do_git_config_sequence(struct config_reader *reader,\n+\t\t\t\t  const struct config_options *opts,\n \t\t\t\t  config_fn_t fn, void *data)\n {\n \tint ret = 0;\n@@ -2163,7 +2173,7 @@ static int do_git_config_sequence(const struct config_options *opts,\n \tchar *xdg_config = NULL;\n \tchar *user_config = NULL;\n \tchar *repo_config;\n-\tenum config_scope prev_parsing_scope = current_parsing_scope;\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@@ -2172,13 +2182,13 @@ static int do_git_config_sequence(const struct config_options *opts,\n \telse\n \t\trepo_config = NULL;\n \n-\tcurrent_parsing_scope = CONFIG_SCOPE_SYSTEM;\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 \n-\tcurrent_parsing_scope = 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@@ -2187,12 +2197,12 @@ static int do_git_config_sequence(const struct config_options *opts,\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 \n-\tcurrent_parsing_scope = 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 \n-\tcurrent_parsing_scope = 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@@ -2200,11 +2210,11 @@ static int do_git_config_sequence(const struct config_options *opts,\n \t\tfree(path);\n \t}\n \n-\tcurrent_parsing_scope = CONFIG_SCOPE_COMMAND;\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-\tcurrent_parsing_scope = prev_parsing_scope;\n+\tconfig_reader_set_scope(reader, prev_parsing_scope);\n \tfree(system_config);\n \tfree(xdg_config);\n \tfree(user_config);\n@@ -2217,6 +2227,7 @@ 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@@ -2230,7 +2241,7 @@ int config_with_options(config_fn_t fn, void *data,\n \t}\n \n \tif (config_source)\n-\t\tcurrent_parsing_scope = config_source->scope;\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@@ -2246,13 +2257,14 @@ 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\tdata);\n \t} else {\n-\t\tret = do_git_config_sequence(opts, fn, data);\n+\t\tret = do_git_config_sequence(&the_reader, 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@@ -2393,7 +2405,7 @@ static int configset_add_value(struct config_reader *reader,\n \t\tkv_info->linenr = -1;\n \t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n \t}\n-\tkv_info->scope = current_parsing_scope;\n+\tkv_info->scope = reader->parsing_scope;\n \tsi->util = kv_info;\n \n \treturn 0;\n@@ -3902,7 +3914,7 @@ 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 current_parsing_scope;\n+\t\treturn the_reader.parsing_scope;\n }\n \n int current_config_line(void)\n-- \ngitgitgadget\n"},{"id":"472965","messageId":"xmqqh6v1g1d3.fsf@gitster.g","threadId":"59327","inReplyTo":"ad513d832d8267f9e4805db5e7a9e8915fc62a23.1677631097.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/6] config.c: plumb config_source through static fns","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-03T18:02:00Z","receivedAt":"2023-03-03T18:02:08Z","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 handle_path_include(const char *path, struct config_include_data *inc)\n> +static int handle_path_include(struct config_source *cs, const char *path,\n> +\t\t\t       struct config_include_data *inc)\n\nBecause handle_path_include() has no remaining reference to cf after\nthis patch, it may make sense to name the parameter \"cf\", instead of\n\"cs\", taking advantage of the fact that it will cover/hide the global.\n\n> -static int prepare_include_condition_pattern(struct strbuf *pat)\n> +static int prepare_include_condition_pattern(struct config_source *cs,\n> +\t\t\t\t\t     struct strbuf *pat)\n\nDitto.\n\n> -static int include_by_gitdir(const struct config_options *opts,\n> +static int include_by_gitdir(struct config_source *cs,\n> +\t\t\t     const struct config_options *opts,\n>  \t\t\t     const char *cond, size_t cond_len, int icase)\n\nDitto.\n\n> +static int include_condition_is_true(struct config_source *cs,\n> +\t\t\t\t     struct config_include_data *inc,\n>  \t\t\t\t     const char *cond, size_t cond_len)\n\nDitto.\n\n> @@ -441,16 +445,16 @@ static int git_config_include(const char *var, const char *value, void *data)\n\nAdding a member to the callback data struct to pass cf around would\nbe the natural next step, I presume.  I wonder if that makes the\nresult too big if it is done in this same commit.  I suspect that it\nwould be easier to grok the whole picture if it were done in the\nsame commit, though.\n\nIf not (IOW, if we deliberately leave some use of the global in the\ncallchains unfixed with this step), it may make the resulting patch\nmuch easier to read if you (1) rename the global to a longer name\nthat stands out more, e.g. cf_global, and (2) add a new parameter\n'cf' to these helper functions and pass 'cf_global' through to the\ncallchain.\n\n> -static int get_next_char(void)\n> +static int get_next_char(struct config_source *cs)\n\nDitto for \"cs\" -> \"cf\".\n\n> -static char *parse_value(void)\n> +static char *parse_value(struct config_source *cs)\n\nDitto.\n\n> -static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n> +static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n> +\t\t     struct strbuf *name)\n\nDitto.\n\n> -static int get_extended_base_var(struct strbuf *name, int c)\n> +static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n> +\t\t\t\t int c)\n\nDitto.\n\n> -static int get_base_var(struct strbuf *name)\n> +static int get_base_var(struct config_source *cs, struct strbuf *name)\n\nDitto.\n\n> -static int do_event(enum config_event_t type, struct parse_event_data *data)\n> +static int do_event(struct config_source *cs, enum config_event_t type,\n> +\t\t    struct parse_event_data *data)\n\nDitto.\n\n> -static int git_parse_source(config_fn_t fn, void *data,\n> -\t\t\t    const struct config_options *opts)\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>  {\n\nDitto.\n\n> -static void die_bad_number(const char *name, const char *value)\n> +static void die_bad_number(struct config_source *cs, const char *name,\n> +\t\t\t   const char *value)\n\nDitto.\n\n> @@ -1304,7 +1312,7 @@ int git_config_int(const char *name, const char *value)\n>  {\n>  \tint ret;\n>  \tif (!git_parse_int(value, &ret))\n> -\t\tdie_bad_number(name, value);\n> +\t\tdie_bad_number(cf, name, value);\n\nAnd using a more visible name like cf_global will leave us a\nreminder here what is remaining to be converted, like this place and\nthe callback function driven by config_with_options().\n\n\n"},{"id":"472966","messageId":"xmqq8rgdg17x.fsf@gitster.g","threadId":"59327","inReplyTo":"751ce3e927d392530038006a3f1e3a9538ce2127.1677631097.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 3/6] config.c: create config_reader and the_reader","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-03T18:05:06Z","receivedAt":"2023-03-03T18:05:16Z","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> A more typical approach would be to put this struct on \"the_repository\",\n> but that's a worse fit for this use case since config reading is not\n> scoped to a repository.\n\nGood thinking.\n\n> We could have also replaced the references to \"cf\" in callback functions\n> (which are the only ones left), but we'll eventually plumb \"the_reader\"\n> through the callback \"*data\" arg,...\n\nGood.\n"},{"id":"473080","messageId":"20230306195756.3399115-1-jonathantanmy@google.com","threadId":"59327","inReplyTo":"pull.1463.git.git.1677631097.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-03-06T19:57:56Z","receivedAt":"2023-03-06T19:59:17Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>  * We could add \"struct config_reader\" to \"config_fn_t\", i.e.\n>    \n>    -typedef int (*config_fn_t)(const char *var, const char *val, void\n>    *data); +typedef int (*config_fn_t)(const struct config_reader *reader,\n>    const char *var, const char *val, void *data);\n>    \n>    which isn't complex at all, except that there are ~100 config_fn_t\n>    implementations [3] and a good number of them may never reference\n>    \"reader\". If the churn is tolerable, I think this a good way forward.\n> \n>  * We could create a new kind of \"config_fn_t\" that accepts \"struct\n>    config_reader\", e.g.\n>    \n>    typedef int (*config_fn_t)(const char *var, const char *val, void *data);\n>    +typedef int (*config_state_fn_t)(const struct config_reader *reader,\n>    const char *var, const char *val, void *data);\n>    \n>    and only adjust the callers that would actually reference \"reader\". This\n>    is less churn, but I couldn't find a great way to do this kind of\n>    'switching between config callback types' elegantly.\n\nTo reduce churn, one thing that could be done alongside is to convert\nconfig-using code (which is...practically the rest of Git) to start\nusing the configset interface (we seem to be using configsets internally\nanyway, as evidenced by repo_config()). That way, we would reduce the\nnumber of implementations of config_fn_t.\n\n>  * We could smuggle \"struct config_reader\" to callback functions in a way\n>    that interested callers could see it, but uninterested callers could\n>    ignore. One trick that Jonathan Tan came up with (though not necessarily\n>    endorsed) would be to allocate a struct for the config value + \"struct\n>    config_reader\", then, interested callers could use \"offset_of\" to recover\n>    the \"struct config_reader\". It's a little hacky, but it's low-churn.\n\nIndeed, although we should probably use this as a last resort.\n\n> = Questions\n> \n>  * Is this worth merging without the extra work? There are some cleanups in\n>    this series that could make it valuable, but there are also some hacks\n>    (see 4/6) that aren't so great.\n\nI'm leaning towards merging it now, but can go either way, since the\ncost of churn is limited to one single file, but so are the benefits.\nIf it was up to me to decide, I would merge it now, because this opens\nup a lot of work that other contributors could individually do (in\nparticular, converting individual config code paths so that we don't\nneed to reference the_reader as a global anymore).\n\nI don't see 4/6 as a hack. It is true that the nature of the config_fn_t\ncallback could change so that passing the reader would end up being\ndone in yet another different way, but firstly, I don't think that will\nhappen for quite some time, and secondly, it might not happen at all\n(right now, I think what's most likely to happen is that the rest of the\nGit code moves to configsets and only a fraction of the Git code would\nneed to do low-level parsing, which would not have a problem passing the\nreader through the data object since they would probably need to pass\nother context anyway).\n\n>  * Is the extra work even worth it?\n\nDepends on which extra work, but I think that eliminating the the_reader\nglobal would really be useful (and, as far as I know, the whole reason\nfor this effort). From the Git codebase's perspective, doing this would\n(as far as I know) eliminate the need for pushing and popping cf, and\nmake multithreaded multi-repo operations less error-prone (e.g. we\ncan spawn a thread operating on a submodule and that thread can itself\nread the configs of nested submodules without worrying about clobbering\nglobal state...well, there is thread-local storage, but as far as I know\nthis is not portable).\n\n>  * Do any of the ideas seem more promising than the others? Are there other\n>    ideas I'm missing?\n\nHopefully I answered this in my answers to the other questions.\n"},{"id":"473081","messageId":"20230306201218.2834756-1-calvinwan@google.com","threadId":"59327","inReplyTo":"c05b33ab29dbce931f9d0ad968fe9eeedc653456.1677631097.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 5/6] config.c: remove current_config_kvi","fromName":"Calvin Wan","fromEmail":"calvinwan@google.com","sentAt":"2023-03-06T20:12:18Z","receivedAt":"2023-03-06T20:13:00Z","isPatch":true,"sender":{"key":"calvinwan@google.com","avatar":"https://avatars.githubusercontent.com/u/92547554?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> From: Glen Choo <chooglen@google.com>\n> \n> Add \".config_kvi\" to \"struct config_reader\" and replace\n> \"current_config_kvi\" with \"the_reader.config_kvi\", either in-place (in\n> public functions) or by passing \"the_reader\" to the \"*data\" arg of\n> callback functions.\n> \n> Also, introduce a setter function for \".config_kvi\", which allows us to\n> enforce the contraint that only one of \".source\" and \".config_kvi\" can\n> be set at a time (as documented in the comments). Because of this\n> constraint, we know that \"populate_remote_urls()\" was never touching\n> \"current_config_kvi\" when iterating through config files, so it doesn't\n> need to store and restore that value.\n> \n> Signed-off-by: Glen Choo <chooglen@google.com>\n> ---\n>  config.c | 103 ++++++++++++++++++++++++++++++++-----------------------\n>  1 file changed, 61 insertions(+), 42 deletions(-)\n> \n> diff --git a/config.c b/config.c\n> index 9676734b1b7..c7995148165 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -52,32 +52,28 @@ 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>  /* Only public functions should reference the_reader. */\n>  static struct config_reader the_reader;\n>  \n> -/*\n> - * FIXME The comments are temporarily out of date since \"cf\" been moved to\n> - * the_reader, but not current_*.\n> - *\n> - * These variables record the \"current\" config source, which\n> - * can be accessed by parsing callbacks.\n> - *\n> - * The \"cf\" variable will be non-NULL only when we are actually parsing a real\n> - * config source (file, blob, cmdline, etc).\n> - *\n> - * The \"current_config_kvi\" variable will be non-NULL only when we are feeding\n> - * cached config from a configset into a callback.\n> - *\n> - * They should generally never be non-NULL at the same time. If they are both\n> - * NULL, then we aren't parsing anything (and depending on the function looking\n> - * at the variables, it's either a bug for it to be called in the first place,\n> - * or it's a function which can be reused for non-config purposes, and should\n> - * fall back to some sane behavior).\n> - */\n> -static struct key_value_info *current_config_kvi;\n> -\n>  /*\n>   * Similar to the variables above, this gives access to the \"scope\" of the\n>   * current value (repo, global, etc). For cached values, it can be found via\n> @@ -90,6 +86,8 @@ static enum config_scope current_parsing_scope;\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 only be set when parsing a config source\");\n>  \tif (reader->source)\n>  \t\ttop->prev = reader->source;\n>  \treader->source = top;\n\nA developer who accidentally sets config_kvi probably has the right\nintention of trying to push source. I think a clearer BUG message here\n(which also doubly functions as a useful comment) would be something\nalong the lines of: \"configset should not be set when parsing a config\nsource\".\n\n> @@ -105,6 +103,14 @@ 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> +\tif (kvi && reader->source)\n> +\t\tBUG(\"kvi should only be set when iterating through configset\");\n> +\treader->config_kvi = kvi;\n> +}\n> +\n\nditto\n\n>  static int pack_compression_seen;\n>  static int zlib_compression_seen;\n>  \n> @@ -373,20 +379,17 @@ static void populate_remote_urls(struct config_include_data *inc)\n>  {\n>  \tstruct config_options opts;\n>  \n> -\tstruct key_value_info *store_kvi = current_config_kvi;\n>  \tenum config_scope store_scope = current_parsing_scope;\n>  \n>  \topts = *inc->opts;\n>  \topts.unconditional_remote_url = 1;\n>  \n> -\tcurrent_config_kvi = NULL;\n>  \tcurrent_parsing_scope = 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> -\tcurrent_config_kvi = store_kvi;\n>  \tcurrent_parsing_scope = store_scope;\n>  }\n>  \n> @@ -2253,26 +2256,34 @@ int config_with_options(config_fn_t fn, void *data,\n>  \treturn ret;\n>  }\n>  \n> +struct configset_iter_data {\n> +\tstruct config_reader *config_reader;\n> +\tvoid *inner;\n> +};\n> +#define CONFIGSET_ITER_INIT { 0 }\n> +\n>  static void configset_iter(struct config_set *cs, 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 = &cs->list;\n> +\tstruct configset_iter_data *iter_data = data;\n>  \n>  \tfor (i = 0; i < list->nr; i++) {\n> +\t\tstruct key_value_info *kvi;\n>  \t\tentry = list->items[i].e;\n>  \t\tvalue_index = list->items[i].value_index;\n>  \t\tvalues = &entry->value_list;\n>  \n> -\t\tcurrent_config_kvi = values->items[value_index].util;\n> +\t\tkvi = values->items[value_index].util;\n> +\t\tconfig_reader_set_kvi(iter_data->config_reader, kvi);\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      current_config_kvi->filename,\n> -\t\t\t\t\t      current_config_kvi->linenr);\n> +\t\tif (fn(entry->key, values->items[value_index].string, iter_data->inner) < 0)\n> +\t\t\tgit_die_config_linenr(entry->key, kvi->filename,\n> +\t\t\t\t\t      kvi->linenr);\n>  \n> -\t\tcurrent_config_kvi = NULL;\n> +\t\tconfig_reader_set_kvi(iter_data->config_reader, NULL);\n>  \t}\n>  }\n>  \n> @@ -2607,10 +2618,14 @@ static void repo_config_clear(struct repository *repo)\n>  \tgit_configset_clear(repo->config);\n>  }\n>  \n> -void repo_config(struct repository *repo, config_fn_t fn, void *data)\n> +void repo_config(struct repository *repo, config_fn_t fn, void *data_inner)\n>  {\n> +\tstruct configset_iter_data data = CONFIGSET_ITER_INIT;\n> +\tdata.inner = data_inner;\n> +\tdata.config_reader = &the_reader;\n> +\n>  \tgit_config_check_init(repo);\n> -\tconfigset_iter(repo->config, fn, data);\n> +\tconfigset_iter(repo->config, fn, &data);\n>  }\n\nI'm not sure I agree with changing *data to *data_inner. An API caller\nwould be wondering why this function has a *data_inner signature vs\n*data for other functions in config. You could instead change `struct\nconfigset_iter_data data` to `struct configset_iter_data data_outer` to\npreserve the function signature.\n\n>  \n>  int repo_config_get_value(struct repository *repo,\n> @@ -2712,11 +2727,15 @@ static void read_protected_config(void)\n>  \tconfig_with_options(config_set_callback, &data, NULL, &opts);\n>  }\n>  \n> -void git_protected_config(config_fn_t fn, void *data)\n> +void git_protected_config(config_fn_t fn, void *data_inner)\n>  {\n> +\tstruct configset_iter_data data = CONFIGSET_ITER_INIT;\n>  \tif (!protected_config.hash_initialized)\n>  \t\tread_protected_config();\n> -\tconfigset_iter(&protected_config, fn, data);\n> +\tdata.inner = data_inner;\n> +\tdata.config_reader = &the_reader;\n> +\n> +\tconfigset_iter(&protected_config, fn, &data);\n>  }\n\nditto\n\n>  \n>  /* Functions used historically to read configuration from 'the_repository' */\n> @@ -3823,8 +3842,8 @@ int parse_config_key(const char *var,\n>  const char *current_config_origin_type(void)\n>  {\n>  \tint type;\n> -\tif (current_config_kvi)\n> -\t\ttype = current_config_kvi->origin_type;\n> +\tif (the_reader.config_kvi)\n> +\t\ttype = the_reader.config_kvi->origin_type;\n>  \telse if(the_reader.source)\n>  \t\ttype = the_reader.source->origin_type;\n>  \telse\n> @@ -3869,8 +3888,8 @@ const char *config_scope_name(enum config_scope scope)\n>  const char *current_config_name(void)\n>  {\n>  \tconst char *name;\n> -\tif (current_config_kvi)\n> -\t\tname = current_config_kvi->filename;\n> +\tif (the_reader.config_kvi)\n> +\t\tname = the_reader.config_kvi->filename;\n>  \telse if (the_reader.source)\n>  \t\tname = the_reader.source->name;\n>  \telse\n> @@ -3880,16 +3899,16 @@ const char *current_config_name(void)\n>  \n>  enum config_scope current_config_scope(void)\n>  {\n> -\tif (current_config_kvi)\n> -\t\treturn current_config_kvi->scope;\n> +\tif (the_reader.config_kvi)\n> +\t\treturn the_reader.config_kvi->scope;\n>  \telse\n>  \t\treturn current_parsing_scope;\n>  }\n>  \n>  int current_config_line(void)\n>  {\n> -\tif (current_config_kvi)\n> -\t\treturn current_config_kvi->linenr;\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> gitgitgadget\n> \n> \n> \n"},{"id":"473087","messageId":"kl6l356heeqb.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"20230306195756.3399115-1-jonathantanmy@google.com","subject":"Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-06T21:45:16Z","receivedAt":"2023-03-06T21:45:22Z","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>>  * We could create a new kind of \"config_fn_t\" that accepts \"struct\n>>    config_reader\", e.g.\n>>    \n>>    typedef int (*config_fn_t)(const char *var, const char *val, void *data);\n>>    +typedef int (*config_state_fn_t)(const struct config_reader *reader,\n>>    const char *var, const char *val, void *data);\n>>    \n>>    and only adjust the callers that would actually reference \"reader\". This\n>>    is less churn, but I couldn't find a great way to do this kind of\n>>    'switching between config callback types' elegantly.\n>\n> To reduce churn, one thing that could be done alongside is to convert\n> config-using code (which is...practically the rest of Git) to start\n> using the configset interface (we seem to be using configsets internally\n> anyway, as evidenced by repo_config()). That way, we would reduce the\n> number of implementations of config_fn_t.\n\nBy configset interface, I believe you mean the O(1) lookup functions\nlike git_config_get_int() (which rely on the value being cached, but\ndon't necessarily accept \"struct config_set\" as an arg)? I think that\nmakes sense both from a performance and maintenance perspective.\n\n>> = Questions\n>> \n>>  * Is this worth merging without the extra work? There are some cleanups in\n>>    this series that could make it valuable, but there are also some hacks\n>>    (see 4/6) that aren't so great.\n> I don't see 4/6 as a hack. It is true that the nature of the config_fn_t\n> callback could change so that passing the reader would end up being\n> done in yet another different way, but firstly, I don't think that will\n> happen for quite some time, and secondly, it might not happen at all\n> (right now, I think what's most likely to happen is that the rest of the\n> Git code moves to configsets and only a fraction of the Git code would\n> need to do low-level parsing, which would not have a problem passing the\n> reader through the data object since they would probably need to pass\n> other context anyway).\n\nGiven how painful it is to change the config_fn_t signature, I think it\nis important to get as right as possible the first time. After I sent\nthis out, I thought of yet another possible config_fn_t signature\n(since most callbacks only need diagnostic information, we could pass\n\"struct key_value_info\" instead of the more privileged \"struct\nconfig_reader\"), but given how many functions we'd have to change, it\nseemed extremely difficult to even begin experimenting with this\ndifferent signature.\n\nSo I think you're right that we'd want to start by removing as many\nconfig_fn_t implementations as possible. Perhaps we'd also want to\nconsider creating new abstractions for other situations where the key is\nnot known, e.g. \"remote.<name>\" and \"branch.<name>\".\n"},{"id":"473092","messageId":"20230306223804.3414251-1-jonathantanmy@google.com","threadId":"59327","inReplyTo":"kl6l356heeqb.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-03-06T22:38:04Z","receivedAt":"2023-03-06T22:38:21Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> By configset interface, I believe you mean the O(1) lookup functions\n> like git_config_get_int() (which rely on the value being cached, but\n> don't necessarily accept \"struct config_set\" as an arg)? I think that\n> makes sense both from a performance and maintenance perspective.\n\nAh, yes. (More precisely, not the one where you call something like\nrepo_config(), passing a callback function that.)\n\n> Given how painful it is to change the config_fn_t signature, I think it\n> is important to get as right as possible the first time. After I sent\n> this out, I thought of yet another possible config_fn_t signature\n> (since most callbacks only need diagnostic information, we could pass\n> \"struct key_value_info\" instead of the more privileged \"struct\n> config_reader\"), but given how many functions we'd have to change, it\n> seemed extremely difficult to even begin experimenting with this\n> different signature.\n\nYeah, the first change is the hardest. I think passing it a single\nstruct (so, instead of key, value, data, reader, and then any future\nfields we would need) to which we can add fields later would mean that\nwe wouldn't need any changes beyond the first, though.\n"},{"id":"473118","messageId":"230307.86wn3szrzu.gmgdl@evledraar.gmail.com","threadId":"59327","inReplyTo":"pull.1463.git.git.1677631097.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-03-07T11:57:15Z","receivedAt":"2023-03-07T12:05:32Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Mar 01 2023, Glen Choo via GitGitGadget wrote:\n\n> This RFC is preparation for config.[ch] to be libified as as part of the\n> libification effort that Emily described in [1]. One of the first goals is\n> to read config from a file, but the trouble with how config.c is written\n> today is that all reading operations rely on global state, so before turning\n> that into a library, we'd want to make that state non-global.\n>\n> This series gets us about halfway there; it does enough plumbing for a\n> workable-but-kinda-ugly library interface, but with a little bit more work,\n> I think we can get rid of global state in-tree as well. That requires a fair\n> amount of work though, so I'd like to get thoughts on that before starting\n> work.\n>\n> = Description\n>\n> This series extracts the global config reading state into \"struct\n> config_reader\" and plumbs it through the config reading machinery. It's very\n> similar to how we've plumbed \"struct repository\" and other 'context objects'\n> in the past, except:\n>\n>  * The global state (named \"the_reader\") for the git process lives in a\n>    config.c static variable, and not on \"the_repository\". See 3/6 for the\n>    rationale.\n\nI agree with the overall direction, but don't think that rationale in\n3/6 is sufficient to go in this \"the_reader\" direction, as opposed to\nsticking with and extending \"the_repository\" approach.\n\nFor orthagonal reasons (getting rid of some of the API duplication) I've\nbeen carrying a patch to get rid of the \"configset\" part of the *public*\nAPI, i.e. to have API users always use the \"repo_config_*()\" or\n\"git_config_*()\" variants, that patch is at:\nhttps://github.com/avar/git/commit/0233297a359bbda43a902dd0213aacdca82faa34\n\nAll of the rationale in your 3/6 is true now, but as that patch shows\nthe reason for why we have \"the_repository\" is for the trivial reason\nthat we want to access the repo's \"config\" member.\n\nIt's a bit distasteful, but that change argues that just mocking up a\n\"struct repository\" with a \"config\" member pointing to a new configset\nis better than maintaining an entirely different API just for those\ncases where we need to parse a one-off file or whatever.\n\nI think that going in that direction neatly solves the issues you're\nnoting here and in your 3/6, i.e. we'd always have this in the \"repo\"\nobject, so we'd just stick the persistent \"reader\" variables in the\n\"struct repository\"'s \"config\" member.\n"},{"id":"473145","messageId":"kl6lfsagifpc.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"230307.86wn3szrzu.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-07T18:22:55Z","receivedAt":"2023-03-07T18:30:07Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"\nÆvar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> On Wed, Mar 01 2023, Glen Choo via GitGitGadget wrote:\n>\n>> This series extracts the global config reading state into \"struct\n>> config_reader\" and plumbs it through the config reading machinery. It's very\n>> similar to how we've plumbed \"struct repository\" and other 'context objects'\n>> in the past, except:\n>>\n>>  * The global state (named \"the_reader\") for the git process lives in a\n>>    config.c static variable, and not on \"the_repository\". See 3/6 for the\n>>    rationale.\n>\n> I agree with the overall direction, but don't think that rationale in\n> 3/6 is sufficient to go in this \"the_reader\" direction, as opposed to\n> sticking with and extending \"the_repository\" approach.\n>\n> For orthagonal reasons (getting rid of some of the API duplication) I've\n> been carrying a patch to get rid of the \"configset\" part of the *public*\n> API, i.e. to have API users always use the \"repo_config_*()\" or\n> \"git_config_*()\" variants, that patch is at:\n> https://github.com/avar/git/commit/0233297a359bbda43a902dd0213aacdca82faa34\n\nThose patches are probably worth sending, even if only as RFC. I found\nit pretty hard to draft a substantial response without effectively doing\na full review of the patch.\n\n> It's a bit distasteful, but that change argues that just mocking up a\n> \"struct repository\" with a \"config\" member pointing to a new configset\n> is better than maintaining an entirely different API just for those\n> cases where we need to parse a one-off file or whatever.\n>\n> I think that going in that direction neatly solves the issues you're\n> noting here and in your 3/6, i.e. we'd always have this in the \"repo\"\n> object, so we'd just stick the persistent \"reader\" variables in the\n> \"struct repository\"'s \"config\" member.\n\nIf I understand your proposal correctly, we would move the config\nvariables to the_repository. Then, any time a caller would like to work\nwith an individual file, it would init a new \"struct repository\" with a\nclean set of config members (using repo_init_repo_blank_config() or\nsomething) and reuse the repo_config_* API?\n\nIt is a workable solution, e.g. that approach would work around the\nfailures in test-tool and scalar that I observed. In the spirit of\nlibification, this feels like a kludge, though, since we'd be reverting\nto using \"struct repository\" for more things instead of using more\nwell-scoped interfaces. IMO a better future for the config_set API would\nbe to move it into configset.c or something, where only users who want\nthe low level API would use it and everyone else would just pretend it\ndoesn't exist. This would be a little like libgit2's organization, where\n'general config', 'config parsing' and 'in-memory config value\nrepresentations' are separate files, e.g.\n\n  https://github.com/libgit2/libgit2/blob/main/src/libgit2/config.h\n  https://github.com/libgit2/libgit2/blob/main/src/libgit2/config_parse.h\n  https://github.com/libgit2/libgit2/blob/main/src/libgit2/config_entries.h\n\nI also hesitate to put the config variables on the_repository, because\nin the long term, I think \"struct config_reader\" can and should be\npurely internal to config.c. But if we start advertising its existence\nvia the_repository, that might be an invitation to (ab)use that API and\nmake that transition harder.\n"},{"id":"473148","messageId":"230307.86jzzsz8x3.gmgdl@evledraar.gmail.com","threadId":"59327","inReplyTo":"kl6lfsagifpc.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-03-07T18:36:58Z","receivedAt":"2023-03-07T19:14:09Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Mar 07 2023, Glen Choo wrote:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>\n>> On Wed, Mar 01 2023, Glen Choo via GitGitGadget wrote:\n>>\n>>> This series extracts the global config reading state into \"struct\n>>> config_reader\" and plumbs it through the config reading machinery. It's very\n>>> similar to how we've plumbed \"struct repository\" and other 'context objects'\n>>> in the past, except:\n>>>\n>>>  * The global state (named \"the_reader\") for the git process lives in a\n>>>    config.c static variable, and not on \"the_repository\". See 3/6 for the\n>>>    rationale.\n>>\n>> I agree with the overall direction, but don't think that rationale in\n>> 3/6 is sufficient to go in this \"the_reader\" direction, as opposed to\n>> sticking with and extending \"the_repository\" approach.\n>>\n>> For orthagonal reasons (getting rid of some of the API duplication) I've\n>> been carrying a patch to get rid of the \"configset\" part of the *public*\n>> API, i.e. to have API users always use the \"repo_config_*()\" or\n>> \"git_config_*()\" variants, that patch is at:\n>> https://github.com/avar/git/commit/0233297a359bbda43a902dd0213aacdca82faa34\n>\n> Those patches are probably worth sending, even if only as RFC. I found\n> it pretty hard to draft a substantial response without effectively doing\n> a full review of the patch.\n\nYes, sorry. It's part of some changes on top of my outstanding config\nAPI changes (just re-rolled at\nhttps://lore.kernel.org/git/cover-v6-0.9-00000000000-20230307T180516Z-avarab@gmail.com/). Hopefully\nthose will land soon after the upcoming release, I'll try to submit this\n(along with related changes) soon afterwards.\n\n>> It's a bit distasteful, but that change argues that just mocking up a\n>> \"struct repository\" with a \"config\" member pointing to a new\n>> configset is better than maintaining an entirely different API just\n>> for those cases where we need to parse a one-off file or whatever.\n>>\n>> I think that going in that direction neatly solves the issues you're\n>> noting here and in your 3/6, i.e. we'd always have this in the \"repo\"\n>> object, so we'd just stick the persistent \"reader\" variables in the\n>> \"struct repository\"'s \"config\" member.\n>\n> If I understand your proposal correctly, we would move the config\n> variables to the_repository. Then, any time a caller would like to work\n> with an individual file, it would init a new \"struct repository\" with a\n> clean set of config members (using repo_init_repo_blank_config() or\n> something) and reuse the repo_config_* API?\n\nIt's certainly a hack, but so is introducing a new \"the_reader\"\nsingleton whose lifetime we need to manage seperately from\n\"the_repository\" in the common case :)\n\nI think a better argument for this is probably that if you try to change\nrepository.h so that we define \"struct repository\" thusly (The\n\"hash_algo\" field being still there due to a very common macro):\n\n\tstruct repository {\n\t        const struct git_hash_algo *hash_algo;\n\t        struct config_set *config;\n\t};\n\nAnd then try to:\n\n\tmake config.o\n\nYou'll get:\n\t\n\t$ make config.o\n\t    CC config.o\n\tconfig.c: In function ‘include_by_branch’:\n\tconfig.c:311:46: error: ‘struct repository’ has no member named ‘gitdir’\n\t  311 |         const char *refname = !the_repository->gitdir ?\n\t      |                                              ^~\n\tconfig.c: In function ‘repo_read_config’:\n\tconfig.c:2523:30: error: ‘struct repository’ has no member named ‘commondir’\n\t 2523 |         opts.commondir = repo->commondir;\n\t      |                              ^~\n\tconfig.c:2524:28: error: ‘struct repository’ has no member named ‘gitdir’\n\t 2524 |         opts.git_dir = repo->gitdir;\n\t      |                            ^~\n\tmake: *** [Makefile:2719: config.o] Error 1\n\nI.e. almost all of the config code doesn't care about the repository at\nall, it just needs the \"struct config_set\" that's in the repository\nstruct.\n\nWith the linked-to change I'm arguing that just mocking it up sucks less\nthan carrying a duplicate set of API functions just for those rare cases\nwhere we need to one-off read a config file.\n\nBut...\n\n> It is a workable solution, e.g. that approach would work around the\n> failures in test-tool and scalar that I observed. In the spirit of\n> libification, this feels like a kludge, though, since we'd be reverting\n> to using \"struct repository\" for more things instead of using more\n> well-scoped interfaces. IMO a better future for the config_set API would\n> be to move it into configset.c or something, where only users who want\n> the low level API would use it and everyone else would just pretend it\n> doesn't exist. This would be a little like libgit2's organization, where\n> 'general config', 'config parsing' and 'in-memory config value\n> representations' are separate files, e.g.\n>\n>   https://github.com/libgit2/libgit2/blob/main/src/libgit2/config.h\n>   https://github.com/libgit2/libgit2/blob/main/src/libgit2/config_parse.h\n>   https://github.com/libgit2/libgit2/blob/main/src/libgit2/config_entries.h\n>\n> I also hesitate to put the config variables on the_repository, because\n> in the long term, I think \"struct config_reader\" can and should be\n> purely internal to config.c. But if we start advertising its existence\n> via the_repository, that might be an invitation to (ab)use that API and\n> make that transition harder.\n\n...yes it's a kludge, but I'd think if you're interested in more\ngenerally fixing it that a better use of time would be to narrowly focus\non those cases where we don't have a \"repository\" now, i.e. the\nconfigset API users my linked-to patch shows & amends.\n\nEverything else that uses git_config_get_*(), repo_config_get_*() will\neither use an already set up \"the_repository\", or lazily init it, see\nthe git_config_check_init() API users.\n\nOr, we could have repo_config() and friends not take a \"struct\nrepository\", but another config-specific object which has the subset of\nthe three fields from that struct which it actually needs (config,\ngitdir & commondir).\n\nThe mocking function I added in the linked-to commit was just a way of\ngetting to that via the shortest means.\n\n"},{"id":"473149","messageId":"xmqqbkl4xsj2.fsf@gitster.g","threadId":"59327","inReplyTo":"kl6lfsagifpc.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-07T19:36:49Z","receivedAt":"2023-03-07T19:46:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> ... In the spirit of\n> libification, this feels like a kludge, though, since we'd be reverting\n> to using \"struct repository\" for more things instead of using more\n> well-scoped interfaces.\n\nIf you include \"populate from system-wide, per-user, and repository\nspecific configuration files\" as part of the API being libified,\nyour configsets cannot avoid being tied to a repository.  But I do\nnot think the reader needs to be in the repository.\n\n> IMO a better future for the config_set API would\n> be to move it into configset.c or something, where only users who want\n> the low level API would use it and everyone else would just pretend it\n> doesn't exist.\n\nIsn't the use of the reader object purely transitory while you\npopulate the keys and values in a configset from a single file?  At\nthe layer to read and populate a configset from a single \"source\"\nfile, you still do not need repository.\n\nOnly when you say \"I have a 'repo' instance and I want to read the\nconfig variables from appropriate places\", you call such a \"read and\npopulate configset from a single source\" helper three or four times\nto populate repo->config.  Once a configset is populated, it or its\ncontents do not depend on the reader instance to function, so I do\nnot see how it benefits to have the reader in the repository object.\n\n"},{"id":"473158","messageId":"kl6lcz5ki36g.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"xmqqbkl4xsj2.fsf@gitster.g","subject":"Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-07T22:53:27Z","receivedAt":"2023-03-07T22:57:50Z","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> If you include \"populate from system-wide, per-user, and repository\n> specific configuration files\" as part of the API being libified,\n> your configsets cannot avoid being tied to a repository.  But I do\n> not think the reader needs to be in the repository.\n>\n> [...]\n>\n> Isn't the use of the reader object purely transitory while you\n> populate the keys and values in a configset from a single file?  At\n> the layer to read and populate a configset from a single \"source\"\n> file, you still do not need repository.\n>\n> [...]\n> Only when you say \"I have a 'repo' instance and I want to read the\n> config variables from appropriate places\", you call such a \"read and\n> populate configset from a single source\" helper three or four times\n> to populate repo->config.  Once a configset is populated, it or its\n> contents do not depend on the reader instance to function, so I do\n> not see how it benefits to have the reader in the repository object.\n\nYes, exactly. Having a config_set on the repository makes sense, but I\ndon't see a good reason to have the reader on the repository.\n\nIf Ævar sends his series soon, it would be fruitful to see how that\nseries interacts with this one :)\n"},{"id":"473184","messageId":"230308.867cvrziac.gmgdl@evledraar.gmail.com","threadId":"59327","inReplyTo":"kl6lcz5ki36g.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-03-08T09:17:35Z","receivedAt":"2023-03-08T09:47:49Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Mar 07 2023, Glen Choo wrote:\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>> If you include \"populate from system-wide, per-user, and repository\n>> specific configuration files\" as part of the API being libified,\n>> your configsets cannot avoid being tied to a repository.  But I do\n>> not think the reader needs to be in the repository.\n>>\n>> [...]\n>>\n>> Isn't the use of the reader object purely transitory while you\n>> populate the keys and values in a configset from a single file?  At\n>> the layer to read and populate a configset from a single \"source\"\n>> file, you still do not need repository.\n>>\n>> [...]\n>> Only when you say \"I have a 'repo' instance and I want to read the\n>> config variables from appropriate places\", you call such a \"read and\n>> populate configset from a single source\" helper three or four times\n>> to populate repo->config.  Once a configset is populated, it or its\n>> contents do not depend on the reader instance to function, so I do\n>> not see how it benefits to have the reader in the repository object.\n>\n> Yes, exactly. Having a config_set on the repository makes sense, but I\n> don't see a good reason to have the reader on the repository.\n\nIsn't Junio suggesting something different here?\n\nI hadn't looked closely at this aspect of it, and just took it as a\ngiven that we needed to persist this data outside of the configset\nlifetime.\n\nIf that's not the case then we don't need it in the file scope, nor a\n\"struct repository\" or whatever, and could just have it materialized by\ngit_config_check_init(), no? I.e. when we create the configset we'd\ncreate it, and throw it away after the configset is created?\n\nI.e. to address this note in your initial RFC:\n\n\tI think we can get rid of global state in-tree as well. That requires a fair\n\tamount of work though, so I'd like to get thoughts on that before starting\n\twork.\n\n> If Ævar sends his series soon, it would be fruitful to see how that\n> series interacts with this one :)\n\nI tried merging this topic with that, and it didn't conflict textually\nor semantically. I just raised it because I think with your 3/6 you're\nneedlessly tying yourself in knots, i.e. with this part:\n\t\n\tA more typical approach would be to put this struct on \"the_repository\",\n\tbut that's a worse fit for this use case since config reading is not\n\tscoped to a repository. E.g. we can read config before the repository is\n\tknown (\"read_very_early_config()\"), blatantly ignore the repo\n\t(\"read_protected_config()\"), or read only from a file\n\t(\"git_config_from_file()\"). This is especially evident in t5318 and\n\tt9210, where test-tool and scalar parse config but don't fully\n\tinitialize \"the_repository\".\n\nOut of those examples read_very_early_config() and\nread_protected_config() already call config_with_options(), which\noptionally uses a \"struct repository\" via the \"repo\" member of \"struct\ngit_config_source\".\n\nI think we may have gotten lost in the weeds over what amounts to an\nasthetic preference about how to do polymorphism in C. I'd be fine with\nmocking up the \"struct repository\", but you could equally prepare and\npass some new \"config_state\" struct that would contain the required\ninformation (including the configset).\n\nAs well as this in the CL:\n\n\tThe catch (aka the reason I stopped halfway through) is that I\n\tcouldn't find a way to expose \"struct config_reader\" state\n\twithout some fairly big changes, complexity-wise or LoC-wise[...]\n\nI didn't look into exactly why config_fn_t would need your new \"reader\",\nbut if you accept that we could stick such a thing into \"the_repository\"\nthen there's no catch or need for the churn to change all those\ncallbacks.\n\nOf course something that wants to use the API as a \"real\" library would\nneed to use some alternate mechanism, as you reference in adding a new\n\"config_state_fn_t\". You note:\n\n\tbut I couldn't find a great way to do this kind of 'switching\n\tbetween config callback types' elegantly.\n\nSo, I don't know, but I was suggesting looking into doing that based on\n\"the_repository\" in play...\n"},{"id":"473188","messageId":"230308.86356fzhbd.gmgdl@evledraar.gmail.com","threadId":"59327","inReplyTo":"74a63fed7054da8049d4a32ecdb582726368c5a8.1677631097.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 4/6] config.c: plumb the_reader through callbacks","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-03-08T09:54:12Z","receivedAt":"2023-03-08T10:08:31Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Mar 01 2023, Glen Choo via GitGitGadget wrote:\n\n> From: Glen Choo <chooglen@google.com>\n>\n> The remaining references to \"cf\" are in config callback functions.\n> Remove them by plumbing \"struct config_reader\" via the \"*data\" arg.\n>\n> [...]\n>  \t * All remote URLs discovered when reading all config files.\n> @@ -461,6 +457,7 @@ static int include_condition_is_true(struct config_source *cs,\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>  \tconst char *cond, *key;\n>  \tsize_t cond_len;\n>  \tint ret;\n> @@ -474,16 +471,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(cf, value, inc);\n> +\t\tret = handle_path_include(cs, value, inc);\n\nSo, there's a lot of churn in this topic from renaming \"cf\" to \"cs\" all\nover the place, I really wish that could be avoided for the size of the\noverall diff, but haven't looked into in detail how easy that is, but...\n\n>  \n>  \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n> -\t    cond && include_condition_is_true(cf, inc, cond, cond_len) &&\n> +\t    cond && include_condition_is_true(cs, 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(cf, value, inc);\n> +\t\tret = handle_path_include(cs, value, inc);\n>  \t\tinc->fn = old_fn;\n>  \t}\n>  \n> @@ -2224,6 +2221,7 @@ 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> @@ -2344,7 +2342,9 @@ static struct config_set_element *configset_find_element(struct config_set *cs,\n>  \treturn found_entry;\n>  }\n>  \n> -static int configset_add_value(struct config_set *cs, const char *key, const char *value)\n> +static int configset_add_value(struct config_reader *reader,\n> +\t\t\t       struct config_set *cs, const char *key,\n> +\t\t\t       const char *value)\n\n...this is an existing name just seen in the context, but could we in\nthis topic at least avoid having a \"cs\" refer to both a \"struct\nconfig_set\" and a \"struct config_source\" in the end-state?\n\nI tried the below on top of this topic, and the overall diff looks much\nnicer as a result:\n\ndiff --git a/config.c b/config.c\nindex 19bab84c47f..c27c128e3c0 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 config_source *cf, 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 (!cf || !cf->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(cf->path);\n \t\tif (slash)\n-\t\t\tstrbuf_add(&buf, cs->path, slash - cs->path + 1);\n+\t\t\tstrbuf_add(&buf, cf->path, slash - cf->path + 1);\n \t\tstrbuf_addstr(&buf, path);\n \t\tpath = buf.buf;\n \t}\n@@ -236,8 +236,8 @@ 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    !cf ? \"<unknown>\" :\n+\t\t\t    cf->name ? cf->name :\n \t\t\t    \"the command line\");\n \t\tret = git_config_from_file(git_config_include, path, inc);\n \t\tinc->depth--;\n@@ -254,7 +254,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 config_source *cf,\n \t\t\t\t\t     struct strbuf *pat)\n {\n \tstruct strbuf path = STRBUF_INIT;\n@@ -271,11 +271,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 (!cf || !cf->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, cf->path, 1);\n \t\tslash = find_last_dir_sep(path.buf);\n \t\tif (!slash)\n \t\t\tBUG(\"how is this possible?\");\n@@ -290,7 +290,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 config_source *cf,\n \t\t\t     const struct config_options *opts,\n \t\t\t     const char *cond, size_t cond_len, int icase)\n {\n@@ -307,7 +307,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(cf, &pattern);\n \n again:\n \tif (prefix < 0)\n@@ -446,16 +446,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 config_source *cf,\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(cf, 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(cf, 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@@ -469,7 +469,7 @@ static int include_condition_is_true(struct config_source *cs,\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 config_source *cf = inc->config_reader->source;\n \tconst char *cond, *key;\n \tsize_t cond_len;\n \tint ret;\n@@ -483,16 +483,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(cf, 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(cf, 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(cf, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -817,21 +817,21 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \treturn ret;\n }\n \n-static int get_next_char(struct config_source *cs)\n+static int get_next_char(struct config_source *cf)\n {\n-\tint c = cs->do_fgetc(cs);\n+\tint c = cf->do_fgetc(cf);\n \n \tif (c == '\\r') {\n \t\t/* DOS like systems */\n-\t\tc = cs->do_fgetc(cs);\n+\t\tc = cf->do_fgetc(cf);\n \t\tif (c != '\\n') {\n \t\t\tif (c != EOF)\n-\t\t\t\tcs->do_ungetc(c, cs);\n+\t\t\t\tcf->do_ungetc(c, cf);\n \t\t\tc = '\\r';\n \t\t}\n \t}\n \n-\tif (c != EOF && ++cs->total_len > INT_MAX) {\n+\tif (c != EOF && ++cf->total_len > INT_MAX) {\n \t\t/*\n \t\t * This is an absurdly long config file; refuse to parse\n \t\t * further in order to protect downstream code from integer\n@@ -839,38 +839,38 @@ static int get_next_char(struct config_source *cs)\n \t\t * but we can mark EOF and put trash in the return value,\n \t\t * which will trigger a parse error.\n \t\t */\n-\t\tcs->eof = 1;\n+\t\tcf->eof = 1;\n \t\treturn 0;\n \t}\n \n \tif (c == '\\n')\n-\t\tcs->linenr++;\n+\t\tcf->linenr++;\n \tif (c == EOF) {\n-\t\tcs->eof = 1;\n-\t\tcs->linenr++;\n+\t\tcf->eof = 1;\n+\t\tcf->linenr++;\n \t\tc = '\\n';\n \t}\n \treturn c;\n }\n \n-static char *parse_value(struct config_source *cs)\n+static char *parse_value(struct config_source *cf)\n {\n \tint quote = 0, comment = 0, space = 0;\n \n-\tstrbuf_reset(&cs->value);\n+\tstrbuf_reset(&cf->value);\n \tfor (;;) {\n-\t\tint c = get_next_char(cs);\n+\t\tint c = get_next_char(cf);\n \t\tif (c == '\\n') {\n \t\t\tif (quote) {\n-\t\t\t\tcs->linenr--;\n+\t\t\t\tcf->linenr--;\n \t\t\t\treturn NULL;\n \t\t\t}\n-\t\t\treturn cs->value.buf;\n+\t\t\treturn cf->value.buf;\n \t\t}\n \t\tif (comment)\n \t\t\tcontinue;\n \t\tif (isspace(c) && !quote) {\n-\t\t\tif (cs->value.len)\n+\t\t\tif (cf->value.len)\n \t\t\t\tspace++;\n \t\t\tcontinue;\n \t\t}\n@@ -881,9 +881,9 @@ static char *parse_value(struct config_source *cs)\n \t\t\t}\n \t\t}\n \t\tfor (; space; space--)\n-\t\t\tstrbuf_addch(&cs->value, ' ');\n+\t\t\tstrbuf_addch(&cf->value, ' ');\n \t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char(cs);\n+\t\t\tc = get_next_char(cf);\n \t\t\tswitch (c) {\n \t\t\tcase '\\n':\n \t\t\t\tcontinue;\n@@ -903,18 +903,18 @@ static char *parse_value(struct config_source *cs)\n \t\t\tdefault:\n \t\t\t\treturn NULL;\n \t\t\t}\n-\t\t\tstrbuf_addch(&cs->value, c);\n+\t\t\tstrbuf_addch(&cf->value, c);\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '\"') {\n \t\t\tquote = 1-quote;\n \t\t\tcontinue;\n \t\t}\n-\t\tstrbuf_addch(&cs->value, c);\n+\t\tstrbuf_addch(&cf->value, c);\n \t}\n }\n \n-static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n+static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n \t\t     struct strbuf *name)\n {\n \tint c;\n@@ -923,8 +923,8 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \n \t/* Get the full name */\n \tfor (;;) {\n-\t\tc = get_next_char(cs);\n-\t\tif (cs->eof)\n+\t\tc = get_next_char(cf);\n+\t\tif (cf->eof)\n \t\t\tbreak;\n \t\tif (!iskeychar(c))\n \t\t\tbreak;\n@@ -932,13 +932,13 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \t}\n \n \twhile (c == ' ' || c == '\\t')\n-\t\tc = get_next_char(cs);\n+\t\tc = get_next_char(cf);\n \n \tvalue = NULL;\n \tif (c != '\\n') {\n \t\tif (c != '=')\n \t\t\treturn -1;\n-\t\tvalue = parse_value(cs);\n+\t\tvalue = parse_value(cf);\n \t\tif (!value)\n \t\t\treturn -1;\n \t}\n@@ -947,21 +947,21 @@ static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \t * the line we just parsed during the call to fn to get\n \t * accurate line number in error messages.\n \t */\n-\tcs->linenr--;\n+\tcf->linenr--;\n \tret = fn(name->buf, value, data);\n \tif (ret >= 0)\n-\t\tcs->linenr++;\n+\t\tcf->linenr++;\n \treturn ret;\n }\n \n-static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n+static int get_extended_base_var(struct config_source *cf, struct strbuf *name,\n \t\t\t\t int c)\n {\n-\tcs->subsection_case_sensitive = 0;\n+\tcf->subsection_case_sensitive = 0;\n \tdo {\n \t\tif (c == '\\n')\n \t\t\tgoto error_incomplete_line;\n-\t\tc = get_next_char(cs);\n+\t\tc = get_next_char(cf);\n \t} while (isspace(c));\n \n \t/* We require the format to be '[base \"extension\"]' */\n@@ -970,13 +970,13 @@ static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n \tstrbuf_addch(name, '.');\n \n \tfor (;;) {\n-\t\tint c = get_next_char(cs);\n+\t\tint c = get_next_char(cf);\n \t\tif (c == '\\n')\n \t\t\tgoto error_incomplete_line;\n \t\tif (c == '\"')\n \t\t\tbreak;\n \t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char(cs);\n+\t\t\tc = get_next_char(cf);\n \t\t\tif (c == '\\n')\n \t\t\t\tgoto error_incomplete_line;\n \t\t}\n@@ -984,25 +984,25 @@ static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n \t}\n \n \t/* Final ']' */\n-\tif (get_next_char(cs) != ']')\n+\tif (get_next_char(cf) != ']')\n \t\treturn -1;\n \treturn 0;\n error_incomplete_line:\n-\tcs->linenr--;\n+\tcf->linenr--;\n \treturn -1;\n }\n \n-static int get_base_var(struct config_source *cs, struct strbuf *name)\n+static int get_base_var(struct config_source *cf, struct strbuf *name)\n {\n-\tcs->subsection_case_sensitive = 1;\n+\tcf->subsection_case_sensitive = 1;\n \tfor (;;) {\n-\t\tint c = get_next_char(cs);\n-\t\tif (cs->eof)\n+\t\tint c = get_next_char(cf);\n+\t\tif (cf->eof)\n \t\t\treturn -1;\n \t\tif (c == ']')\n \t\t\treturn 0;\n \t\tif (isspace(c))\n-\t\t\treturn get_extended_base_var(cs, name, c);\n+\t\t\treturn get_extended_base_var(cf, name, c);\n \t\tif (!iskeychar(c) && c != '.')\n \t\t\treturn -1;\n \t\tstrbuf_addch(name, tolower(c));\n@@ -1015,7 +1015,7 @@ struct parse_event_data {\n \tconst struct config_options *opts;\n };\n \n-static int do_event(struct config_source *cs, enum config_event_t type,\n+static int do_event(struct config_source *cf, enum config_event_t type,\n \t\t    struct parse_event_data *data)\n {\n \tsize_t offset;\n@@ -1027,7 +1027,7 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \t    data->previous_type == type)\n \t\treturn 0;\n \n-\toffset = cs->do_ftell(cs);\n+\toffset = cf->do_ftell(cf);\n \t/*\n \t * At EOF, the parser always \"inserts\" an extra '\\n', therefore\n \t * the end offset of the event is the current file position, otherwise\n@@ -1047,12 +1047,12 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \treturn 0;\n }\n \n-static int git_parse_source(struct config_source *cs, config_fn_t fn,\n+static int git_parse_source(struct config_source *cf, config_fn_t fn,\n \t\t\t    void *data, const struct config_options *opts)\n {\n \tint comment = 0;\n \tsize_t baselen = 0;\n-\tstruct strbuf *var = &cs->var;\n+\tstruct strbuf *var = &cf->var;\n \tint error_return = 0;\n \tchar *error_msg = NULL;\n \n@@ -1067,7 +1067,7 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \tfor (;;) {\n \t\tint c;\n \n-\t\tc = get_next_char(cs);\n+\t\tc = get_next_char(cf);\n \t\tif (bomptr && *bomptr) {\n \t\t\t/* We are at the file beginning; skip UTF8-encoded BOM\n \t\t\t * if present. Sane editors won't put this in on their\n@@ -1084,12 +1084,12 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t\t}\n \t\t}\n \t\tif (c == '\\n') {\n-\t\t\tif (cs->eof) {\n-\t\t\t\tif (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)\n+\t\t\tif (cf->eof) {\n+\t\t\t\tif (do_event(cf, CONFIG_EVENT_EOF, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\t\treturn 0;\n \t\t\t}\n-\t\t\tif (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n+\t\t\tif (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n \t\t\t\treturn -1;\n \t\t\tcomment = 0;\n \t\t\tcontinue;\n@@ -1097,23 +1097,23 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\tif (comment)\n \t\t\tcontinue;\n \t\tif (isspace(c)) {\n-\t\t\tif (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n+\t\t\tif (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '#' || c == ';') {\n-\t\t\tif (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)\n+\t\t\tif (do_event(cf, CONFIG_EVENT_COMMENT, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\tcomment = 1;\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '[') {\n-\t\t\tif (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)\n+\t\t\tif (do_event(cf, CONFIG_EVENT_SECTION, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \n \t\t\t/* Reset prior to determining a new stem */\n \t\t\tstrbuf_reset(var);\n-\t\t\tif (get_base_var(cs, var) < 0 || var->len < 1)\n+\t\t\tif (get_base_var(cf, var) < 0 || var->len < 1)\n \t\t\t\tbreak;\n \t\t\tstrbuf_addch(var, '.');\n \t\t\tbaselen = var->len;\n@@ -1122,7 +1122,7 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\tif (!isalpha(c))\n \t\t\tbreak;\n \n-\t\tif (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)\n+\t\tif (do_event(cf, CONFIG_EVENT_ENTRY, &event_data) < 0)\n \t\t\treturn -1;\n \n \t\t/*\n@@ -1132,42 +1132,42 @@ 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(cf, fn, data, var) < 0)\n \t\t\tbreak;\n \t}\n \n-\tif (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)\n+\tif (do_event(cf, CONFIG_EVENT_ERROR, &event_data) < 0)\n \t\treturn -1;\n \n-\tswitch (cs->origin_type) {\n+\tswitch (cf->origin_type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in blob %s\"),\n-\t\t\t\t      cs->linenr, cs->name);\n+\t\t\t\t      cf->linenr, cf->name);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_FILE:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in file %s\"),\n-\t\t\t\t      cs->linenr, cs->name);\n+\t\t\t\t      cf->linenr, cf->name);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_STDIN:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in standard input\"),\n-\t\t\t\t      cs->linenr);\n+\t\t\t\t      cf->linenr);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_SUBMODULE_BLOB:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in submodule-blob %s\"),\n-\t\t\t\t       cs->linenr, cs->name);\n+\t\t\t\t       cf->linenr, cf->name);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_CMDLINE:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in command line %s\"),\n-\t\t\t\t       cs->linenr, cs->name);\n+\t\t\t\t       cf->linenr, cf->name);\n \t\tbreak;\n \tdefault:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in %s\"),\n-\t\t\t\t      cs->linenr, cs->name);\n+\t\t\t\t      cf->linenr, cf->name);\n \t}\n \n \tswitch (opts && opts->error_action ?\n \t\topts->error_action :\n-\t\tcs->default_error_action) {\n+\t\tcf->default_error_action) {\n \tcase CONFIG_ERROR_DIE:\n \t\tdie(\"%s\", error_msg);\n \t\tbreak;\n@@ -1309,7 +1309,7 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)\n }\n \n NORETURN\n-static void die_bad_number(struct config_source *cs, const char *name,\n+static void die_bad_number(struct config_source *cf, const char *name,\n \t\t\t   const char *value)\n {\n \tconst char *error_type = (errno == ERANGE) ?\n@@ -1319,28 +1319,28 @@ static void die_bad_number(struct config_source *cs, const char *name,\n \tif (!value)\n \t\tvalue = \"\";\n \n-\tif (!(cs && cs->name))\n+\tif (!(cf && cf->name))\n \t\tdie(_(bad_numeric), value, name, _(error_type));\n \n-\tswitch (cs->origin_type) {\n+\tswitch (cf->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, cs->name, _(error_type));\n+\t\t    value, name, cf->name, _(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, cs->name, _(error_type));\n+\t\t    value, name, cf->name, _(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, cs->name, _(error_type));\n+\t\t    value, name, cf->name, _(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, cs->name, _(error_type));\n+\t\t    value, name, cf->name, _(error_type));\n \tdefault:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in %s: %s\"),\n-\t\t    value, name, cs->name, _(error_type));\n+\t\t    value, name, cf->name, _(error_type));\n \t}\n }\n \n@@ -2274,12 +2274,12 @@ struct configset_iter_data {\n };\n #define CONFIGSET_ITER_INIT { 0 }\n \n-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n+static void configset_iter(struct config_set *cf, 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 = &cs->list;\n+\tstruct configset_list *list = &cf->list;\n \tstruct configset_iter_data *iter_data = data;\n \n \tfor (i = 0; i < list->nr; i++) {\n@@ -2975,7 +2975,7 @@ static int store_aux_event(enum config_event_t type,\n \t\t\t   size_t begin, size_t end, void *data)\n {\n \tstruct config_store_data *store = data;\n-\tstruct config_source *cs = store->config_reader->source;\n+\tstruct config_source *cf = 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@@ -2985,10 +2985,10 @@ static int store_aux_event(enum config_event_t type,\n \tif (type == CONFIG_EVENT_SECTION) {\n \t\tint (*cmpfn)(const char *, const char *, size_t);\n \n-\t\tif (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')\n-\t\t\treturn error(_(\"invalid section name '%s'\"), cs->var.buf);\n+\t\tif (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')\n+\t\t\treturn error(_(\"invalid section name '%s'\"), cf->var.buf);\n \n-\t\tif (cs->subsection_case_sensitive)\n+\t\tif (cf->subsection_case_sensitive)\n \t\t\tcmpfn = strncasecmp;\n \t\telse\n \t\t\tcmpfn = strncmp;\n@@ -2996,8 +2996,8 @@ static int store_aux_event(enum config_event_t type,\n \t\t/* Is this the section we were looking for? */\n \t\tstore->is_keys_section =\n \t\t\tstore->parsed[store->parsed_nr].is_keys_section =\n-\t\t\tcs->var.len - 1 == store->baselen &&\n-\t\t\t!cmpfn(cs->var.buf, store->key, store->baselen);\n+\t\t\tcf->var.len - 1 == store->baselen &&\n+\t\t\t!cmpfn(cf->var.buf, store->key, store->baselen);\n \t\tif (store->is_keys_section) {\n \t\t\tstore->section_seen = 1;\n \t\t\tALLOC_GROW(store->seen, store->seen_nr + 1,\ndiff --git a/config.h b/config.h\nindex 7606246531a..5421c48e287 100644\n--- a/config.h\n+++ b/config.h\n@@ -614,7 +614,7 @@ int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t no\n \n struct key_value_info {\n \tconst char *filename;\n-\tint linenr;\n+\tint linenr2;\n \tenum config_origin_type origin_type;\n \tenum config_scope scope;\n };\n"},{"id":"473189","messageId":"230308.86y1o7y0jc.gmgdl@evledraar.gmail.com","threadId":"59327","inReplyTo":"20230306223804.3414251-1-jonathantanmy@google.com","subject":"Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-03-08T10:32:16Z","receivedAt":"2023-03-08T10:56:25Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Mar 06 2023, Jonathan Tan wrote:\n\n> Glen Choo <chooglen@google.com> writes:\n>> By configset interface, I believe you mean the O(1) lookup functions\n>> like git_config_get_int() (which rely on the value being cached, but\n>> don't necessarily accept \"struct config_set\" as an arg)? I think that\n>> makes sense both from a performance and maintenance perspective.\n>\n> Ah, yes. (More precisely, not the one where you call something like\n> repo_config(), passing a callback function that.)\n>\n>> Given how painful it is to change the config_fn_t signature, I think it\n>> is important to get as right as possible the first time. After I sent\n>> this out, I thought of yet another possible config_fn_t signature\n>> (since most callbacks only need diagnostic information, we could pass\n>> \"struct key_value_info\" instead of the more privileged \"struct\n>> config_reader\"), but given how many functions we'd have to change, it\n>> seemed extremely difficult to even begin experimenting with this\n>> different signature.\n>\n> Yeah, the first change is the hardest. I think passing it a single\n> struct (so, instead of key, value, data, reader, and then any future\n> fields we would need) to which we can add fields later would mean that\n> we wouldn't need any changes beyond the first, though.\n\nThe more I've looked at this the more I'm convinced this is the wrong\ndirection.\n\nFor the configset API users we already have the line number, source\netc. in the \"util\" member, i.e. when we have an error in any API user\nthat uses the configset they can error about the specific line that\nconfig came from.\n\nI think this may have been conflated because e.g. for the configset to\nget the \"scope\" we need to go from do_git_config_sequence(), which will\ncurrently set \"current_parsing_scope\", all the way down to\nconfigset_add_value(), and there we'll make use of the\n\"config_set_callback\", which is a config_fn_t.\n\nBut that's all internal \"static\" functions, except\ngit_config_from_file() and git_config_from_file_with_options(), but\nthose have only a handful of callers.\n\nBut that's *different* than the user callbacks, which will be invoked\nthrough a loop in configset_iter(), i.e. *after* we've parsed the\nconfig, and are just getting the line number, scope etc. from the\nconfigset.\n\nThere's other edge cases, e.g. current_config_line() will access the\nglobal, but it only has two callers (one if we exclude the test\nhelper). But I think the answer there is to change the\nconfig_read_push_default() code, not to give every current \"config_fn_t\"\nimplementation an extra parameter.\n"},{"id":"473206","messageId":"kl6lttyvgm24.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"230308.86356fzhbd.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 4/6] config.c: plumb the_reader through callbacks","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-08T18:00:51Z","receivedAt":"2023-03-08T18:01:00Z","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> So, there's a lot of churn in this topic from renaming \"cf\" to \"cs\" all\n> over the place, I really wish that could be avoided for the size of the\n> overall diff, but haven't looked into in detail how easy that is, but...\n\nYeah, I think it can be avoided by taking Junio's suggestion to rename\nthe global \"cf\" to \"cf_global\" or something, then accepting a \"cf\"\nparameter.\n\n  https://lore.kernel.org/git/xmqqh6v1g1d3.fsf@gitster.g\n\n>> -static int configset_add_value(struct config_set *cs, const char *key, const char *value)\n>> +static int configset_add_value(struct config_reader *reader,\n>> +\t\t\t       struct config_set *cs, const char *key,\n>> +\t\t\t       const char *value)\n>\n> ...this is an existing name just seen in the context, but could we in\n> this topic at least avoid having a \"cs\" refer to both a \"struct\n> config_set\" and a \"struct config_source\" in the end-state?\n\nFair. This also irked me when I first saw it.\n\nAs an aside, I found \"struct config_source cf\" to be a bit of an eyesore\ntoo. It's an unfortunate holdover from before 4d8dd1494e (config: make\nparsing stack struct independent from actual data source, 2013-07-12)\nwhen the struct was called \"config_file\". Unfortunately, \"cs\" is already\ntaken by at least one parameter, so maybe we'll never be able to\nproperly rename it...\n"},{"id":"473207","messageId":"xmqqsfeft8ua.fsf@gitster.g","threadId":"59327","inReplyTo":"kl6lttyvgm24.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH 4/6] config.c: plumb the_reader through callbacks","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-08T18:07:57Z","receivedAt":"2023-03-08T18:08:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> As an aside, I found \"struct config_source cf\" to be a bit of an eyesore\n> too. It's an unfortunate holdover from before 4d8dd1494e (config: make\n> parsing stack struct independent from actual data source, 2013-07-12)\n> when the struct was called \"config_file\".\n\nAh, thanks for digging---I was wondering why it was called cf\nmyself.\n\n"},{"id":"473236","messageId":"kl6lr0tyhmbj.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"230308.86y1o7y0jc.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-08T23:09:52Z","receivedAt":"2023-03-08T23:10:46Z","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 Mon, Mar 06 2023, Jonathan Tan wrote:\n>\n>> Glen Choo <chooglen@google.com> writes:\n>>> By configset interface, I believe you mean the O(1) lookup functions\n>>> like git_config_get_int() (which rely on the value being cached, but\n>>> don't necessarily accept \"struct config_set\" as an arg)? I think that\n>>> makes sense both from a performance and maintenance perspective.\n>>\n>> Ah, yes. (More precisely, not the one where you call something like\n>> repo_config(), passing a callback function that.)\n>>\n>>> Given how painful it is to change the config_fn_t signature, I think it\n>>> is important to get as right as possible the first time. After I sent\n>>> this out, I thought of yet another possible config_fn_t signature\n>>> (since most callbacks only need diagnostic information, we could pass\n>>> \"struct key_value_info\" instead of the more privileged \"struct\n>>> config_reader\"), but given how many functions we'd have to change, it\n>>> seemed extremely difficult to even begin experimenting with this\n>>> different signature.\n>>\n>> Yeah, the first change is the hardest. I think passing it a single\n>> struct (so, instead of key, value, data, reader, and then any future\n>> fields we would need) to which we can add fields later would mean that\n>> we wouldn't need any changes beyond the first, though.\n>\n> For the configset API users we already have the line number, source\n> etc. in the \"util\" member, i.e. when we have an error in any API user\n> that uses the configset they can error about the specific line that\n> config came from.\n\nYeah, and I think we should plumb the \"util\" (actually \"struct\nkey_value_info\") back to the users who need that diagnostic info, which\nachieves the same purpose as plumbing the \"config_reader\" to the\ncallback functions (since the callback functions only need to read\ndiagnostic information), but it's better since it exposes fewer gory\ndetails and we can refactor those using coccinelle. The difficult part\nis replacing the callbacks with the configset API in the first place.\n\n> I think this may have been conflated because e.g. for the configset to\n> get the \"scope\" we need to go from do_git_config_sequence(), which will\n> currently set \"current_parsing_scope\", all the way down to\n> configset_add_value(), and there we'll make use of the\n> \"config_set_callback\", which is a config_fn_t.\n\nI think this is half-right (at least from a historical perspective). We\nstarted by just reading auxiliary info like line number and file name\nfrom the \"current config source\", but when we started caching values in\nconfig sets, we had to find another way to share this auxiliary info.\nThe solution that 0d44a2dacc (config: return configset value for\ncurrent_config_ functions, 2016-05-26) gave us is to also cache the\nauxiliary info, and then when we iterate the configset, we set the\nglobal \"current_config_kvi\" to the cached value.\n\nSo they aren't conflated per se, since the reason for their existence is\nso that API users don't have to care whether or not they are iterating a\nfile or iterating a configset. But as you've observed...\n\n> But that's all internal \"static\" functions, except\n> git_config_from_file() and git_config_from_file_with_options(), but\n> those have only a handful of callers.\n>\n> But that's *different* than the user callbacks, which will be invoked\n> through a loop in configset_iter(), i.e. *after* we've parsed the\n> config, and are just getting the line number, scope etc. from the\n> configset.\n\nin practice, very few users read (and should be reading) config directly\nfrom files. Most want the 'config for the whole repo' (which is handled\nby the repo_* and git_* functions) and others will explicitly call\ngit_config_from_file*() so I don't think the config API needs to keep\nusers ignorant of 'whether the current config is cached or not'.\n\nWe could convert callbacks to the configset API, and if we then we plumb\nthe key_value_info to the configset API users, maybe we can retire\n\"current_config_kvi\".\n\n> There's other edge cases, e.g. current_config_line() will access the\n> global, but it only has two callers (one if we exclude the test\n> helper). But I think the answer there is to change the\n> config_read_push_default() code, not to give every current \"config_fn_t\"\n> implementation an extra parameter.\n\nI agree that we should rewrite config_read_push_default(), but wouldn't\nwe still need to expose auxiliary information (line number, scope) to\nusers of the callback API? e.g. config.c:die_bad_number() uses the file\nname [*], and builtin/config.c definitely needs it for 'git config -l'.\nAlso, an express purpose for this series is to prepare\ngit_config_from_file() to be used by callers out-of-tree, which would\ndefinitely need that information.\n\nI'm not sure if you're proposing to move state from \"the_reader\" to\nsomething like \"the_repository.config_state\". I'd hesitate to take that\napproach, since we're just swapping one global for another.\n\n[*] config.c:die_bad_number() is actually a bit broken because it\ndoesn't use the cached kvi info from the config set. I'll probably send\na fixup patch to fix this.\n"},{"id":"473238","messageId":"kl6lmt4mhlxa.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"230308.867cvrziac.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH 0/6] [RFC] config.c: use struct for config reading state","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-08T23:18:25Z","receivedAt":"2023-03-08T23:18:41Z","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>> Yes, exactly. Having a config_set on the repository makes sense, but I\n>> don't see a good reason to have the reader on the repository.\n>\n> [...]\n>\n> I hadn't looked closely at this aspect of it, and just took it as a\n> given that we needed to persist this data outside of the configset\n> lifetime.\n\nThere really isn't a need to persist the \"config_reader\" state, we'd\nonly need it while reading the file, and as you've hinted, once we've\ncached the info in the configset, we'd just use that instead.\n\n> If that's not the case then we don't need it in the file scope, nor a\n> \"struct repository\" or whatever, and could just have it materialized by\n> git_config_check_init(), no? I.e. when we create the configset we'd\n> create it, and throw it away after the configset is created?\n\nYes, except that I'm proposing that we should do it even lower in the\nchain, like do_config_from().\n\n> \tThe catch (aka the reason I stopped halfway through) is that I\n> \tcouldn't find a way to expose \"struct config_reader\" state\n> \twithout some fairly big changes, complexity-wise or LoC-wise[...]\n>\n> I didn't look into exactly why config_fn_t would need your new \"reader\",\n\nAh, this is what I was referencing in the CL here:\n\n  I believe that only config callbacks are accessing this [config\n  reader] state, e.g. because they use the low-level information (like\n  builtin/config.c printing the filename and scope of the value) or for\n  error reporting (like git_parse_int() reporting the filename and line\n  number of the value it failed to parse)\n\n> but if you accept that we could stick such a thing into \"the_repository\"\n> then there's no catch or need for the churn to change all those\n> callbacks.\n>\n> Of course something that wants to use the API as a \"real\" library would\n> need to use some alternate mechanism, as you reference in adding a new\n> \"config_state_fn_t\". You note:\n>\n> \tbut I couldn't find a great way to do this kind of 'switching\n> \tbetween config callback types' elegantly.\n>\n> So, I don't know, but I was suggesting looking into doing that based on\n> \"the_repository\" in play...\n\nAnd yes, since the primary purpose is to make git_config_from_file()\nusable by a library caller (and secondarily, prepare for a future where\nreading config is thread-safer because of less global state, as Jonathan\ndiscussed [1]), I'd prefer not to use the_repository.\n\n1. https://lore.kernel.org/git/20230306195756.3399115-1-jonathantanmy@google.com\n"},{"id":"473576","messageId":"7555da0b0e012a5fd28ca1a70a4a0897514cd607.1678925506.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com","subject":"[PATCH v2 2/8] config.c: don't assign to \"cf_global\" directly","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-16T00:11:40Z","receivedAt":"2023-03-16T00: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\nTo make \"cf_global\" easier to remove, replace all direct assignments to\nit with function calls. This refactor has an additional maintainability\nbenefit: all of these functions were manually implementing stack\npop/push semantics on \"struct config_source\", so replacing them with\nfunction calls allows us to only implement this logic once.\n\nIn this process, perform some now-obvious clean ups:\n\n- Drop some unnecessary \"cf_global\" assignments in\n  populate_remote_urls(). Since it was introduced in 399b198489 (config:\n  include file if remote URL matches a glob, 2022-01-18), it has stored\n  and restored the value of \"cf_global\" to ensure that it doesn't get\n  accidentally mutated. However, this was never necessary since\n  \"do_config_from()\" already pushes/pops \"cf_global\" further down the\n  call chain.\n\n- Zero out every \"struct config_source\" with a dedicated initializer.\n  This matters because the \"struct config_source\" is assigned to\n  \"cf_global\" and we later 'pop the stack' by assigning \"cf_global =\n  cf_global->prev\", but \"cf_global->prev\" could be pointing to\n  uninitialized garbage.\n\n  Fortunately, this has never bothered us since we never try to read\n  \"cf_global\" except while iterating through config, in which case,\n  \"cf_global\" is either set to a sensible value (when parsing a file),\n  or it is ignored (when iterating a configset). Later in the series,\n  zero-ing out memory will also let us enforce the constraint that\n  \"cf_global\" and \"current_config_kvi\" are never non-NULL together.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 38 +++++++++++++++++++++++++-------------\n 1 file changed, 25 insertions(+), 13 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex e4a76739365..517b8f64038 100644\n--- a/config.c\n+++ b/config.c\n@@ -49,6 +49,7 @@ struct config_source {\n \tint (*do_ungetc)(int c, struct config_source *conf);\n \tlong (*do_ftell)(struct config_source *c);\n };\n+#define CONFIG_SOURCE_INIT { 0 }\n \n /*\n  * These variables record the \"current\" config source, which\n@@ -79,6 +80,23 @@ static struct key_value_info *current_config_kvi;\n  */\n static enum config_scope current_parsing_scope;\n \n+static inline void config_reader_push_source(struct config_source *top)\n+{\n+\tif (cf_global)\n+\t\ttop->prev = cf_global;\n+\tcf_global = top;\n+}\n+\n+static inline struct config_source *config_reader_pop_source()\n+{\n+\tstruct config_source *ret;\n+\tif (!cf_global)\n+\t\tBUG(\"tried to pop config source, but we weren't reading config\");\n+\tret = cf_global;\n+\tcf_global = cf_global->prev;\n+\treturn ret;\n+}\n+\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -346,14 +364,12 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tstruct config_source *store_cf = cf_global;\n \tstruct key_value_info *store_kvi = current_config_kvi;\n \tenum config_scope store_scope = current_parsing_scope;\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tcf_global = NULL;\n \tcurrent_config_kvi = NULL;\n \tcurrent_parsing_scope = 0;\n \n@@ -361,7 +377,6 @@ static void populate_remote_urls(struct config_include_data *inc)\n \tstring_list_init_dup(inc->remote_urls);\n \tconfig_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);\n \n-\tcf_global = store_cf;\n \tcurrent_config_kvi = store_kvi;\n \tcurrent_parsing_scope = store_scope;\n }\n@@ -715,12 +730,10 @@ 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;\n+\tstruct config_source source = CONFIG_SOURCE_INIT;\n \n-\tmemset(&source, 0, sizeof(source));\n-\tsource.prev = cf_global;\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n-\tcf_global = &source;\n+\tconfig_reader_push_source(&source);\n \n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n@@ -778,7 +791,7 @@ out:\n \tstrbuf_release(&envvar);\n \tstrvec_clear(&to_free);\n \tfree(envw);\n-\tcf_global = source.prev;\n+\tconfig_reader_pop_source();\n \treturn ret;\n }\n \n@@ -1949,20 +1962,19 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,\n \tint ret;\n \n \t/* push config-file parsing state stack */\n-\ttop->prev = cf_global;\n \ttop->linenr = 1;\n \ttop->eof = 0;\n \ttop->total_len = 0;\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n-\tcf_global = top;\n+\tconfig_reader_push_source(top);\n \n \tret = git_parse_source(top, fn, data, opts);\n \n \t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n \tstrbuf_release(&top->var);\n-\tcf_global = top->prev;\n+\tconfig_reader_pop_source();\n \n \treturn ret;\n }\n@@ -1972,7 +1984,7 @@ static int do_config_from_file(config_fn_t fn,\n \t\tconst char *name, const char *path, FILE *f,\n \t\tvoid *data, const struct config_options *opts)\n {\n-\tstruct config_source top;\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n \n \ttop.u.file = f;\n@@ -2024,7 +2036,7 @@ int git_config_from_mem(config_fn_t fn,\n \t\t\tconst char *name, const char *buf, size_t len,\n \t\t\tvoid *data, const struct config_options *opts)\n {\n-\tstruct config_source top;\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n \n \ttop.u.buf.buf = buf;\n \ttop.u.buf.len = len;\n-- \ngitgitgadget\n\n"},{"id":"473577","messageId":"75d0f0efb7924fa6ce2387b1a8e4105d672a991f.1678925506.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com","subject":"[PATCH v2 1/8] config.c: plumb config_source through static fns","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-16T00:11:39Z","receivedAt":"2023-03-16T00:11: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\nThis reduces the direct dependence on the global \"struct config_source\",\nwhich will make it easier to remove in a later commit.\n\nTo minimize the changes we need to make, we rename the current variable\nfrom \"cf\" to \"cf_global\", and the plumbed arg uses the old name \"cf\".\nThis is a little unfortunate, since we now have the confusingly named\n\"struct config_source cf\" everywhere (which is a holdover from before\n4d8dd1494e (config: make parsing stack struct independent from actual\ndata source, 2013-07-12), when the struct used to be called\n\"config_file\"), but we will rename \"cf\" to \"cs\" by the end of the\nseries.\n\nIn some cases (public functions and config callback functions), there\nisn't an obvious way to plumb \"struct config_source\" through function\nargs. As a workaround, add references to \"cf_global\" that we'll address\nin later commits.\n\nThe remaining references to \"cf_global\" are direct assignments to\n\"cf_global\", which we'll also address in a later commit.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 153 ++++++++++++++++++++++++++++++-------------------------\n 1 file changed, 84 insertions(+), 69 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 00090a32fc3..e4a76739365 100644\n--- a/config.c\n+++ b/config.c\n@@ -54,8 +54,8 @@ struct config_source {\n  * These variables record the \"current\" config source, which\n  * can be accessed by parsing callbacks.\n  *\n- * The \"cf\" variable will be non-NULL only when we are actually parsing a real\n- * config source (file, blob, cmdline, etc).\n+ * The \"cf_global\" variable will be non-NULL only when we are actually\n+ * parsing a real config source (file, blob, cmdline, etc).\n  *\n  * The \"current_config_kvi\" variable will be non-NULL only when we are feeding\n  * cached config from a configset into a callback.\n@@ -66,15 +66,16 @@ struct config_source {\n  * or it's a function which can be reused for non-config purposes, and should\n  * fall back to some sane behavior).\n  */\n-static struct config_source *cf;\n+static struct config_source *cf_global;\n static struct key_value_info *current_config_kvi;\n \n /*\n  * Similar to the variables above, this gives access to the \"scope\" of the\n  * current value (repo, global, etc). For cached values, it can be found via\n  * the current_config_kvi as above. During parsing, the current value can be\n- * found in this variable. It's not part of \"cf\" because it transcends a single\n- * file (i.e., a file included from .git/config is still in \"repo\" scope).\n+ * found in this variable. It's not part of \"cf_global\" because it transcends a\n+ * single file (i.e., a file included from .git/config is still in \"repo\"\n+ * scope).\n  */\n static enum config_scope current_parsing_scope;\n \n@@ -156,7 +157,8 @@ 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(const char *path, struct config_include_data *inc)\n+static int handle_path_include(struct config_source *cf, const char *path,\n+\t\t\t       struct config_include_data *inc)\n {\n \tint ret = 0;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -210,7 +212,8 @@ 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 strbuf *pat)\n+static int prepare_include_condition_pattern(struct config_source *cf,\n+\t\t\t\t\t     struct strbuf *pat)\n {\n \tstruct strbuf path = STRBUF_INIT;\n \tchar *expanded;\n@@ -245,7 +248,8 @@ static int prepare_include_condition_pattern(struct strbuf *pat)\n \treturn prefix;\n }\n \n-static int include_by_gitdir(const struct config_options *opts,\n+static int include_by_gitdir(struct config_source *cf,\n+\t\t\t     const struct config_options *opts,\n \t\t\t     const char *cond, size_t cond_len, int icase)\n {\n \tstruct strbuf text = STRBUF_INIT;\n@@ -261,7 +265,7 @@ static int include_by_gitdir(const struct config_options *opts,\n \n \tstrbuf_realpath(&text, git_dir, 1);\n \tstrbuf_add(&pattern, cond, cond_len);\n-\tprefix = prepare_include_condition_pattern(&pattern);\n+\tprefix = prepare_include_condition_pattern(cf, &pattern);\n \n again:\n \tif (prefix < 0)\n@@ -342,14 +346,14 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tstruct config_source *store_cf = cf;\n+\tstruct config_source *store_cf = cf_global;\n \tstruct key_value_info *store_kvi = current_config_kvi;\n \tenum config_scope store_scope = current_parsing_scope;\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tcf = NULL;\n+\tcf_global = NULL;\n \tcurrent_config_kvi = NULL;\n \tcurrent_parsing_scope = 0;\n \n@@ -357,7 +361,7 @@ static void populate_remote_urls(struct config_include_data *inc)\n \tstring_list_init_dup(inc->remote_urls);\n \tconfig_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);\n \n-\tcf = store_cf;\n+\tcf_global = store_cf;\n \tcurrent_config_kvi = store_kvi;\n \tcurrent_parsing_scope = store_scope;\n }\n@@ -406,15 +410,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_include_data *inc,\n+static int include_condition_is_true(struct config_source *cf,\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(opts, cond, cond_len, 0);\n+\t\treturn include_by_gitdir(cf, 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(opts, cond, cond_len, 1);\n+\t\treturn include_by_gitdir(cf, 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@@ -441,16 +446,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(value, inc);\n+\t\tret = handle_path_include(cf_global, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n-\t    cond && include_condition_is_true(inc, cond, cond_len) &&\n+\t    cond && include_condition_is_true(cf_global, 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(value, inc);\n+\t\tret = handle_path_include(cf_global, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -713,9 +718,9 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tstruct config_source source;\n \n \tmemset(&source, 0, sizeof(source));\n-\tsource.prev = cf;\n+\tsource.prev = cf_global;\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n-\tcf = &source;\n+\tcf_global = &source;\n \n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n@@ -773,11 +778,11 @@ out:\n \tstrbuf_release(&envvar);\n \tstrvec_clear(&to_free);\n \tfree(envw);\n-\tcf = source.prev;\n+\tcf_global = source.prev;\n \treturn ret;\n }\n \n-static int get_next_char(void)\n+static int get_next_char(struct config_source *cf)\n {\n \tint c = cf->do_fgetc(cf);\n \n@@ -813,13 +818,13 @@ static int get_next_char(void)\n \treturn c;\n }\n \n-static char *parse_value(void)\n+static char *parse_value(struct config_source *cf)\n {\n \tint quote = 0, comment = 0, space = 0;\n \n \tstrbuf_reset(&cf->value);\n \tfor (;;) {\n-\t\tint c = get_next_char();\n+\t\tint c = get_next_char(cf);\n \t\tif (c == '\\n') {\n \t\t\tif (quote) {\n \t\t\t\tcf->linenr--;\n@@ -843,7 +848,7 @@ static char *parse_value(void)\n \t\tfor (; space; space--)\n \t\t\tstrbuf_addch(&cf->value, ' ');\n \t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char();\n+\t\t\tc = get_next_char(cf);\n \t\t\tswitch (c) {\n \t\t\tcase '\\n':\n \t\t\t\tcontinue;\n@@ -874,7 +879,8 @@ static char *parse_value(void)\n \t}\n }\n \n-static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n+static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n+\t\t     struct strbuf *name)\n {\n \tint c;\n \tchar *value;\n@@ -882,7 +888,7 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n \n \t/* Get the full name */\n \tfor (;;) {\n-\t\tc = get_next_char();\n+\t\tc = get_next_char(cf);\n \t\tif (cf->eof)\n \t\t\tbreak;\n \t\tif (!iskeychar(c))\n@@ -891,13 +897,13 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n \t}\n \n \twhile (c == ' ' || c == '\\t')\n-\t\tc = get_next_char();\n+\t\tc = get_next_char(cf);\n \n \tvalue = NULL;\n \tif (c != '\\n') {\n \t\tif (c != '=')\n \t\t\treturn -1;\n-\t\tvalue = parse_value();\n+\t\tvalue = parse_value(cf);\n \t\tif (!value)\n \t\t\treturn -1;\n \t}\n@@ -913,13 +919,14 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n \treturn ret;\n }\n \n-static int get_extended_base_var(struct strbuf *name, int c)\n+static int get_extended_base_var(struct config_source *cf, struct strbuf *name,\n+\t\t\t\t int c)\n {\n \tcf->subsection_case_sensitive = 0;\n \tdo {\n \t\tif (c == '\\n')\n \t\t\tgoto error_incomplete_line;\n-\t\tc = get_next_char();\n+\t\tc = get_next_char(cf);\n \t} while (isspace(c));\n \n \t/* We require the format to be '[base \"extension\"]' */\n@@ -928,13 +935,13 @@ static int get_extended_base_var(struct strbuf *name, int c)\n \tstrbuf_addch(name, '.');\n \n \tfor (;;) {\n-\t\tint c = get_next_char();\n+\t\tint c = get_next_char(cf);\n \t\tif (c == '\\n')\n \t\t\tgoto error_incomplete_line;\n \t\tif (c == '\"')\n \t\t\tbreak;\n \t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char();\n+\t\t\tc = get_next_char(cf);\n \t\t\tif (c == '\\n')\n \t\t\t\tgoto error_incomplete_line;\n \t\t}\n@@ -942,7 +949,7 @@ static int get_extended_base_var(struct strbuf *name, int c)\n \t}\n \n \t/* Final ']' */\n-\tif (get_next_char() != ']')\n+\tif (get_next_char(cf) != ']')\n \t\treturn -1;\n \treturn 0;\n error_incomplete_line:\n@@ -950,17 +957,17 @@ error_incomplete_line:\n \treturn -1;\n }\n \n-static int get_base_var(struct strbuf *name)\n+static int get_base_var(struct config_source *cf, struct strbuf *name)\n {\n \tcf->subsection_case_sensitive = 1;\n \tfor (;;) {\n-\t\tint c = get_next_char();\n+\t\tint c = get_next_char(cf);\n \t\tif (cf->eof)\n \t\t\treturn -1;\n \t\tif (c == ']')\n \t\t\treturn 0;\n \t\tif (isspace(c))\n-\t\t\treturn get_extended_base_var(name, c);\n+\t\t\treturn get_extended_base_var(cf, name, c);\n \t\tif (!iskeychar(c) && c != '.')\n \t\t\treturn -1;\n \t\tstrbuf_addch(name, tolower(c));\n@@ -973,7 +980,8 @@ struct parse_event_data {\n \tconst struct config_options *opts;\n };\n \n-static int do_event(enum config_event_t type, struct parse_event_data *data)\n+static int do_event(struct config_source *cf, enum config_event_t type,\n+\t\t    struct parse_event_data *data)\n {\n \tsize_t offset;\n \n@@ -1004,8 +1012,8 @@ static int do_event(enum config_event_t type, struct parse_event_data *data)\n \treturn 0;\n }\n \n-static int git_parse_source(config_fn_t fn, void *data,\n-\t\t\t    const struct config_options *opts)\n+static int git_parse_source(struct config_source *cf, config_fn_t fn,\n+\t\t\t    void *data, const struct config_options *opts)\n {\n \tint comment = 0;\n \tsize_t baselen = 0;\n@@ -1024,7 +1032,7 @@ static int git_parse_source(config_fn_t fn, void *data,\n \tfor (;;) {\n \t\tint c;\n \n-\t\tc = get_next_char();\n+\t\tc = get_next_char(cf);\n \t\tif (bomptr && *bomptr) {\n \t\t\t/* We are at the file beginning; skip UTF8-encoded BOM\n \t\t\t * if present. Sane editors won't put this in on their\n@@ -1042,11 +1050,11 @@ static int git_parse_source(config_fn_t fn, void *data,\n \t\t}\n \t\tif (c == '\\n') {\n \t\t\tif (cf->eof) {\n-\t\t\t\tif (do_event(CONFIG_EVENT_EOF, &event_data) < 0)\n+\t\t\t\tif (do_event(cf, CONFIG_EVENT_EOF, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\t\treturn 0;\n \t\t\t}\n-\t\t\tif (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n+\t\t\tif (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n \t\t\t\treturn -1;\n \t\t\tcomment = 0;\n \t\t\tcontinue;\n@@ -1054,23 +1062,23 @@ static int git_parse_source(config_fn_t fn, void *data,\n \t\tif (comment)\n \t\t\tcontinue;\n \t\tif (isspace(c)) {\n-\t\t\tif (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n+\t\t\tif (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '#' || c == ';') {\n-\t\t\tif (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0)\n+\t\t\tif (do_event(cf, CONFIG_EVENT_COMMENT, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\tcomment = 1;\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '[') {\n-\t\t\tif (do_event(CONFIG_EVENT_SECTION, &event_data) < 0)\n+\t\t\tif (do_event(cf, CONFIG_EVENT_SECTION, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \n \t\t\t/* Reset prior to determining a new stem */\n \t\t\tstrbuf_reset(var);\n-\t\t\tif (get_base_var(var) < 0 || var->len < 1)\n+\t\t\tif (get_base_var(cf, var) < 0 || var->len < 1)\n \t\t\t\tbreak;\n \t\t\tstrbuf_addch(var, '.');\n \t\t\tbaselen = var->len;\n@@ -1079,7 +1087,7 @@ static int git_parse_source(config_fn_t fn, void *data,\n \t\tif (!isalpha(c))\n \t\t\tbreak;\n \n-\t\tif (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0)\n+\t\tif (do_event(cf, CONFIG_EVENT_ENTRY, &event_data) < 0)\n \t\t\treturn -1;\n \n \t\t/*\n@@ -1089,11 +1097,11 @@ static int git_parse_source(config_fn_t fn, void *data,\n \t\t */\n \t\tstrbuf_setlen(var, baselen);\n \t\tstrbuf_addch(var, tolower(c));\n-\t\tif (get_value(fn, data, var) < 0)\n+\t\tif (get_value(cf, fn, data, var) < 0)\n \t\t\tbreak;\n \t}\n \n-\tif (do_event(CONFIG_EVENT_ERROR, &event_data) < 0)\n+\tif (do_event(cf, CONFIG_EVENT_ERROR, &event_data) < 0)\n \t\treturn -1;\n \n \tswitch (cf->origin_type) {\n@@ -1266,7 +1274,8 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)\n }\n \n NORETURN\n-static void die_bad_number(const char *name, const char *value)\n+static void die_bad_number(struct config_source *cf, const char *name,\n+\t\t\t   const char *value)\n {\n \tconst char *error_type = (errno == ERANGE) ?\n \t\tN_(\"out of range\") : N_(\"invalid unit\");\n@@ -1304,7 +1313,7 @@ int git_config_int(const char *name, const char *value)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(name, value);\n+\t\tdie_bad_number(cf_global, name, value);\n \treturn ret;\n }\n \n@@ -1312,7 +1321,7 @@ int64_t git_config_int64(const char *name, const char *value)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(name, value);\n+\t\tdie_bad_number(cf_global, name, value);\n \treturn ret;\n }\n \n@@ -1320,7 +1329,7 @@ unsigned long git_config_ulong(const char *name, const char *value)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(name, value);\n+\t\tdie_bad_number(cf_global, name, value);\n \treturn ret;\n }\n \n@@ -1328,7 +1337,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(name, value);\n+\t\tdie_bad_number(cf_global, name, value);\n \treturn ret;\n }\n \n@@ -1940,20 +1949,20 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,\n \tint ret;\n \n \t/* push config-file parsing state stack */\n-\ttop->prev = cf;\n+\ttop->prev = cf_global;\n \ttop->linenr = 1;\n \ttop->eof = 0;\n \ttop->total_len = 0;\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n-\tcf = top;\n+\tcf_global = top;\n \n-\tret = git_parse_source(fn, data, opts);\n+\tret = git_parse_source(top, fn, data, opts);\n \n \t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n \tstrbuf_release(&top->var);\n-\tcf = top->prev;\n+\tcf_global = top->prev;\n \n \treturn ret;\n }\n@@ -2334,12 +2343,12 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (!cf)\n+\tif (!cf_global)\n \t\tBUG(\"configset_add_value has no source\");\n-\tif (cf->name) {\n-\t\tkv_info->filename = strintern(cf->name);\n-\t\tkv_info->linenr = cf->linenr;\n-\t\tkv_info->origin_type = cf->origin_type;\n+\tif (cf_global->name) {\n+\t\tkv_info->filename = strintern(cf_global->name);\n+\t\tkv_info->linenr = cf_global->linenr;\n+\t\tkv_info->origin_type = cf_global->origin_type;\n \t} else {\n \t\t/* for values read from `git_config_from_parameters()` */\n \t\tkv_info->filename = NULL;\n@@ -2891,6 +2900,12 @@ static int store_aux_event(enum config_event_t type,\n \t\t\t   size_t begin, size_t end, void *data)\n {\n \tstruct config_store_data *store = data;\n+\t/*\n+\t * FIXME Keep using \"cf\" so that we can avoid rewrapping a\n+\t * really long line below. Remove this when \"cf\" gets plumbed\n+\t * correctly.\n+\t */\n+\tstruct config_source *cf = cf_global;\n \n \tALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);\n \tstore->parsed[store->parsed_nr].begin = begin;\n@@ -3771,8 +3786,8 @@ const char *current_config_origin_type(void)\n \tint type;\n \tif (current_config_kvi)\n \t\ttype = current_config_kvi->origin_type;\n-\telse if(cf)\n-\t\ttype = cf->origin_type;\n+\telse if(cf_global)\n+\t\ttype = cf_global->origin_type;\n \telse\n \t\tBUG(\"current_config_origin_type called outside config callback\");\n \n@@ -3817,8 +3832,8 @@ const char *current_config_name(void)\n \tconst char *name;\n \tif (current_config_kvi)\n \t\tname = current_config_kvi->filename;\n-\telse if (cf)\n-\t\tname = cf->name;\n+\telse if (cf_global)\n+\t\tname = cf_global->name;\n \telse\n \t\tBUG(\"current_config_name called outside config callback\");\n \treturn name ? name : \"\";\n@@ -3837,7 +3852,7 @@ int current_config_line(void)\n \tif (current_config_kvi)\n \t\treturn current_config_kvi->linenr;\n \telse\n-\t\treturn cf->linenr;\n+\t\treturn cf_global->linenr;\n }\n \n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n-- \ngitgitgadget\n\n"},{"id":"473578","messageId":"4347896f0a4896efe5b1410b72f5b583ad6bd9e0.1678925506.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com","subject":"[PATCH v2 3/8] config.c: create config_reader and the_reader","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-16T00:11:41Z","receivedAt":"2023-03-16T00:12: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\nCreate \"struct config_reader\" to hold the state of the config source\ncurrently being read. Then, create a static instance of it,\n\"the_reader\", and use \"the_reader.source\" to replace references to\n\"cf_global\" in public functions.\n\nThis doesn't create much immediate benefit (since we're mostly replacing\nstatic variables with a bigger static variable), but it prepares us for\na future where this state doesn't have to be global; \"struct\nconfig_reader\" (or a similar struct) could be provided by the caller, or\nconstructed internally by a function like \"do_config_from()\".\n\nA more typical approach would be to put this struct on \"the_repository\",\nbut that's a worse fit for this use case since config reading is not\nscoped to a repository. E.g. we can read config before the repository is\nknown (\"read_very_early_config()\"), blatantly ignore the repo\n(\"read_protected_config()\"), or read only from a file\n(\"git_config_from_file()\"). This is especially evident in t5318 and\nt9210, where test-tool and scalar parse config but don't fully\ninitialize \"the_repository\".\n\nWe could have also replaced the references to \"cf_global\" in callback\nfunctions (which are the only ones left), but we'll eventually plumb\n\"the_reader\" through the callback \"*data\" arg, so that would be\nunnecessary churn. Until we remove \"cf_global\" altogether, add logic to\n\"config_reader_*_source()\" to keep \"cf_global\" and \"the_reader.source\"\nin sync.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 84 +++++++++++++++++++++++++++++++++++---------------------\n 1 file changed, 52 insertions(+), 32 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 517b8f64038..7de25515818 100644\n--- a/config.c\n+++ b/config.c\n@@ -51,6 +51,16 @@ struct config_source {\n };\n #define CONFIG_SOURCE_INIT { 0 }\n \n+struct config_reader {\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 /*\n  * These variables record the \"current\" config source, which\n  * can be accessed by parsing callbacks.\n@@ -66,6 +76,9 @@ struct config_source {\n  * at the variables, it's either a bug for it to be called in the first place,\n  * or it's a function which can be reused for non-config purposes, and should\n  * fall back to some sane behavior).\n+ *\n+ * FIXME \"cf_global\" has been replaced by \"the_reader.source\", remove\n+ * \"cf_global\" once we plumb \"the_reader\" through all of the callback functions.\n  */\n static struct config_source *cf_global;\n static struct key_value_info *current_config_kvi;\n@@ -80,20 +93,25 @@ static struct key_value_info *current_config_kvi;\n  */\n static enum config_scope current_parsing_scope;\n \n-static inline void config_reader_push_source(struct config_source *top)\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 (cf_global)\n-\t\ttop->prev = cf_global;\n-\tcf_global = top;\n+\tif (reader->source)\n+\t\ttop->prev = reader->source;\n+\treader->source = top;\n+\t/* FIXME remove this when cf_global is removed. */\n+\tcf_global = reader->source;\n }\n \n-static inline struct config_source *config_reader_pop_source()\n+static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n {\n \tstruct config_source *ret;\n-\tif (!cf_global)\n+\tif (!reader->source)\n \t\tBUG(\"tried to pop config source, but we weren't reading config\");\n-\tret = cf_global;\n-\tcf_global = cf_global->prev;\n+\tret = reader->source;\n+\treader->source = reader->source->prev;\n+\t/* FIXME remove this when cf is removed. */\n+\tcf_global = reader->source;\n \treturn ret;\n }\n \n@@ -733,7 +751,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tstruct config_source source = CONFIG_SOURCE_INIT;\n \n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n-\tconfig_reader_push_source(&source);\n+\tconfig_reader_push_source(&the_reader, &source);\n \n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n@@ -791,7 +809,7 @@ out:\n \tstrbuf_release(&envvar);\n \tstrvec_clear(&to_free);\n \tfree(envw);\n-\tconfig_reader_pop_source();\n+\tconfig_reader_pop_source(&the_reader);\n \treturn ret;\n }\n \n@@ -1326,7 +1344,7 @@ int git_config_int(const char *name, const char *value)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(cf_global, name, value);\n+\t\tdie_bad_number(the_reader.source, name, value);\n \treturn ret;\n }\n \n@@ -1334,7 +1352,7 @@ int64_t git_config_int64(const char *name, const char *value)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(cf_global, name, value);\n+\t\tdie_bad_number(the_reader.source, name, value);\n \treturn ret;\n }\n \n@@ -1342,7 +1360,7 @@ unsigned long git_config_ulong(const char *name, const char *value)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(cf_global, name, value);\n+\t\tdie_bad_number(the_reader.source, name, value);\n \treturn ret;\n }\n \n@@ -1350,7 +1368,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(cf_global, name, value);\n+\t\tdie_bad_number(the_reader.source, name, value);\n \treturn ret;\n }\n \n@@ -1956,7 +1974,8 @@ 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_source *top, config_fn_t fn, void *data,\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  const struct config_options *opts)\n {\n \tint ret;\n@@ -1967,22 +1986,23 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,\n \ttop->total_len = 0;\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n-\tconfig_reader_push_source(top);\n+\tconfig_reader_push_source(reader, top);\n \n \tret = git_parse_source(top, fn, 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();\n+\tconfig_reader_pop_source(reader);\n \n \treturn ret;\n }\n \n-static int do_config_from_file(config_fn_t fn,\n-\t\tconst enum config_origin_type origin_type,\n-\t\tconst char *name, const char *path, FILE *f,\n-\t\tvoid *data, const struct config_options *opts)\n+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 {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n@@ -1997,15 +2017,15 @@ static int do_config_from_file(config_fn_t fn,\n \ttop.do_ftell = config_file_ftell;\n \n \tflockfile(f);\n-\tret = do_config_from(&top, fn, data, opts);\n+\tret = do_config_from(reader, &top, fn, data, opts);\n \tfunlockfile(f);\n \treturn ret;\n }\n \n static int git_config_from_stdin(config_fn_t fn, void *data)\n {\n-\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n-\t\t\t\t   data, NULL);\n+\treturn do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, \"\",\n+\t\t\t\t   NULL, stdin, data, NULL);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2019,8 +2039,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(fn, CONFIG_ORIGIN_FILE, filename,\n-\t\t\t\t\t  filename, f, data, opts);\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\tfclose(f);\n \t}\n \treturn ret;\n@@ -2049,7 +2069,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(&top, fn, data, opts);\n+\treturn do_config_from(&the_reader, &top, fn, data, opts);\n }\n \n int git_config_from_blob_oid(config_fn_t fn,\n@@ -3798,8 +3818,8 @@ const char *current_config_origin_type(void)\n \tint type;\n \tif (current_config_kvi)\n \t\ttype = current_config_kvi->origin_type;\n-\telse if(cf_global)\n-\t\ttype = cf_global->origin_type;\n+\telse if(the_reader.source)\n+\t\ttype = the_reader.source->origin_type;\n \telse\n \t\tBUG(\"current_config_origin_type called outside config callback\");\n \n@@ -3844,8 +3864,8 @@ const char *current_config_name(void)\n \tconst char *name;\n \tif (current_config_kvi)\n \t\tname = current_config_kvi->filename;\n-\telse if (cf_global)\n-\t\tname = cf_global->name;\n+\telse if (the_reader.source)\n+\t\tname = the_reader.source->name;\n \telse\n \t\tBUG(\"current_config_name called outside config callback\");\n \treturn name ? name : \"\";\n@@ -3864,7 +3884,7 @@ int current_config_line(void)\n \tif (current_config_kvi)\n \t\treturn current_config_kvi->linenr;\n \telse\n-\t\treturn cf_global->linenr;\n+\t\treturn the_reader.source->linenr;\n }\n \n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n-- \ngitgitgadget\n\n"},{"id":"473579","messageId":"22b699717499e4f51bab02ed5ab5a4489f42cc6d.1678925506.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com","subject":"[PATCH v2 4/8] config.c: plumb the_reader through callbacks","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-16T00:11:42Z","receivedAt":"2023-03-16T00:12: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\nThe remaining references to \"cf_global\" are in config callback\nfunctions. Remove them by plumbing \"struct config_reader\" via the\n\"*data\" arg.\n\nIn both of the callbacks here, we are only reading from\n\"reader->source\". So in the long run, if we had a way to expose readonly\ninformation from \"reader->source\" (probably in the form of \"struct\nkey_value_info\"), we could undo this patch (i.e. remove \"struct\nconfig_reader\" fom \"*data\").\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 74 ++++++++++++++++++++++++++++++++------------------------\n 1 file changed, 43 insertions(+), 31 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 7de25515818..b75cde42ca6 100644\n--- a/config.c\n+++ b/config.c\n@@ -62,6 +62,9 @@ struct config_reader {\n static struct config_reader the_reader;\n \n /*\n+ * FIXME The comments are temporarily out of date since \"cf_global\" has been\n+ * moved to the_reader, but not current_*.\n+ *\n  * These variables record the \"current\" config source, which\n  * can be accessed by parsing callbacks.\n  *\n@@ -76,11 +79,7 @@ static struct config_reader the_reader;\n  * at the variables, it's either a bug for it to be called in the first place,\n  * or it's a function which can be reused for non-config purposes, and should\n  * fall back to some sane behavior).\n- *\n- * FIXME \"cf_global\" has been replaced by \"the_reader.source\", remove\n- * \"cf_global\" once we plumb \"the_reader\" through all of the callback functions.\n  */\n-static struct config_source *cf_global;\n static struct key_value_info *current_config_kvi;\n \n /*\n@@ -99,8 +98,6 @@ static inline void config_reader_push_source(struct config_reader *reader,\n \tif (reader->source)\n \t\ttop->prev = reader->source;\n \treader->source = top;\n-\t/* FIXME remove this when cf_global is removed. */\n-\tcf_global = reader->source;\n }\n \n static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n@@ -110,8 +107,6 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\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-\t/* FIXME remove this when cf is removed. */\n-\tcf_global = reader->source;\n \treturn ret;\n }\n \n@@ -176,6 +171,7 @@ 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@@ -466,6 +462,7 @@ static int include_condition_is_true(struct config_source *cf,\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 *cf = inc->config_reader->source;\n \tconst char *cond, *key;\n \tsize_t cond_len;\n \tint ret;\n@@ -479,16 +476,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(cf_global, value, inc);\n+\t\tret = handle_path_include(cf, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n-\t    cond && include_condition_is_true(cf_global, inc, cond, cond_len) &&\n+\t    cond && include_condition_is_true(cf, 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(cf_global, value, inc);\n+\t\tret = handle_path_include(cf, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -2229,6 +2226,7 @@ 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@@ -2349,7 +2347,9 @@ static struct config_set_element *configset_find_element(struct config_set *cs,\n \treturn found_entry;\n }\n \n-static int configset_add_value(struct config_set *cs, const char *key, const char *value)\n+static int configset_add_value(struct config_reader *reader,\n+\t\t\t       struct config_set *cs, const char *key,\n+\t\t\t       const char *value)\n {\n \tstruct config_set_element *e;\n \tstruct string_list_item *si;\n@@ -2375,12 +2375,12 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (!cf_global)\n+\tif (!reader->source)\n \t\tBUG(\"configset_add_value has no source\");\n-\tif (cf_global->name) {\n-\t\tkv_info->filename = strintern(cf_global->name);\n-\t\tkv_info->linenr = cf_global->linenr;\n-\t\tkv_info->origin_type = cf_global->origin_type;\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} else {\n \t\t/* for values read from `git_config_from_parameters()` */\n \t\tkv_info->filename = NULL;\n@@ -2435,16 +2435,25 @@ void git_configset_clear(struct config_set *cs)\n \tcs->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, void *cb)\n {\n-\tstruct config_set *cs = cb;\n-\tconfigset_add_value(cs, key, value);\n+\tstruct configset_add_data *data = cb;\n+\tconfigset_add_value(data->config_reader, data->config_set, key, value);\n \treturn 0;\n }\n \n int git_configset_add_file(struct config_set *cs, const char *filename)\n {\n-\treturn git_config_from_file(config_set_callback, filename, cs);\n+\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n+\tdata.config_reader = &the_reader;\n+\tdata.config_set = cs;\n+\treturn git_config_from_file(config_set_callback, filename, &data);\n }\n \n int git_configset_get_value(struct config_set *cs, const char *key, const char **value)\n@@ -2559,6 +2568,7 @@ int git_configset_get_pathname(struct config_set *cs, const char *key, const cha\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@@ -2570,8 +2580,10 @@ 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, repo->config, NULL, &opts) < 0)\n+\tif (config_with_options(config_set_callback, &data, 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@@ -2697,9 +2709,12 @@ 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-\tconfig_with_options(config_set_callback, &protected_config,\n-\t\t\t    NULL, &opts);\n+\tdata.config_set = &protected_config;\n+\tdata.config_reader = &the_reader;\n+\tconfig_with_options(config_set_callback, &data, NULL, &opts);\n }\n \n void git_protected_config(config_fn_t fn, void *data)\n@@ -2884,6 +2899,7 @@ 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@@ -2898,6 +2914,7 @@ struct config_store_data {\n \tunsigned int parsed_nr, parsed_alloc, *seen, seen_nr, seen_alloc;\n \tunsigned int key_seen:1, section_seen:1, is_keys_section:1;\n };\n+#define CONFIG_STORE_INIT { 0 }\n \n static void config_store_data_clear(struct config_store_data *store)\n {\n@@ -2932,12 +2949,7 @@ static int store_aux_event(enum config_event_t type,\n \t\t\t   size_t begin, size_t end, void *data)\n {\n \tstruct config_store_data *store = data;\n-\t/*\n-\t * FIXME Keep using \"cf\" so that we can avoid rewrapping a\n-\t * really long line below. Remove this when \"cf\" gets plumbed\n-\t * correctly.\n-\t */\n-\tstruct config_source *cf = cf_global;\n+\tstruct config_source *cf = 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@@ -3255,9 +3267,9 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \tchar *filename_buf = NULL;\n \tchar *contents = NULL;\n \tsize_t contents_sz;\n-\tstruct config_store_data store;\n+\tstruct config_store_data store = CONFIG_STORE_INIT;\n \n-\tmemset(&store, 0, sizeof(store));\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-- \ngitgitgadget\n\n"},{"id":"473580","messageId":"a57e35163ae68e81a7983fc766be79992c3f4b60.1678925506.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com","subject":"[PATCH v2 6/8] config.c: remove current_parsing_scope","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-16T00:11:44Z","receivedAt":"2023-03-16T00:12: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\nAdd \".parsing_scope\" to \"struct config_reader\" and replace\n\"current_parsing_scope\" with \"the_reader.parsing_scope. Adjust the\ncomment slightly to make it clearer that the scope applies to the config\nsource (not the current value), and should only be set when parsing a\nconfig source.\n\nAs such, \".parsing_scope\" (only set when parsing config sources) and\n\".config_kvi\" (only set when iterating a config set) should not be\nset together, so enforce this with a setter function.\n\nUnlike previous commits, \"populate_remote_urls()\" still needs to store\nand restore the 'scope' value because it could have touched\n\"current_parsing_scope\" (\"config_with_options()\" can set the scope).\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 63 +++++++++++++++++++++++++++++++++-----------------------\n 1 file changed, 37 insertions(+), 26 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex ba9b488c40d..460326ae21e 100644\n--- a/config.c\n+++ b/config.c\n@@ -70,6 +70,16 @@ 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@@ -78,16 +88,6 @@ struct config_reader {\n  */\n static struct config_reader the_reader;\n \n-/*\n- * Similar to the variables above, this gives access to the \"scope\" of the\n- * current value (repo, global, etc). For cached values, it can be found via\n- * the current_config_kvi as above. During parsing, the current value can be\n- * found in this variable. It's not part of \"cf_global\" because it transcends a\n- * single file (i.e., a file included from .git/config is still in \"repo\"\n- * scope).\n- */\n-static enum config_scope current_parsing_scope;\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@@ -111,11 +111,19 @@ 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)\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@@ -384,18 +392,18 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tenum config_scope store_scope = current_parsing_scope;\n+\tenum config_scope store_scope = inc->config_reader->parsing_scope;\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tcurrent_parsing_scope = 0;\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-\tcurrent_parsing_scope = store_scope;\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@@ -2160,7 +2168,8 @@ int git_config_system(void)\n \treturn !git_env_bool(\"GIT_CONFIG_NOSYSTEM\", 0);\n }\n \n-static int do_git_config_sequence(const struct config_options *opts,\n+static int do_git_config_sequence(struct config_reader *reader,\n+\t\t\t\t  const struct config_options *opts,\n \t\t\t\t  config_fn_t fn, void *data)\n {\n \tint ret = 0;\n@@ -2168,7 +2177,7 @@ static int do_git_config_sequence(const struct config_options *opts,\n \tchar *xdg_config = NULL;\n \tchar *user_config = NULL;\n \tchar *repo_config;\n-\tenum config_scope prev_parsing_scope = current_parsing_scope;\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@@ -2177,13 +2186,13 @@ static int do_git_config_sequence(const struct config_options *opts,\n \telse\n \t\trepo_config = NULL;\n \n-\tcurrent_parsing_scope = CONFIG_SCOPE_SYSTEM;\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 \n-\tcurrent_parsing_scope = 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@@ -2192,12 +2201,12 @@ static int do_git_config_sequence(const struct config_options *opts,\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 \n-\tcurrent_parsing_scope = 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 \n-\tcurrent_parsing_scope = 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@@ -2205,11 +2214,11 @@ static int do_git_config_sequence(const struct config_options *opts,\n \t\tfree(path);\n \t}\n \n-\tcurrent_parsing_scope = CONFIG_SCOPE_COMMAND;\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-\tcurrent_parsing_scope = prev_parsing_scope;\n+\tconfig_reader_set_scope(reader, prev_parsing_scope);\n \tfree(system_config);\n \tfree(xdg_config);\n \tfree(user_config);\n@@ -2222,6 +2231,7 @@ 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@@ -2235,7 +2245,7 @@ int config_with_options(config_fn_t fn, void *data,\n \t}\n \n \tif (config_source)\n-\t\tcurrent_parsing_scope = config_source->scope;\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@@ -2251,13 +2261,14 @@ 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\tdata);\n \t} else {\n-\t\tret = do_git_config_sequence(opts, fn, data);\n+\t\tret = do_git_config_sequence(&the_reader, 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@@ -2391,7 +2402,7 @@ static int configset_add_value(struct config_reader *reader,\n \t\tkv_info->linenr = -1;\n \t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n \t}\n-\tkv_info->scope = current_parsing_scope;\n+\tkv_info->scope = reader->parsing_scope;\n \tsi->util = kv_info;\n \n \treturn 0;\n@@ -3892,7 +3903,7 @@ 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 current_parsing_scope;\n+\t\treturn the_reader.parsing_scope;\n }\n \n int current_config_line(void)\n-- \ngitgitgadget\n\n"},{"id":"473581","messageId":"afb6e3e318da3ff2f6c904459b28eac2444f0897.1678925506.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com","subject":"[PATCH v2 5/8] config.c: remove current_config_kvi","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-16T00:11:43Z","receivedAt":"2023-03-16T00:12: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\nAdd \".config_kvi\" to \"struct config_reader\" and replace\n\"current_config_kvi\" with \"the_reader.config_kvi\", plumbing \"struct\nconfig_reader\" where necesssary.\n\nAlso, introduce a setter function for \".config_kvi\", which allows us to\nenforce the contraint that only one of \".source\" and \".config_kvi\" can\nbe set at a time (as documented in the comments). Because of this\nconstraint, we know that \"populate_remote_urls()\" was never touching\n\"current_config_kvi\" when iterating through config files, so it doesn't\nneed to store and restore that value.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 82 +++++++++++++++++++++++++++++---------------------------\n 1 file changed, 43 insertions(+), 39 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex b75cde42ca6..ba9b488c40d 100644\n--- a/config.c\n+++ b/config.c\n@@ -52,7 +52,24 @@ 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  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n@@ -61,27 +78,6 @@ struct config_reader {\n  */\n static struct config_reader the_reader;\n \n-/*\n- * FIXME The comments are temporarily out of date since \"cf_global\" has been\n- * moved to the_reader, but not current_*.\n- *\n- * These variables record the \"current\" config source, which\n- * can be accessed by parsing callbacks.\n- *\n- * The \"cf_global\" variable will be non-NULL only when we are actually\n- * parsing a real config source (file, blob, cmdline, etc).\n- *\n- * The \"current_config_kvi\" variable will be non-NULL only when we are feeding\n- * cached config from a configset into a callback.\n- *\n- * They should generally never be non-NULL at the same time. If they are both\n- * NULL, then we aren't parsing anything (and depending on the function looking\n- * at the variables, it's either a bug for it to be called in the first place,\n- * or it's a function which can be reused for non-config purposes, and should\n- * fall back to some sane behavior).\n- */\n-static struct key_value_info *current_config_kvi;\n-\n /*\n  * Similar to the variables above, this gives access to the \"scope\" of the\n  * current value (repo, global, etc). For cached values, it can be found via\n@@ -95,6 +91,8 @@ static enum config_scope current_parsing_scope;\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 \tif (reader->source)\n \t\ttop->prev = reader->source;\n \treader->source = top;\n@@ -110,6 +108,14 @@ 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+\tif (kvi && reader->source)\n+\t\tBUG(\"kvi should not be set while parsing a config source\");\n+\treader->config_kvi = kvi;\n+}\n+\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -378,20 +384,17 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tstruct key_value_info *store_kvi = current_config_kvi;\n \tenum config_scope store_scope = current_parsing_scope;\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tcurrent_config_kvi = NULL;\n \tcurrent_parsing_scope = 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-\tcurrent_config_kvi = store_kvi;\n \tcurrent_parsing_scope = store_scope;\n }\n \n@@ -2258,7 +2261,8 @@ int config_with_options(config_fn_t fn, void *data,\n \treturn ret;\n }\n \n-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n+static void configset_iter(struct config_reader *reader, struct config_set *cs,\n+\t\t\t   config_fn_t fn, void *data)\n {\n \tint i, value_index;\n \tstruct string_list *values;\n@@ -2270,14 +2274,14 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \t\tvalue_index = list->items[i].value_index;\n \t\tvalues = &entry->value_list;\n \n-\t\tcurrent_config_kvi = values->items[value_index].util;\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      current_config_kvi->filename,\n-\t\t\t\t\t      current_config_kvi->linenr);\n+\t\t\t\t\t      reader->config_kvi->filename,\n+\t\t\t\t\t      reader->config_kvi->linenr);\n \n-\t\tcurrent_config_kvi = NULL;\n+\t\tconfig_reader_set_kvi(reader, NULL);\n \t}\n }\n \n@@ -2615,7 +2619,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(repo->config, fn, data);\n+\tconfigset_iter(&the_reader, repo->config, fn, data);\n }\n \n int repo_config_get_value(struct repository *repo,\n@@ -2721,7 +2725,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(&protected_config, fn, data);\n+\tconfigset_iter(&the_reader, &protected_config, fn, data);\n }\n \n /* Functions used historically to read configuration from 'the_repository' */\n@@ -3828,8 +3832,8 @@ int parse_config_key(const char *var,\n const char *current_config_origin_type(void)\n {\n \tint type;\n-\tif (current_config_kvi)\n-\t\ttype = current_config_kvi->origin_type;\n+\tif (the_reader.config_kvi)\n+\t\ttype = the_reader.config_kvi->origin_type;\n \telse if(the_reader.source)\n \t\ttype = the_reader.source->origin_type;\n \telse\n@@ -3874,8 +3878,8 @@ const char *config_scope_name(enum config_scope scope)\n const char *current_config_name(void)\n {\n \tconst char *name;\n-\tif (current_config_kvi)\n-\t\tname = current_config_kvi->filename;\n+\tif (the_reader.config_kvi)\n+\t\tname = the_reader.config_kvi->filename;\n \telse if (the_reader.source)\n \t\tname = the_reader.source->name;\n \telse\n@@ -3885,16 +3889,16 @@ const char *current_config_name(void)\n \n enum config_scope current_config_scope(void)\n {\n-\tif (current_config_kvi)\n-\t\treturn current_config_kvi->scope;\n+\tif (the_reader.config_kvi)\n+\t\treturn the_reader.config_kvi->scope;\n \telse\n \t\treturn current_parsing_scope;\n }\n \n int current_config_line(void)\n {\n-\tif (current_config_kvi)\n-\t\treturn current_config_kvi->linenr;\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-- \ngitgitgadget\n\n"},{"id":"473582","messageId":"pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.git.git.1677631097.gitgitgadget@gmail.com","subject":"[PATCH v2 0/8] config.c: use struct for config reading state","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-16T00:11:38Z","receivedAt":"2023-03-16T00:12:11Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Note to Junio: 8/8 (which renames \"cs\" -> \"set\") conflicts with\nab/config-multi-and-nonbool. The resolution is to rename \"cs\" -> \"set\" in\nthe conflicted hunks. This leaves a few \"struct config_set cs\" in\nunconflicted hunks, but since 8/8 might be controversial, I don't think it's\nworth your time to fix these. I'll reroll this and base it on top of\nab/config-multi-and-nonbool instead (since that is pretty stable and close\nto being merged).\n\nThanks for the thoughtful review on v1, all. I'm sending this as a regular\nseries, not an RFC.\n\nAfter reflecting on Ævar's responses on v1, I'm fairly convinced that\n\"struct config_reader\" shouldn't exist in the long term. I've written my\nthoughts on a good long term direction in the \"Leftover bits\" section. Based\non that, I've also updated my WIP libification patches [1] to remove \"struct\nconfig_reader\" from the library interface, and think it looks a lot better\nas a result.\n\n= Changes in v2\n\n * To reduce churn, don't rename \"struct config_source cf\" to \"cs\" early in\n   the series. Instead, rename the global \"cf\" to \"cf_global\", and leave the\n   existing \"cf\"s untouched.\n   \n   Introduce 8/8 to get rid of the confusing acronym \"struct config_source\n   cf\", but I don't mind ejecting it if it's too much churn.\n\n * Adjust 5/8 so to pass \"struct config_reader\" through args instead of\n   \"*data\". v1 made the mistake of thinking \"*data\" was being passed to a\n   callback, but it wasn't.\n\n * Add a 7/8 to fix a bug in die_bad_number(). I included this because it\n   overlaps a little bit with the refactor here, but I don't mind ejecting\n   this either.\n\n * Assorted BUG() message clarifications.\n\nAs a result of moving the rename, the range-diff is quite noisy. The diff\nbetween the final commits is might be helpful instead [2] (I'll also send a\ndiff to the ML).\n\n= Description\n\nThis series prepares for config.[ch] to be libified as as part of the\nlibification effort that Emily described in [3]. One of the first goals is\nto read config from a file, but the trouble with how config.c is written\ntoday is that all reading operations rely on global state, so before turning\nthat into a library, we'd want to make that state non-global.\n\nThis series doesn't remove all of the global state, but it gets us closer to\nthat goal by extracting the global config reading state into \"struct\nconfig_reader\" and plumbing it through the config reading machinery. This\nmakes it possible to reuse the config machinery without global state, and to\nenforce some constraints on \"struct config_reader\", which makes it more\npredictable and easier to remove in the long run.\n\nThis process is very similar to how we've plumbed \"struct repository\" and\nother 'context objects' in the past, except:\n\n * The global state (named \"the_reader\") for the git process lives in a\n   config.c static variable, and not on \"the_repository\". See 3/6 for the\n   rationale.\n\n * I've stopped short of adding \"struct config_reader\" to config.h public\n   functions, since that would affect non-config.c callers.\n\nAdditionally, I've included a bugfix for die_bad_number() that became clear\nas I did this refactor.\n\n= Leftover bits\n\nWe still need a global \"the_reader\" because config callbacks are reading\nauxiliary information about the config (e.g. line number, file name) via\nglobal functions (e.g. current_config_line(), current_config_name()). This\nis either because the callback uses this info directly (like\nbuiltin/config.c printing the filename and scope of the value) or for error\nreporting (like git_parse_int() reporting the filename of the value it\nfailed to parse).\n\nIf we had a way to plumb the state from \"struct config_reader\" to the config\ncallback functions, we could initialize \"struct config_reader\" in the config\nmachinery whenever we read config (instead of asking the caller to\ninitialize \"struct config_reader\" themselves), and config reading could\nbecome a thread-safe operation. There isn't an obvious way to plumb this\nstate to config callbacks without adding an additional arg to config_fn_t\nand incurring a lot of churn, but if we start replacing \"config_fn_t\" with\nthe configset API (which we've independently wanted for some time), this may\nbecome feasible.\n\nAnd if we do this, \"struct config_reader\" itself will probably become\nobsolete, because we'd be able to plumb only the relevant state for the\ncurrent operation, e.g. if we are parsing a config file, we'd pass only the\nconfig file parsing state, instead of \"struct config_reader\", which also\ncontains config set iterating state. In such a scenario, we'd probably want\nto pass \"struct key_value_info\" to the config callback, since that's all the\ncallback should be interested in anyway. Interestingly, this was proposed by\nJunio back in [4], and we didn't do this back then out of concern for the\nchurn (just like in v1).\n\n[1]\nhttps://github.com/git/git/compare/master...chooglen:git:config-lib-parsing\n[2]\nhttps://github.com/gitgitgadget/git/compare/pr-git-1463/chooglen/config/structify-reading-v1..chooglen:git:config/structify-reading\n[3]\nhttps://lore.kernel.org/git/CAJoAoZ=Cig_kLocxKGax31sU7Xe4==BGzC__Bg2_pr7krNq6MA@mail.gmail.com\n[4]\nhttps://lore.kernel.org/git/CAPc5daV6bdUKS-ExHmpT4Ppy2S832NXoyPw7aOLP7fG=WrBPgg@mail.gmail.com/\n\nGlen Choo (8):\n  config.c: plumb config_source through static fns\n  config.c: don't assign to \"cf_global\" directly\n  config.c: create config_reader and the_reader\n  config.c: plumb the_reader through callbacks\n  config.c: remove current_config_kvi\n  config.c: remove current_parsing_scope\n  config: report cached filenames in die_bad_number()\n  config.c: rename \"struct config_source cf\"\n\n config.c               | 585 ++++++++++++++++++++++++-----------------\n config.h               |   1 +\n t/helper/test-config.c |  17 ++\n t/t1308-config-set.sh  |   9 +\n 4 files changed, 369 insertions(+), 243 deletions(-)\n\n\nbase-commit: dadc8e6dacb629f46aee39bde90b6f09b73722eb\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1463%2Fchooglen%2Fconfig%2Fstructify-reading-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1463/chooglen/config/structify-reading-v2\nPull-Request: https://github.com/git/git/pull/1463\n\nRange-diff vs v1:\n\n -:  ----------- > 1:  75d0f0efb79 config.c: plumb config_source through static fns\n 2:  9f72cbb8d78 ! 2:  7555da0b0e0 config.c: don't assign to \"cf\" directly\n     @@ Metadata\n      Author: Glen Choo <chooglen@google.com>\n      \n       ## Commit message ##\n     -    config.c: don't assign to \"cf\" directly\n     +    config.c: don't assign to \"cf_global\" directly\n      \n     -    To make \"cf\" easier to remove, replace all direct assignments to it with\n     -    function calls. This refactor has an additional maintainability benefit:\n     -    all of these functions were manually implementing stack pop/push\n     -    semantics on \"struct config_source\", so replacing them with function\n     -    calls allows us to only implement this logic once.\n     +    To make \"cf_global\" easier to remove, replace all direct assignments to\n     +    it with function calls. This refactor has an additional maintainability\n     +    benefit: all of these functions were manually implementing stack\n     +    pop/push semantics on \"struct config_source\", so replacing them with\n     +    function calls allows us to only implement this logic once.\n      \n          In this process, perform some now-obvious clean ups:\n      \n     -    - Drop some unnecessary \"cf\" assignments in populate_remote_urls().\n     -      Since it was introduced in 399b198489 (config: include file if remote\n     -      URL matches a glob, 2022-01-18), it has stored and restored the value\n     -      of \"cf\" to ensure that it doesn't get accidentally mutated. However,\n     -      this was never necessary since \"do_config_from()\" already pushes/pops\n     -      \"cf\" further down the call chain.\n     +    - Drop some unnecessary \"cf_global\" assignments in\n     +      populate_remote_urls(). Since it was introduced in 399b198489 (config:\n     +      include file if remote URL matches a glob, 2022-01-18), it has stored\n     +      and restored the value of \"cf_global\" to ensure that it doesn't get\n     +      accidentally mutated. However, this was never necessary since\n     +      \"do_config_from()\" already pushes/pops \"cf_global\" further down the\n     +      call chain.\n      \n          - Zero out every \"struct config_source\" with a dedicated initializer.\n     -      This matters because the \"struct config_source\" is assigned to \"cf\"\n     -      and we later 'pop the stack' by assigning \"cf = cf->prev\", but\n     -      \"cf->prev\" could be pointing to uninitialized garbage.\n     +      This matters because the \"struct config_source\" is assigned to\n     +      \"cf_global\" and we later 'pop the stack' by assigning \"cf_global =\n     +      cf_global->prev\", but \"cf_global->prev\" could be pointing to\n     +      uninitialized garbage.\n      \n            Fortunately, this has never bothered us since we never try to read\n     -      \"cf\" except while iterating through config, in which case, \"cf\" is\n     -      either set to a sensible value (when parsing a file), or it is ignored\n     -      (when iterating a configset). Later in the series, zero-ing out memory\n     -      will also let us enforce the constraint that \"cf\" and\n     -      \"current_config_kvi\" are never non-NULL together.\n     +      \"cf_global\" except while iterating through config, in which case,\n     +      \"cf_global\" is either set to a sensible value (when parsing a file),\n     +      or it is ignored (when iterating a configset). Later in the series,\n     +      zero-ing out memory will also let us enforce the constraint that\n     +      \"cf_global\" and \"current_config_kvi\" are never non-NULL together.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n     @@ config.c: static struct key_value_info *current_config_kvi;\n        */\n       static enum config_scope current_parsing_scope;\n       \n     -+static inline void config_state_push_source(struct config_source *top)\n     ++static inline void config_reader_push_source(struct config_source *top)\n      +{\n     -+\tif (cf)\n     -+\t\ttop->prev = cf;\n     -+\tcf = top;\n     ++\tif (cf_global)\n     ++\t\ttop->prev = cf_global;\n     ++\tcf_global = top;\n      +}\n      +\n     -+static inline struct config_source *config_state_pop_source()\n     ++static inline struct config_source *config_reader_pop_source()\n      +{\n      +\tstruct config_source *ret;\n     -+\tif (!cf)\n     ++\tif (!cf_global)\n      +\t\tBUG(\"tried to pop config source, but we weren't reading config\");\n     -+\tret = cf;\n     -+\tcf = cf->prev;\n     ++\tret = cf_global;\n     ++\tcf_global = cf_global->prev;\n      +\treturn ret;\n      +}\n      +\n     @@ config.c: static void populate_remote_urls(struct config_include_data *inc)\n       {\n       \tstruct config_options opts;\n       \n     --\tstruct config_source *store_cf = cf;\n     +-\tstruct config_source *store_cf = cf_global;\n       \tstruct key_value_info *store_kvi = current_config_kvi;\n       \tenum config_scope store_scope = current_parsing_scope;\n       \n       \topts = *inc->opts;\n       \topts.unconditional_remote_url = 1;\n       \n     --\tcf = NULL;\n     +-\tcf_global = NULL;\n       \tcurrent_config_kvi = NULL;\n       \tcurrent_parsing_scope = 0;\n       \n     @@ config.c: static void populate_remote_urls(struct config_include_data *inc)\n       \tstring_list_init_dup(inc->remote_urls);\n       \tconfig_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);\n       \n     --\tcf = store_cf;\n     +-\tcf_global = store_cf;\n       \tcurrent_config_kvi = store_kvi;\n       \tcurrent_parsing_scope = store_scope;\n       }\n     @@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)\n      +\tstruct config_source source = CONFIG_SOURCE_INIT;\n       \n      -\tmemset(&source, 0, sizeof(source));\n     --\tsource.prev = cf;\n     +-\tsource.prev = cf_global;\n       \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n     --\tcf = &source;\n     -+\tconfig_state_push_source(&source);\n     +-\tcf_global = &source;\n     ++\tconfig_reader_push_source(&source);\n       \n       \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n       \tif (env) {\n     @@ config.c: out:\n       \tstrbuf_release(&envvar);\n       \tstrvec_clear(&to_free);\n       \tfree(envw);\n     --\tcf = source.prev;\n     -+\tconfig_state_pop_source();\n     +-\tcf_global = source.prev;\n     ++\tconfig_reader_pop_source();\n       \treturn ret;\n       }\n       \n     @@ config.c: static int do_config_from(struct config_source *top, config_fn_t fn, v\n       \tint ret;\n       \n       \t/* push config-file parsing state stack */\n     --\ttop->prev = cf;\n     +-\ttop->prev = cf_global;\n       \ttop->linenr = 1;\n       \ttop->eof = 0;\n       \ttop->total_len = 0;\n       \tstrbuf_init(&top->value, 1024);\n       \tstrbuf_init(&top->var, 1024);\n     --\tcf = top;\n     -+\tconfig_state_push_source(top);\n     +-\tcf_global = top;\n     ++\tconfig_reader_push_source(top);\n       \n     --\tret = git_parse_source(cf, fn, data, opts);\n     -+\tret = git_parse_source(top, fn, data, opts);\n     + \tret = git_parse_source(top, fn, data, opts);\n       \n       \t/* pop config-file parsing state stack */\n       \tstrbuf_release(&top->value);\n       \tstrbuf_release(&top->var);\n     --\tcf = top->prev;\n     -+\tconfig_state_pop_source();\n     +-\tcf_global = top->prev;\n     ++\tconfig_reader_pop_source();\n       \n       \treturn ret;\n       }\n 3:  751ce3e927d ! 3:  4347896f0a4 config.c: create config_reader and the_reader\n     @@ Commit message\n      \n          Create \"struct config_reader\" to hold the state of the config source\n          currently being read. Then, create a static instance of it,\n     -    \"the_reader\", and use \"the_reader.source\" to replace references to \"cf\"\n     -    in public functions.\n     +    \"the_reader\", and use \"the_reader.source\" to replace references to\n     +    \"cf_global\" in public functions.\n      \n          This doesn't create much immediate benefit (since we're mostly replacing\n          static variables with a bigger static variable), but it prepares us for\n     -    a future where this state doesn't have to be global; the \"struct\n     -    config_reader\" could be provided by the caller, or constructed\n     -    internally by a function like \"do_config_from()\".\n     +    a future where this state doesn't have to be global; \"struct\n     +    config_reader\" (or a similar struct) could be provided by the caller, or\n     +    constructed internally by a function like \"do_config_from()\".\n      \n          A more typical approach would be to put this struct on \"the_repository\",\n          but that's a worse fit for this use case since config reading is not\n     @@ Commit message\n          t9210, where test-tool and scalar parse config but don't fully\n          initialize \"the_repository\".\n      \n     -    We could have also replaced the references to \"cf\" in callback functions\n     -    (which are the only ones left), but we'll eventually plumb \"the_reader\"\n     -    through the callback \"*data\" arg, which will allow us to rename \"cf\" to\n     -    \"cs\" without changing line lengths. Until we remove \"cf\" altogether,\n     -    add logic to \"config_reader_*_source()\" to keep \"cf\" and\n     -    \"the_reader.source\" in sync.\n     +    We could have also replaced the references to \"cf_global\" in callback\n     +    functions (which are the only ones left), but we'll eventually plumb\n     +    \"the_reader\" through the callback \"*data\" arg, so that would be\n     +    unnecessary churn. Until we remove \"cf_global\" altogether, add logic to\n     +    \"config_reader_*_source()\" to keep \"cf_global\" and \"the_reader.source\"\n     +    in sync.\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n     @@ config.c: struct config_source {\n      +struct config_reader {\n      +\tstruct config_source *source;\n      +};\n     -+/* Only public functions should reference the_reader. */\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       /*\n     @@ config.c: struct config_source {\n        * or it's a function which can be reused for non-config purposes, and should\n        * fall back to some sane behavior).\n      + *\n     -+ * FIXME \"cf\" has been replaced by \"the_reader.source\", remove\n     -+ * \"cf\" once we plumb \"the_reader\" through all of the callback functions.\n     ++ * FIXME \"cf_global\" has been replaced by \"the_reader.source\", remove\n     ++ * \"cf_global\" once we plumb \"the_reader\" through all of the callback functions.\n        */\n     - static struct config_source *cf;\n     + static struct config_source *cf_global;\n       static struct key_value_info *current_config_kvi;\n      @@ config.c: static struct key_value_info *current_config_kvi;\n        */\n       static enum config_scope current_parsing_scope;\n       \n     --static inline void config_state_push_source(struct config_source *top)\n     +-static inline void config_reader_push_source(struct config_source *top)\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 (cf)\n     --\t\ttop->prev = cf;\n     --\tcf = top;\n     +-\tif (cf_global)\n     +-\t\ttop->prev = cf_global;\n     +-\tcf_global = top;\n      +\tif (reader->source)\n      +\t\ttop->prev = reader->source;\n      +\treader->source = top;\n     -+\t/* FIXME remove this when cf is removed. */\n     -+\tcf = reader->source;\n     ++\t/* FIXME remove this when cf_global is removed. */\n     ++\tcf_global = reader->source;\n       }\n       \n     --static inline struct config_source *config_state_pop_source()\n     +-static inline struct config_source *config_reader_pop_source()\n      +static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n       {\n       \tstruct config_source *ret;\n     --\tif (!cf)\n     +-\tif (!cf_global)\n      +\tif (!reader->source)\n       \t\tBUG(\"tried to pop config source, but we weren't reading config\");\n     --\tret = cf;\n     --\tcf = cf->prev;\n     +-\tret = cf_global;\n     +-\tcf_global = cf_global->prev;\n      +\tret = reader->source;\n      +\treader->source = reader->source->prev;\n      +\t/* FIXME remove this when cf is removed. */\n     -+\tcf = reader->source;\n     ++\tcf_global = reader->source;\n       \treturn ret;\n       }\n       \n     @@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)\n       \tstruct config_source source = CONFIG_SOURCE_INIT;\n       \n       \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n     --\tconfig_state_push_source(&source);\n     +-\tconfig_reader_push_source(&source);\n      +\tconfig_reader_push_source(&the_reader, &source);\n       \n       \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n     @@ config.c: out:\n       \tstrbuf_release(&envvar);\n       \tstrvec_clear(&to_free);\n       \tfree(envw);\n     --\tconfig_state_pop_source();\n     +-\tconfig_reader_pop_source();\n      +\tconfig_reader_pop_source(&the_reader);\n       \treturn ret;\n       }\n     @@ config.c: int git_config_int(const char *name, const char *value)\n       {\n       \tint ret;\n       \tif (!git_parse_int(value, &ret))\n     --\t\tdie_bad_number(cf, name, value);\n     +-\t\tdie_bad_number(cf_global, name, value);\n      +\t\tdie_bad_number(the_reader.source, name, value);\n       \treturn ret;\n       }\n     @@ config.c: int64_t git_config_int64(const char *name, const char *value)\n       {\n       \tint64_t ret;\n       \tif (!git_parse_int64(value, &ret))\n     --\t\tdie_bad_number(cf, name, value);\n     +-\t\tdie_bad_number(cf_global, name, value);\n      +\t\tdie_bad_number(the_reader.source, name, value);\n       \treturn ret;\n       }\n     @@ config.c: unsigned long git_config_ulong(const char *name, const char *value)\n       {\n       \tunsigned long ret;\n       \tif (!git_parse_ulong(value, &ret))\n     --\t\tdie_bad_number(cf, name, value);\n     +-\t\tdie_bad_number(cf_global, name, value);\n      +\t\tdie_bad_number(the_reader.source, name, value);\n       \treturn ret;\n       }\n     @@ config.c: ssize_t git_config_ssize_t(const char *name, const char *value)\n       {\n       \tssize_t ret;\n       \tif (!git_parse_ssize_t(value, &ret))\n     --\t\tdie_bad_number(cf, name, value);\n     +-\t\tdie_bad_number(cf_global, name, value);\n      +\t\tdie_bad_number(the_reader.source, name, value);\n       \treturn ret;\n       }\n     @@ config.c: static int do_config_from(struct config_source *top, config_fn_t fn, v\n       \ttop->total_len = 0;\n       \tstrbuf_init(&top->value, 1024);\n       \tstrbuf_init(&top->var, 1024);\n     --\tconfig_state_push_source(top);\n     +-\tconfig_reader_push_source(top);\n      +\tconfig_reader_push_source(reader, top);\n       \n       \tret = git_parse_source(top, fn, data, opts);\n     @@ config.c: static int do_config_from(struct config_source *top, config_fn_t fn, v\n       \t/* pop config-file parsing state stack */\n       \tstrbuf_release(&top->value);\n       \tstrbuf_release(&top->var);\n     --\tconfig_state_pop_source();\n     +-\tconfig_reader_pop_source();\n      +\tconfig_reader_pop_source(reader);\n       \n       \treturn ret;\n     @@ config.c: const char *current_config_origin_type(void)\n       \tint type;\n       \tif (current_config_kvi)\n       \t\ttype = current_config_kvi->origin_type;\n     --\telse if(cf)\n     --\t\ttype = cf->origin_type;\n     +-\telse if(cf_global)\n     +-\t\ttype = cf_global->origin_type;\n      +\telse if(the_reader.source)\n      +\t\ttype = the_reader.source->origin_type;\n       \telse\n     @@ config.c: const char *current_config_name(void)\n       \tconst char *name;\n       \tif (current_config_kvi)\n       \t\tname = current_config_kvi->filename;\n     --\telse if (cf)\n     --\t\tname = cf->name;\n     +-\telse if (cf_global)\n     +-\t\tname = cf_global->name;\n      +\telse if (the_reader.source)\n      +\t\tname = the_reader.source->name;\n       \telse\n     @@ config.c: int current_config_line(void)\n       \tif (current_config_kvi)\n       \t\treturn current_config_kvi->linenr;\n       \telse\n     --\t\treturn cf->linenr;\n     +-\t\treturn cf_global->linenr;\n      +\t\treturn the_reader.source->linenr;\n       }\n       \n 4:  74a63fed705 ! 4:  22b69971749 config.c: plumb the_reader through callbacks\n     @@ Metadata\n       ## Commit message ##\n          config.c: plumb the_reader through callbacks\n      \n     -    The remaining references to \"cf\" are in config callback functions.\n     -    Remove them by plumbing \"struct config_reader\" via the \"*data\" arg.\n     +    The remaining references to \"cf_global\" are in config callback\n     +    functions. Remove them by plumbing \"struct config_reader\" via the\n     +    \"*data\" arg.\n      \n     -    **RFC NOTE** If we had a way to expose \"struct config_reader\" to the\n     -    config callback functions (the 'extra work' in the cover letter), we\n     -    wouldn't need to also pass it via the \"*data\" arg. This is more of a\n     -    hack to avoid doing that work now.\n     +    In both of the callbacks here, we are only reading from\n     +    \"reader->source\". So in the long run, if we had a way to expose readonly\n     +    information from \"reader->source\" (probably in the form of \"struct\n     +    key_value_info\"), we could undo this patch (i.e. remove \"struct\n     +    config_reader\" fom \"*data\").\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \n     @@ config.c: struct config_reader {\n       static struct config_reader the_reader;\n       \n       /*\n     -+ * FIXME The comments are temporarily out of date since \"cf\" been moved to\n     -+ * the_reader, but not current_*.\n     ++ * FIXME The comments are temporarily out of date since \"cf_global\" has been\n     ++ * moved to the_reader, but not current_*.\n      + *\n        * These variables record the \"current\" config source, which\n        * can be accessed by parsing callbacks.\n     @@ config.c: static struct config_reader the_reader;\n        * or it's a function which can be reused for non-config purposes, and should\n        * fall back to some sane behavior).\n      - *\n     -- * FIXME \"cf\" has been replaced by \"the_reader.source\", remove\n     -- * \"cf\" once we plumb \"the_reader\" through all of the callback functions.\n     +- * FIXME \"cf_global\" has been replaced by \"the_reader.source\", remove\n     +- * \"cf_global\" once we plumb \"the_reader\" through all of the callback functions.\n        */\n     --static struct config_source *cf;\n     +-static struct config_source *cf_global;\n       static struct key_value_info *current_config_kvi;\n       \n       /*\n     @@ config.c: static inline void config_reader_push_source(struct config_reader *rea\n       \tif (reader->source)\n       \t\ttop->prev = reader->source;\n       \treader->source = top;\n     --\t/* FIXME remove this when cf is removed. */\n     --\tcf = reader->source;\n     +-\t/* FIXME remove this when cf_global is removed. */\n     +-\tcf_global = reader->source;\n       }\n       \n       static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n     @@ config.c: static inline struct config_source *config_reader_pop_source(struct co\n       \tret = reader->source;\n       \treader->source = reader->source->prev;\n      -\t/* FIXME remove this when cf is removed. */\n     --\tcf = reader->source;\n     +-\tcf_global = reader->source;\n       \treturn ret;\n       }\n       \n     @@ config.c: struct config_include_data {\n       \n       \t/*\n       \t * All remote URLs discovered when reading all config files.\n     -@@ config.c: static int include_condition_is_true(struct config_source *cs,\n     +@@ config.c: static int include_condition_is_true(struct config_source *cf,\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 config_source *cf = inc->config_reader->source;\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\n       \t\treturn ret;\n       \n       \tif (!strcmp(var, \"include.path\"))\n     --\t\tret = handle_path_include(cf, value, inc);\n     -+\t\tret = handle_path_include(cs, value, inc);\n     +-\t\tret = handle_path_include(cf_global, value, inc);\n     ++\t\tret = handle_path_include(cf, value, inc);\n       \n       \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n     --\t    cond && include_condition_is_true(cf, inc, cond, cond_len) &&\n     -+\t    cond && include_condition_is_true(cs, inc, cond, cond_len) &&\n     +-\t    cond && include_condition_is_true(cf_global, inc, cond, cond_len) &&\n     ++\t    cond && include_condition_is_true(cf, 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(cf, value, inc);\n     -+\t\tret = handle_path_include(cs, value, inc);\n     +-\t\tret = handle_path_include(cf_global, value, inc);\n     ++\t\tret = handle_path_include(cf, value, inc);\n       \t\tinc->fn = old_fn;\n       \t}\n       \n     @@ config.c: static int configset_add_value(struct config_set *cs, const char *key,\n       \tl_item->e = e;\n       \tl_item->value_index = e->value_list.nr - 1;\n       \n     --\tif (!cf)\n     +-\tif (!cf_global)\n      +\tif (!reader->source)\n       \t\tBUG(\"configset_add_value has no source\");\n     --\tif (cf->name) {\n     --\t\tkv_info->filename = strintern(cf->name);\n     --\t\tkv_info->linenr = cf->linenr;\n     --\t\tkv_info->origin_type = cf->origin_type;\n     +-\tif (cf_global->name) {\n     +-\t\tkv_info->filename = strintern(cf_global->name);\n     +-\t\tkv_info->linenr = cf_global->linenr;\n     +-\t\tkv_info->origin_type = cf_global->origin_type;\n      +\tif (reader->source->name) {\n      +\t\tkv_info->filename = strintern(reader->source->name);\n      +\t\tkv_info->linenr = reader->source->linenr;\n     @@ config.c: static int store_aux_event(enum config_event_t type,\n       \t\t\t   size_t begin, size_t end, void *data)\n       {\n       \tstruct config_store_data *store = data;\n     -+\tstruct config_source *cs = store->config_reader->source;\n     +-\t/*\n     +-\t * FIXME Keep using \"cf\" so that we can avoid rewrapping a\n     +-\t * really long line below. Remove this when \"cf\" gets plumbed\n     +-\t * correctly.\n     +-\t */\n     +-\tstruct config_source *cf = cf_global;\n     ++\tstruct config_source *cf = 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     -@@ config.c: static int store_aux_event(enum config_event_t type,\n     - \tif (type == CONFIG_EVENT_SECTION) {\n     - \t\tint (*cmpfn)(const char *, const char *, size_t);\n     - \n     --\t\tif (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')\n     --\t\t\treturn error(_(\"invalid section name '%s'\"), cf->var.buf);\n     -+\t\tif (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')\n     -+\t\t\treturn error(_(\"invalid section name '%s'\"), cs->var.buf);\n     - \n     --\t\tif (cf->subsection_case_sensitive)\n     -+\t\tif (cs->subsection_case_sensitive)\n     - \t\t\tcmpfn = strncasecmp;\n     - \t\telse\n     - \t\t\tcmpfn = strncmp;\n     -@@ config.c: static int store_aux_event(enum config_event_t type,\n     - \t\t/* Is this the section we were looking for? */\n     - \t\tstore->is_keys_section =\n     - \t\t\tstore->parsed[store->parsed_nr].is_keys_section =\n     --\t\t\tcf->var.len - 1 == store->baselen &&\n     --\t\t\t!cmpfn(cf->var.buf, store->key, store->baselen);\n     -+\t\t\tcs->var.len - 1 == store->baselen &&\n     -+\t\t\t!cmpfn(cs->var.buf, store->key, store->baselen);\n     - \t\tif (store->is_keys_section) {\n     - \t\t\tstore->section_seen = 1;\n     - \t\t\tALLOC_GROW(store->seen, store->seen_nr + 1,\n      @@ config.c: int git_config_set_multivar_in_file_gently(const char *config_filename,\n       \tchar *filename_buf = NULL;\n       \tchar *contents = NULL;\n 5:  c05b33ab29d ! 5:  afb6e3e318d config.c: remove current_config_kvi\n     @@ Commit message\n          config.c: remove current_config_kvi\n      \n          Add \".config_kvi\" to \"struct config_reader\" and replace\n     -    \"current_config_kvi\" with \"the_reader.config_kvi\", either in-place (in\n     -    public functions) or by passing \"the_reader\" to the \"*data\" arg of\n     -    callback functions.\n     +    \"current_config_kvi\" with \"the_reader.config_kvi\", plumbing \"struct\n     +    config_reader\" where necesssary.\n      \n          Also, introduce a setter function for \".config_kvi\", which allows us to\n          enforce the contraint that only one of \".source\" and \".config_kvi\" can\n     @@ config.c: struct config_source {\n       \tstruct config_source *source;\n      +\tstruct key_value_info *config_kvi;\n       };\n     - /* Only public functions should reference the_reader. */\n     + /*\n     +  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n     +@@ config.c: struct config_reader {\n     +  */\n       static struct config_reader the_reader;\n       \n      -/*\n     -- * FIXME The comments are temporarily out of date since \"cf\" been moved to\n     -- * the_reader, but not current_*.\n     +- * FIXME The comments are temporarily out of date since \"cf_global\" has been\n     +- * moved to the_reader, but not current_*.\n      - *\n      - * These variables record the \"current\" config source, which\n      - * can be accessed by parsing callbacks.\n      - *\n     -- * The \"cf\" variable will be non-NULL only when we are actually parsing a real\n     -- * config source (file, blob, cmdline, etc).\n     +- * The \"cf_global\" variable will be non-NULL only when we are actually\n     +- * parsing a real config source (file, blob, cmdline, etc).\n      - *\n      - * The \"current_config_kvi\" variable will be non-NULL only when we are feeding\n      - * cached config from a configset into a callback.\n     @@ config.c: static enum config_scope current_parsing_scope;\n       \t\t\t\t\t     struct config_source *top)\n       {\n      +\tif (reader->config_kvi)\n     -+\t\tBUG(\"source should only be set when parsing a config source\");\n     ++\t\tBUG(\"source should not be set while iterating a config set\");\n       \tif (reader->source)\n       \t\ttop->prev = reader->source;\n       \treader->source = top;\n     @@ config.c: static inline struct config_source *config_reader_pop_source(struct co\n      +\t\t\t\t\t struct key_value_info *kvi)\n      +{\n      +\tif (kvi && reader->source)\n     -+\t\tBUG(\"kvi should only be set when iterating through configset\");\n     ++\t\tBUG(\"kvi should not be set while parsing a config source\");\n      +\treader->config_kvi = kvi;\n      +}\n      +\n     @@ config.c: int config_with_options(config_fn_t fn, void *data,\n       \treturn ret;\n       }\n       \n     -+struct configset_iter_data {\n     -+\tstruct config_reader *config_reader;\n     -+\tvoid *inner;\n     -+};\n     -+#define CONFIGSET_ITER_INIT { 0 }\n     -+\n     - static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n     +-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n     ++static void configset_iter(struct config_reader *reader, struct config_set *cs,\n     ++\t\t\t   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 = &cs->list;\n     -+\tstruct configset_iter_data *iter_data = data;\n     - \n     - \tfor (i = 0; i < list->nr; i++) {\n     -+\t\tstruct key_value_info *kvi;\n     - \t\tentry = list->items[i].e;\n     +@@ config.c: static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n       \t\tvalue_index = list->items[i].value_index;\n       \t\tvalues = &entry->value_list;\n       \n      -\t\tcurrent_config_kvi = values->items[value_index].util;\n     -+\t\tkvi = values->items[value_index].util;\n     -+\t\tconfig_reader_set_kvi(iter_data->config_reader, kvi);\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\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      current_config_kvi->filename,\n      -\t\t\t\t\t      current_config_kvi->linenr);\n     -+\t\tif (fn(entry->key, values->items[value_index].string, iter_data->inner) < 0)\n     -+\t\t\tgit_die_config_linenr(entry->key, kvi->filename,\n     -+\t\t\t\t\t      kvi->linenr);\n     ++\t\t\t\t\t      reader->config_kvi->filename,\n     ++\t\t\t\t\t      reader->config_kvi->linenr);\n       \n      -\t\tcurrent_config_kvi = NULL;\n     -+\t\tconfig_reader_set_kvi(iter_data->config_reader, NULL);\n     ++\t\tconfig_reader_set_kvi(reader, NULL);\n       \t}\n       }\n       \n      @@ config.c: static void repo_config_clear(struct repository *repo)\n     - \tgit_configset_clear(repo->config);\n     - }\n     - \n     --void repo_config(struct repository *repo, config_fn_t fn, void *data)\n     -+void repo_config(struct repository *repo, config_fn_t fn, void *data_inner)\n     + void repo_config(struct repository *repo, config_fn_t fn, void *data)\n       {\n     -+\tstruct configset_iter_data data = CONFIGSET_ITER_INIT;\n     -+\tdata.inner = data_inner;\n     -+\tdata.config_reader = &the_reader;\n     -+\n       \tgit_config_check_init(repo);\n      -\tconfigset_iter(repo->config, fn, data);\n     -+\tconfigset_iter(repo->config, fn, &data);\n     ++\tconfigset_iter(&the_reader, repo->config, fn, data);\n       }\n       \n       int repo_config_get_value(struct repository *repo,\n     -@@ config.c: static void read_protected_config(void)\n     - \tconfig_with_options(config_set_callback, &data, NULL, &opts);\n     - }\n     - \n     --void git_protected_config(config_fn_t fn, void *data)\n     -+void git_protected_config(config_fn_t fn, void *data_inner)\n     +@@ config.c: void git_protected_config(config_fn_t fn, void *data)\n       {\n     -+\tstruct configset_iter_data data = CONFIGSET_ITER_INIT;\n       \tif (!protected_config.hash_initialized)\n       \t\tread_protected_config();\n      -\tconfigset_iter(&protected_config, fn, data);\n     -+\tdata.inner = data_inner;\n     -+\tdata.config_reader = &the_reader;\n     -+\n     -+\tconfigset_iter(&protected_config, fn, &data);\n     ++\tconfigset_iter(&the_reader, &protected_config, fn, data);\n       }\n       \n       /* Functions used historically to read configuration from 'the_repository' */\n 6:  b1e866f9216 ! 6:  a57e35163ae config.c: remove current_parsing_scope\n     @@ config.c: struct config_reader {\n      +\t */\n      +\tenum config_scope parsing_scope;\n       };\n     - /* Only public functions should reference the_reader. */\n     + /*\n     +  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n     +@@ config.c: struct config_reader {\n     +  */\n       static struct config_reader the_reader;\n       \n      -/*\n      - * Similar to the variables above, this gives access to the \"scope\" of the\n      - * current value (repo, global, etc). For cached values, it can be found via\n      - * the current_config_kvi as above. During parsing, the current value can be\n     -- * found in this variable. It's not part of \"cf\" because it transcends a single\n     -- * file (i.e., a file included from .git/config is still in \"repo\" scope).\n     +- * found in this variable. It's not part of \"cf_global\" because it transcends a\n     +- * single file (i.e., a file included from .git/config is still in \"repo\"\n     +- * scope).\n      - */\n      -static enum config_scope current_parsing_scope;\n      -\n     @@ config.c: static inline struct config_source *config_reader_pop_source(struct co\n       {\n      -\tif (kvi && reader->source)\n      +\tif (kvi && (reader->source || reader->parsing_scope))\n     - \t\tBUG(\"kvi should only be set when iterating through configset\");\n     + \t\tBUG(\"kvi should not be set while parsing a config source\");\n       \treader->config_kvi = kvi;\n       }\n       \n -:  ----------- > 7:  3c83d9535a0 config: report cached filenames in die_bad_number()\n 1:  ad513d832d8 ! 8:  9aec9092fdf config.c: plumb config_source through static fns\n     @@ Metadata\n      Author: Glen Choo <chooglen@google.com>\n      \n       ## Commit message ##\n     -    config.c: plumb config_source through static fns\n     +    config.c: rename \"struct config_source cf\"\n      \n     -    This reduces the direct dependence on the \"cf\" static variable, which\n     -    will make it easier to remove in a later commit. The plumbed arg is\n     -    named \"cs\" to differentiate between it and the static variable.\n     -\n     -    In some cases (public functions and config callback functions), there\n     -    isn't an obvious way to plumb \"struct config_source\" through function\n     -    args. As a workaround, add references to \"cf\" that we'll address in\n     -    later commits.\n     -\n     -    The remaining references to \"cf\" are direct assignments to \"cf\", which\n     -    we'll also address in a later commit.\n     +    The \"cf\" name is a holdover from before 4d8dd1494e (config: make parsing\n     +    stack struct independent from actual data source, 2013-07-12), when the\n     +    struct was named config_file. Since that acronym no longer makes sense,\n     +    rename \"cf\" to \"cs\". In some places, we have \"struct config_set cs\", so\n     +    to avoid conflict, rename those \"cs\" to \"set\" (\"config_set\" would be\n     +    more descriptive, but it's much longer and would require us to rewrap\n     +    several lines).\n      \n          Signed-off-by: Glen Choo <chooglen@google.com>\n      \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(const char *path, struct config_include_data *inc)\n     +-static int handle_path_include(struct config_source *cf, const char *path,\n      +static int handle_path_include(struct config_source *cs, const char *path,\n     -+\t\t\t       struct config_include_data *inc)\n     + \t\t\t       struct config_include_data *inc)\n       {\n       \tint ret = 0;\n     - \tstruct strbuf buf = STRBUF_INIT;\n     -@@ config.c: static int handle_path_include(const char *path, struct config_include_data *inc\n     +@@ config.c: static int handle_path_include(struct config_source *cf, const char *path,\n       \tif (!is_absolute_path(path)) {\n       \t\tchar *slash;\n       \n     @@ config.c: static int handle_path_include(const char *path, struct config_include\n       \t\tstrbuf_addstr(&buf, path);\n       \t\tpath = buf.buf;\n       \t}\n     -@@ config.c: static int handle_path_include(const char *path, struct config_include_data *inc\n     +@@ config.c: static int handle_path_include(struct config_source *cf, 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     @@ config.c: 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 strbuf *pat)\n     +-static int prepare_include_condition_pattern(struct config_source *cf,\n      +static int prepare_include_condition_pattern(struct config_source *cs,\n     -+\t\t\t\t\t     struct strbuf *pat)\n     + \t\t\t\t\t     struct strbuf *pat)\n       {\n       \tstruct strbuf path = STRBUF_INIT;\n     - \tchar *expanded;\n     -@@ config.c: static int prepare_include_condition_pattern(struct strbuf *pat)\n     +@@ config.c: static int prepare_include_condition_pattern(struct config_source *cf,\n       \tif (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {\n       \t\tconst char *slash;\n       \n     @@ config.c: static int prepare_include_condition_pattern(struct strbuf *pat)\n       \t\tslash = find_last_dir_sep(path.buf);\n       \t\tif (!slash)\n       \t\t\tBUG(\"how is this possible?\");\n     -@@ config.c: static int prepare_include_condition_pattern(struct strbuf *pat)\n     +@@ config.c: static int prepare_include_condition_pattern(struct config_source *cf,\n       \treturn prefix;\n       }\n       \n     --static int include_by_gitdir(const struct config_options *opts,\n     +-static int include_by_gitdir(struct config_source *cf,\n      +static int include_by_gitdir(struct config_source *cs,\n     -+\t\t\t     const struct config_options *opts,\n     + \t\t\t     const struct config_options *opts,\n       \t\t\t     const char *cond, size_t cond_len, int icase)\n       {\n     - \tstruct strbuf text = STRBUF_INIT;\n     -@@ config.c: static int include_by_gitdir(const struct config_options *opts,\n     +@@ config.c: static int include_by_gitdir(struct config_source *cf,\n       \n       \tstrbuf_realpath(&text, git_dir, 1);\n       \tstrbuf_add(&pattern, cond, cond_len);\n     --\tprefix = prepare_include_condition_pattern(&pattern);\n     +-\tprefix = prepare_include_condition_pattern(cf, &pattern);\n      +\tprefix = prepare_include_condition_pattern(cs, &pattern);\n       \n       again:\n     @@ config.c: 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_include_data *inc,\n     +-static int include_condition_is_true(struct config_source *cf,\n      +static int include_condition_is_true(struct config_source *cs,\n     -+\t\t\t\t     struct config_include_data *inc,\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(opts, cond, cond_len, 0);\n     +-\t\treturn include_by_gitdir(cf, opts, cond, cond_len, 0);\n      +\t\treturn include_by_gitdir(cs, 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(opts, cond, cond_len, 1);\n     +-\t\treturn include_by_gitdir(cf, opts, cond, cond_len, 1);\n      +\t\treturn include_by_gitdir(cs, 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     +@@ config.c: static int include_condition_is_true(struct config_source *cf,\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 *cf = inc->config_reader->source;\n     ++\tstruct config_source *cs = inc->config_reader->source;\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       \t\treturn ret;\n       \n       \tif (!strcmp(var, \"include.path\"))\n     --\t\tret = handle_path_include(value, inc);\n     -+\t\tret = handle_path_include(cf, value, inc);\n     +-\t\tret = handle_path_include(cf, value, inc);\n     ++\t\tret = handle_path_include(cs, value, inc);\n       \n       \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n     --\t    cond && include_condition_is_true(inc, cond, cond_len) &&\n     -+\t    cond && include_condition_is_true(cf, inc, cond, cond_len) &&\n     +-\t    cond && include_condition_is_true(cf, inc, cond, cond_len) &&\n     ++\t    cond && include_condition_is_true(cs, 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(value, inc);\n     -+\t\tret = handle_path_include(cf, value, inc);\n     +-\t\tret = handle_path_include(cf, value, inc);\n     ++\t\tret = handle_path_include(cs, value, inc);\n       \t\tinc->fn = old_fn;\n       \t}\n       \n     @@ config.c: out:\n       \treturn ret;\n       }\n       \n     --static int get_next_char(void)\n     +-static int get_next_char(struct config_source *cf)\n      +static int get_next_char(struct config_source *cs)\n       {\n      -\tint c = cf->do_fgetc(cf);\n     @@ config.c: out:\n       \t\t/*\n       \t\t * This is an absurdly long config file; refuse to parse\n       \t\t * further in order to protect downstream code from integer\n     -@@ config.c: static int get_next_char(void)\n     +@@ config.c: static int get_next_char(struct config_source *cf)\n       \t\t * but we can mark EOF and put trash in the return value,\n       \t\t * which will trigger a parse error.\n       \t\t */\n     @@ config.c: static int get_next_char(void)\n       \treturn c;\n       }\n       \n     --static char *parse_value(void)\n     +-static char *parse_value(struct config_source *cf)\n      +static char *parse_value(struct config_source *cs)\n       {\n       \tint quote = 0, comment = 0, space = 0;\n     @@ config.c: static int get_next_char(void)\n      -\tstrbuf_reset(&cf->value);\n      +\tstrbuf_reset(&cs->value);\n       \tfor (;;) {\n     --\t\tint c = get_next_char();\n     +-\t\tint c = get_next_char(cf);\n      +\t\tint c = get_next_char(cs);\n       \t\tif (c == '\\n') {\n       \t\t\tif (quote) {\n     @@ config.c: static int get_next_char(void)\n       \t\t\t\tspace++;\n       \t\t\tcontinue;\n       \t\t}\n     -@@ config.c: static char *parse_value(void)\n     +@@ config.c: static char *parse_value(struct config_source *cf)\n       \t\t\t}\n       \t\t}\n       \t\tfor (; space; space--)\n      -\t\t\tstrbuf_addch(&cf->value, ' ');\n      +\t\t\tstrbuf_addch(&cs->value, ' ');\n       \t\tif (c == '\\\\') {\n     --\t\t\tc = get_next_char();\n     +-\t\t\tc = get_next_char(cf);\n      +\t\t\tc = get_next_char(cs);\n       \t\t\tswitch (c) {\n       \t\t\tcase '\\n':\n       \t\t\t\tcontinue;\n     -@@ config.c: static char *parse_value(void)\n     +@@ config.c: static char *parse_value(struct config_source *cf)\n       \t\t\tdefault:\n       \t\t\t\treturn NULL;\n       \t\t\t}\n     @@ config.c: static char *parse_value(void)\n       \t}\n       }\n       \n     --static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n     +-static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n      +static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n     -+\t\t     struct strbuf *name)\n     + \t\t     struct strbuf *name)\n       {\n       \tint c;\n     - \tchar *value;\n     -@@ config.c: static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n     +@@ config.c: static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n       \n       \t/* Get the full name */\n       \tfor (;;) {\n     --\t\tc = get_next_char();\n     +-\t\tc = get_next_char(cf);\n      -\t\tif (cf->eof)\n      +\t\tc = get_next_char(cs);\n      +\t\tif (cs->eof)\n       \t\t\tbreak;\n       \t\tif (!iskeychar(c))\n       \t\t\tbreak;\n     -@@ config.c: static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n     +@@ config.c: static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n       \t}\n       \n       \twhile (c == ' ' || c == '\\t')\n     --\t\tc = get_next_char();\n     +-\t\tc = get_next_char(cf);\n      +\t\tc = get_next_char(cs);\n       \n       \tvalue = NULL;\n       \tif (c != '\\n') {\n       \t\tif (c != '=')\n       \t\t\treturn -1;\n     --\t\tvalue = parse_value();\n     +-\t\tvalue = parse_value(cf);\n      +\t\tvalue = parse_value(cs);\n       \t\tif (!value)\n       \t\t\treturn -1;\n       \t}\n     -@@ config.c: static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n     +@@ config.c: static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n       \t * the line we just parsed during the call to fn to get\n       \t * accurate line number in error messages.\n       \t */\n     @@ config.c: static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n       \treturn ret;\n       }\n       \n     --static int get_extended_base_var(struct strbuf *name, int c)\n     +-static int get_extended_base_var(struct config_source *cf, struct strbuf *name,\n      +static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n     -+\t\t\t\t int c)\n     + \t\t\t\t int c)\n       {\n      -\tcf->subsection_case_sensitive = 0;\n      +\tcs->subsection_case_sensitive = 0;\n       \tdo {\n       \t\tif (c == '\\n')\n       \t\t\tgoto error_incomplete_line;\n     --\t\tc = get_next_char();\n     +-\t\tc = get_next_char(cf);\n      +\t\tc = get_next_char(cs);\n       \t} while (isspace(c));\n       \n       \t/* We require the format to be '[base \"extension\"]' */\n     -@@ config.c: static int get_extended_base_var(struct strbuf *name, int c)\n     +@@ config.c: static int get_extended_base_var(struct config_source *cf, struct strbuf *name,\n       \tstrbuf_addch(name, '.');\n       \n       \tfor (;;) {\n     --\t\tint c = get_next_char();\n     +-\t\tint c = get_next_char(cf);\n      +\t\tint c = get_next_char(cs);\n       \t\tif (c == '\\n')\n       \t\t\tgoto error_incomplete_line;\n       \t\tif (c == '\"')\n       \t\t\tbreak;\n       \t\tif (c == '\\\\') {\n     --\t\t\tc = get_next_char();\n     +-\t\t\tc = get_next_char(cf);\n      +\t\t\tc = get_next_char(cs);\n       \t\t\tif (c == '\\n')\n       \t\t\t\tgoto error_incomplete_line;\n       \t\t}\n     -@@ config.c: static int get_extended_base_var(struct strbuf *name, int c)\n     +@@ config.c: static int get_extended_base_var(struct config_source *cf, struct strbuf *name,\n       \t}\n       \n       \t/* Final ']' */\n     --\tif (get_next_char() != ']')\n     +-\tif (get_next_char(cf) != ']')\n      +\tif (get_next_char(cs) != ']')\n       \t\treturn -1;\n       \treturn 0;\n     @@ config.c: static int get_extended_base_var(struct strbuf *name, int c)\n       \treturn -1;\n       }\n       \n     --static int get_base_var(struct strbuf *name)\n     +-static int get_base_var(struct config_source *cf, struct strbuf *name)\n      +static int get_base_var(struct config_source *cs, struct strbuf *name)\n       {\n      -\tcf->subsection_case_sensitive = 1;\n      +\tcs->subsection_case_sensitive = 1;\n       \tfor (;;) {\n     --\t\tint c = get_next_char();\n     +-\t\tint c = get_next_char(cf);\n      -\t\tif (cf->eof)\n      +\t\tint c = get_next_char(cs);\n      +\t\tif (cs->eof)\n     @@ config.c: static int get_extended_base_var(struct strbuf *name, int c)\n       \t\tif (c == ']')\n       \t\t\treturn 0;\n       \t\tif (isspace(c))\n     --\t\t\treturn get_extended_base_var(name, c);\n     +-\t\t\treturn get_extended_base_var(cf, name, c);\n      +\t\t\treturn get_extended_base_var(cs, name, c);\n       \t\tif (!iskeychar(c) && c != '.')\n       \t\t\treturn -1;\n     @@ config.c: struct parse_event_data {\n       \tconst struct config_options *opts;\n       };\n       \n     --static int do_event(enum config_event_t type, struct parse_event_data *data)\n     +-static int do_event(struct config_source *cf, enum config_event_t type,\n      +static int do_event(struct config_source *cs, enum config_event_t type,\n     -+\t\t    struct parse_event_data *data)\n     + \t\t    struct parse_event_data *data)\n       {\n       \tsize_t offset;\n     - \n     -@@ config.c: static int do_event(enum config_event_t type, struct parse_event_data *data)\n     +@@ config.c: static int do_event(struct config_source *cf, enum config_event_t type,\n       \t    data->previous_type == type)\n       \t\treturn 0;\n       \n     @@ config.c: static int do_event(enum config_event_t type, struct parse_event_data\n       \t/*\n       \t * At EOF, the parser always \"inserts\" an extra '\\n', therefore\n       \t * the end offset of the event is the current file position, otherwise\n     -@@ config.c: static int do_event(enum config_event_t type, struct parse_event_data *data)\n     +@@ config.c: static int do_event(struct config_source *cf, enum config_event_t type,\n       \treturn 0;\n       }\n       \n     --static int git_parse_source(config_fn_t fn, void *data,\n     --\t\t\t    const struct config_options *opts)\n     +-static int git_parse_source(struct config_source *cf, config_fn_t fn,\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    void *data, const struct config_options *opts)\n       {\n       \tint comment = 0;\n       \tsize_t baselen = 0;\n     @@ config.c: static int do_event(enum config_event_t type, struct parse_event_data\n       \tint error_return = 0;\n       \tchar *error_msg = NULL;\n       \n     -@@ config.c: static int git_parse_source(config_fn_t fn, void *data,\n     +@@ config.c: static int git_parse_source(struct config_source *cf, config_fn_t fn,\n       \tfor (;;) {\n       \t\tint c;\n       \n     --\t\tc = get_next_char();\n     +-\t\tc = get_next_char(cf);\n      +\t\tc = get_next_char(cs);\n       \t\tif (bomptr && *bomptr) {\n       \t\t\t/* We are at the file beginning; skip UTF8-encoded BOM\n       \t\t\t * if present. Sane editors won't put this in on their\n     -@@ config.c: static int git_parse_source(config_fn_t fn, void *data,\n     +@@ config.c: static int git_parse_source(struct config_source *cf, config_fn_t fn,\n       \t\t\t}\n       \t\t}\n       \t\tif (c == '\\n') {\n      -\t\t\tif (cf->eof) {\n     --\t\t\t\tif (do_event(CONFIG_EVENT_EOF, &event_data) < 0)\n     +-\t\t\t\tif (do_event(cf, CONFIG_EVENT_EOF, &event_data) < 0)\n      +\t\t\tif (cs->eof) {\n      +\t\t\t\tif (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)\n       \t\t\t\t\treturn -1;\n       \t\t\t\treturn 0;\n       \t\t\t}\n     --\t\t\tif (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n     +-\t\t\tif (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n      +\t\t\tif (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n       \t\t\t\treturn -1;\n       \t\t\tcomment = 0;\n       \t\t\tcontinue;\n     -@@ config.c: static int git_parse_source(config_fn_t fn, void *data,\n     +@@ config.c: static int git_parse_source(struct config_source *cf, config_fn_t fn,\n       \t\tif (comment)\n       \t\t\tcontinue;\n       \t\tif (isspace(c)) {\n     --\t\t\tif (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n     +-\t\t\tif (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n      +\t\t\tif (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n       \t\t\t\t\treturn -1;\n       \t\t\tcontinue;\n       \t\t}\n       \t\tif (c == '#' || c == ';') {\n     --\t\t\tif (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0)\n     +-\t\t\tif (do_event(cf, CONFIG_EVENT_COMMENT, &event_data) < 0)\n      +\t\t\tif (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)\n       \t\t\t\t\treturn -1;\n       \t\t\tcomment = 1;\n       \t\t\tcontinue;\n       \t\t}\n       \t\tif (c == '[') {\n     --\t\t\tif (do_event(CONFIG_EVENT_SECTION, &event_data) < 0)\n     +-\t\t\tif (do_event(cf, CONFIG_EVENT_SECTION, &event_data) < 0)\n      +\t\t\tif (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)\n       \t\t\t\t\treturn -1;\n       \n       \t\t\t/* Reset prior to determining a new stem */\n       \t\t\tstrbuf_reset(var);\n     --\t\t\tif (get_base_var(var) < 0 || var->len < 1)\n     +-\t\t\tif (get_base_var(cf, var) < 0 || var->len < 1)\n      +\t\t\tif (get_base_var(cs, var) < 0 || var->len < 1)\n       \t\t\t\tbreak;\n       \t\t\tstrbuf_addch(var, '.');\n       \t\t\tbaselen = var->len;\n     -@@ config.c: static int git_parse_source(config_fn_t fn, void *data,\n     +@@ config.c: static int git_parse_source(struct config_source *cf, config_fn_t fn,\n       \t\tif (!isalpha(c))\n       \t\t\tbreak;\n       \n     --\t\tif (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0)\n     +-\t\tif (do_event(cf, CONFIG_EVENT_ENTRY, &event_data) < 0)\n      +\t\tif (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)\n       \t\t\treturn -1;\n       \n       \t\t/*\n     -@@ config.c: static int git_parse_source(config_fn_t fn, void *data,\n     +@@ config.c: static int git_parse_source(struct config_source *cf, 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(fn, data, var) < 0)\n     +-\t\tif (get_value(cf, fn, data, var) < 0)\n      +\t\tif (get_value(cs, fn, data, var) < 0)\n       \t\t\tbreak;\n       \t}\n       \n     --\tif (do_event(CONFIG_EVENT_ERROR, &event_data) < 0)\n     +-\tif (do_event(cf, CONFIG_EVENT_ERROR, &event_data) < 0)\n      +\tif (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)\n       \t\treturn -1;\n       \n     @@ config.c: static int git_parse_source(config_fn_t fn, void *data,\n       \tcase CONFIG_ERROR_DIE:\n       \t\tdie(\"%s\", error_msg);\n       \t\tbreak;\n     -@@ config.c: int git_parse_ssize_t(const char *value, ssize_t *ret)\n     +@@ config.c: int config_with_options(config_fn_t fn, void *data,\n     + \treturn ret;\n       }\n       \n     - NORETURN\n     --static void die_bad_number(const char *name, const char *value)\n     -+static void die_bad_number(struct config_source *cs, const char *name,\n     -+\t\t\t   const char *value)\n     +-static void configset_iter(struct config_reader *reader, struct config_set *cs,\n     ++static void configset_iter(struct config_reader *reader, struct config_set *set,\n     + \t\t\t   config_fn_t fn, void *data)\n       {\n     - \tconst char *error_type = (errno == ERANGE) ?\n     - \t\tN_(\"out of range\") : N_(\"invalid unit\");\n     -@@ config.c: static void die_bad_number(const char *name, const char *value)\n     - \tif (!value)\n     - \t\tvalue = \"\";\n     + \tint i, value_index;\n     + \tstruct string_list *values;\n     + \tstruct config_set_element *entry;\n     +-\tstruct configset_list *list = &cs->list;\n     ++\tstruct configset_list *list = &set->list;\n     + \n     + \tfor (i = 0; i < list->nr; i++) {\n     + \t\tentry = list->items[i].e;\n     +@@ config.c: void read_very_early_config(config_fn_t cb, void *data)\n     + \tconfig_with_options(cb, data, NULL, &opts);\n     + }\n       \n     --\tif (!(cf && cf->name))\n     -+\tif (!(cs && cs->name))\n     - \t\tdie(_(bad_numeric), value, name, _(error_type));\n     +-static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)\n     ++static struct config_set_element *configset_find_element(struct config_set *set, const char *key)\n     + {\n     + \tstruct config_set_element k;\n     + \tstruct config_set_element *found_entry;\n     +@@ config.c: static struct config_set_element *configset_find_element(struct config_set *cs,\n     + \n     + \thashmap_entry_init(&k.ent, strhash(normalized_key));\n     + \tk.key = normalized_key;\n     +-\tfound_entry = hashmap_get_entry(&cs->config_hash, &k, ent, NULL);\n     ++\tfound_entry = hashmap_get_entry(&set->config_hash, &k, ent, NULL);\n     + \tfree(normalized_key);\n     + \treturn found_entry;\n     + }\n       \n     --\tswitch (cf->origin_type) {\n     -+\tswitch (cs->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, cf->name, _(error_type));\n     -+\t\t    value, name, cs->name, _(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, cf->name, _(error_type));\n     -+\t\t    value, name, cs->name, _(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, cf->name, _(error_type));\n     -+\t\t    value, name, cs->name, _(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, cf->name, _(error_type));\n     -+\t\t    value, name, cs->name, _(error_type));\n     - \tdefault:\n     - \t\tdie(_(\"bad numeric config value '%s' for '%s' in %s: %s\"),\n     --\t\t    value, name, cf->name, _(error_type));\n     -+\t\t    value, name, cs->name, _(error_type));\n     + static int configset_add_value(struct config_reader *reader,\n     +-\t\t\t       struct config_set *cs, const char *key,\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     +@@ config.c: static int configset_add_value(struct config_reader *reader,\n     + \tstruct configset_list_item *l_item;\n     + \tstruct key_value_info *kv_info = xmalloc(sizeof(*kv_info));\n     + \n     +-\te = configset_find_element(cs, key);\n     ++\te = configset_find_element(set, key);\n     + \t/*\n     + \t * Since the keys are being fed by git_config*() callback mechanism, they\n     + \t * are already normalized. So simply add them without any further munging.\n     +@@ config.c: static int configset_add_value(struct config_reader *reader,\n     + \t\thashmap_entry_init(&e->ent, strhash(key));\n     + \t\te->key = xstrdup(key);\n     + \t\tstring_list_init_dup(&e->value_list);\n     +-\t\thashmap_add(&cs->config_hash, &e->ent);\n     ++\t\thashmap_add(&set->config_hash, &e->ent);\n       \t}\n     + \tsi = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));\n     + \n     +-\tALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc);\n     +-\tl_item = &cs->list.items[cs->list.nr++];\n     ++\tALLOC_GROW(set->list.items, set->list.nr + 1, set->list.alloc);\n     ++\tl_item = &set->list.items[set->list.nr++];\n     + \tl_item->e = e;\n     + \tl_item->value_index = e->value_list.nr - 1;\n     + \n     +@@ config.c: static int config_set_element_cmp(const void *cmp_data UNUSED,\n     + \treturn strcmp(e1->key, e2->key);\n       }\n       \n     -@@ config.c: int git_config_int(const char *name, const char *value)\n     +-void git_configset_init(struct config_set *cs)\n     ++void git_configset_init(struct config_set *set)\n       {\n     - \tint ret;\n     - \tif (!git_parse_int(value, &ret))\n     --\t\tdie_bad_number(name, value);\n     -+\t\tdie_bad_number(cf, name, value);\n     - \treturn ret;\n     +-\thashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0);\n     +-\tcs->hash_initialized = 1;\n     +-\tcs->list.nr = 0;\n     +-\tcs->list.alloc = 0;\n     +-\tcs->list.items = NULL;\n     ++\thashmap_init(&set->config_hash, config_set_element_cmp, NULL, 0);\n     ++\tset->hash_initialized = 1;\n     ++\tset->list.nr = 0;\n     ++\tset->list.alloc = 0;\n     ++\tset->list.items = NULL;\n       }\n       \n     -@@ config.c: int64_t git_config_int64(const char *name, const char *value)\n     +-void git_configset_clear(struct config_set *cs)\n     ++void git_configset_clear(struct config_set *set)\n       {\n     - \tint64_t ret;\n     - \tif (!git_parse_int64(value, &ret))\n     --\t\tdie_bad_number(name, value);\n     -+\t\tdie_bad_number(cf, name, value);\n     - \treturn ret;\n     + \tstruct config_set_element *entry;\n     + \tstruct hashmap_iter iter;\n     +-\tif (!cs->hash_initialized)\n     ++\tif (!set->hash_initialized)\n     + \t\treturn;\n     + \n     +-\thashmap_for_each_entry(&cs->config_hash, &iter, entry,\n     ++\thashmap_for_each_entry(&set->config_hash, &iter, entry,\n     + \t\t\t\tent /* member name */) {\n     + \t\tfree(entry->key);\n     + \t\tstring_list_clear(&entry->value_list, 1);\n     + \t}\n     +-\thashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent);\n     +-\tcs->hash_initialized = 0;\n     +-\tfree(cs->list.items);\n     +-\tcs->list.nr = 0;\n     +-\tcs->list.alloc = 0;\n     +-\tcs->list.items = NULL;\n     ++\thashmap_clear_and_free(&set->config_hash, struct config_set_element, ent);\n     ++\tset->hash_initialized = 0;\n     ++\tfree(set->list.items);\n     ++\tset->list.nr = 0;\n     ++\tset->list.alloc = 0;\n     ++\tset->list.items = NULL;\n     + }\n     + \n     + struct configset_add_data {\n     +@@ config.c: static int config_set_callback(const char *key, const char *value, void *cb)\n     + \treturn 0;\n       }\n       \n     -@@ config.c: unsigned long git_config_ulong(const char *name, const char *value)\n     +-int git_configset_add_file(struct config_set *cs, const char *filename)\n     ++int git_configset_add_file(struct config_set *set, const char *filename)\n       {\n     - \tunsigned long ret;\n     - \tif (!git_parse_ulong(value, &ret))\n     --\t\tdie_bad_number(name, value);\n     -+\t\tdie_bad_number(cf, name, value);\n     - \treturn ret;\n     + \tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n     + \tdata.config_reader = &the_reader;\n     +-\tdata.config_set = cs;\n     ++\tdata.config_set = set;\n     + \treturn git_config_from_file(config_set_callback, filename, &data);\n       }\n       \n     -@@ config.c: ssize_t git_config_ssize_t(const char *name, const char *value)\n     +-int git_configset_get_value(struct config_set *cs, const char *key, const char **value)\n     ++int git_configset_get_value(struct config_set *set, const char *key, const char **value)\n       {\n     - \tssize_t ret;\n     - \tif (!git_parse_ssize_t(value, &ret))\n     --\t\tdie_bad_number(name, value);\n     -+\t\tdie_bad_number(cf, name, value);\n     - \treturn ret;\n     + \tconst struct string_list *values = NULL;\n     + \t/*\n     +@@ config.c: int git_configset_get_value(struct config_set *cs, const char *key, const char *\n     + \t * queried key in the files of the configset, the value returned will be the last\n     + \t * value in the value list for that key.\n     + \t */\n     +-\tvalues = git_configset_get_value_multi(cs, key);\n     ++\tvalues = git_configset_get_value_multi(set, key);\n     + \n     + \tif (!values)\n     + \t\treturn 1;\n     +@@ config.c: int git_configset_get_value(struct config_set *cs, const char *key, const char *\n     + \treturn 0;\n       }\n       \n     -@@ config.c: static int do_config_from(struct config_source *top, config_fn_t fn, void *data,\n     - \tstrbuf_init(&top->var, 1024);\n     - \tcf = top;\n     +-const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)\n     ++const struct string_list *git_configset_get_value_multi(struct config_set *set, const char *key)\n     + {\n     +-\tstruct config_set_element *e = configset_find_element(cs, key);\n     ++\tstruct config_set_element *e = configset_find_element(set, key);\n     + \treturn e ? &e->value_list : NULL;\n     + }\n       \n     --\tret = git_parse_source(fn, data, opts);\n     -+\tret = git_parse_source(cf, fn, data, opts);\n     +-int git_configset_get_string(struct config_set *cs, const char *key, char **dest)\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(cs, key, &value))\n     ++\tif (!git_configset_get_value(set, key, &value))\n     + \t\treturn git_config_string((const char **)dest, key, value);\n     + \telse\n     + \t\treturn 1;\n     + }\n       \n     - \t/* pop config-file parsing state stack */\n     - \tstrbuf_release(&top->value);\n     +-static int git_configset_get_string_tmp(struct config_set *cs, const char *key,\n     ++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(cs, key, &value)) {\n     ++\tif (!git_configset_get_value(set, key, &value)) {\n     + \t\tif (!value)\n     + \t\t\treturn config_error_nonbool(key);\n     + \t\t*dest = value;\n     +@@ config.c: static int git_configset_get_string_tmp(struct config_set *cs, const char *key,\n     + \t}\n     + }\n     + \n     +-int git_configset_get_int(struct config_set *cs, const char *key, int *dest)\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(cs, key, &value)) {\n     ++\tif (!git_configset_get_value(set, key, &value)) {\n     + \t\t*dest = git_config_int(key, value);\n     + \t\treturn 0;\n     + \t} else\n     + \t\treturn 1;\n     + }\n     + \n     +-int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *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(cs, key, &value)) {\n     ++\tif (!git_configset_get_value(set, key, &value)) {\n     + \t\t*dest = git_config_ulong(key, value);\n     + \t\treturn 0;\n     + \t} else\n     + \t\treturn 1;\n     + }\n     + \n     +-int git_configset_get_bool(struct config_set *cs, const char *key, int *dest)\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(cs, key, &value)) {\n     ++\tif (!git_configset_get_value(set, key, &value)) {\n     + \t\t*dest = git_config_bool(key, value);\n     + \t\treturn 0;\n     + \t} else\n     + \t\treturn 1;\n     + }\n     + \n     +-int git_configset_get_bool_or_int(struct config_set *cs, const char *key,\n     ++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(cs, key, &value)) {\n     ++\tif (!git_configset_get_value(set, key, &value)) {\n     + \t\t*dest = git_config_bool_or_int(key, value, is_bool);\n     + \t\treturn 0;\n     + \t} else\n     + \t\treturn 1;\n     + }\n     + \n     +-int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest)\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(cs, key, &value)) {\n     ++\tif (!git_configset_get_value(set, key, &value)) {\n     + \t\t*dest = git_parse_maybe_bool(value);\n     + \t\tif (*dest == -1)\n     + \t\t\treturn -1;\n     +@@ config.c: int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *de\n     + \t\treturn 1;\n     + }\n     + \n     +-int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest)\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(cs, key, &value))\n     ++\tif (!git_configset_get_value(set, key, &value))\n     + \t\treturn git_config_pathname(dest, key, value);\n     + \telse\n     + \t\treturn 1;\n     +@@ config.c: static int store_aux_event(enum config_event_t type,\n     + \t\t\t   size_t begin, size_t end, void *data)\n     + {\n     + \tstruct config_store_data *store = data;\n     +-\tstruct config_source *cf = store->config_reader->source;\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     +@@ config.c: static int store_aux_event(enum config_event_t type,\n     + \tif (type == CONFIG_EVENT_SECTION) {\n     + \t\tint (*cmpfn)(const char *, const char *, size_t);\n     + \n     +-\t\tif (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')\n     +-\t\t\treturn error(_(\"invalid section name '%s'\"), cf->var.buf);\n     ++\t\tif (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')\n     ++\t\t\treturn error(_(\"invalid section name '%s'\"), cs->var.buf);\n     + \n     +-\t\tif (cf->subsection_case_sensitive)\n     ++\t\tif (cs->subsection_case_sensitive)\n     + \t\t\tcmpfn = strncasecmp;\n     + \t\telse\n     + \t\t\tcmpfn = strncmp;\n     +@@ config.c: static int store_aux_event(enum config_event_t type,\n     + \t\t/* Is this the section we were looking for? */\n     + \t\tstore->is_keys_section =\n     + \t\t\tstore->parsed[store->parsed_nr].is_keys_section =\n     +-\t\t\tcf->var.len - 1 == store->baselen &&\n     +-\t\t\t!cmpfn(cf->var.buf, store->key, store->baselen);\n     ++\t\t\tcs->var.len - 1 == store->baselen &&\n     ++\t\t\t!cmpfn(cs->var.buf, store->key, store->baselen);\n     + \t\tif (store->is_keys_section) {\n     + \t\t\tstore->section_seen = 1;\n     + \t\t\tALLOC_GROW(store->seen, store->seen_nr + 1,\n\n-- \ngitgitgadget\n"},{"id":"473583","messageId":"3c83d9535a037653c7de2d462a4df3a3c43a9308.1678925506.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com","subject":"[PATCH v2 7/8] config: report cached filenames in die_bad_number()","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-16T00:11:45Z","receivedAt":"2023-03-16T00:12:24Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nIf, when parsing numbers from config, die_bad_number() is called, it\nreports the filename and config source type if we were parsing a config\nfile, but not if we were iterating a config_set (it defaults to a less\nspecific error message). Most call sites don't parse config files\nbecause config is typically read once and cached, so we only report\nfilename and config source type in \"git config --type\" (since \"git\nconfig\" always parses config files).\n\nThis could have been fixed when we taught the current_config_*\nfunctions to respect config_set values (0d44a2dacc (config: return\nconfigset value for current_config_ functions, 2016-05-26), but it was\nhard to spot then and we might have just missed it (I didn't find\nmention of die_bad_number() in the original ML discussion [1].)\n\nFix this by refactoring the current_config_* functions into variants\nthat don't BUG() when we aren't reading config, and using the resulting\nfunctions in die_bad_number(). Refactoring is needed because \"git config\n--get[-regexp] --type=int\" parses the int value _after_ parsing the\nconfig file, which will run into the BUG().\n\nAlso, plumb \"struct config_reader\" into the new functions. This isn't\nnecessary per se, but this generalizes better, so it might help us avoid\nyet another refactor.\n\n1. https://lore.kernel.org/git/20160518223712.GA18317@sigill.intra.peff.net/\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c               | 65 +++++++++++++++++++++++++++++-------------\n config.h               |  1 +\n t/helper/test-config.c | 17 +++++++++++\n t/t1308-config-set.sh  |  9 ++++++\n 4 files changed, 72 insertions(+), 20 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 460326ae21e..da5f6381cde 100644\n--- a/config.c\n+++ b/config.c\n@@ -1312,39 +1312,48 @@ 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_source *cf, const char *name,\n+static void die_bad_number(struct config_reader *reader, const char *name,\n \t\t\t   const char *value)\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-\tif (!(cf && cf->name))\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 \t\tdie(_(bad_numeric), value, name, _(error_type));\n \n-\tswitch (cf->origin_type) {\n+\tswitch (config_origin) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in blob %s: %s\"),\n-\t\t    value, name, cf->name, _(error_type));\n+\t\t    value, name, config_name, _(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, cf->name, _(error_type));\n+\t\t    value, name, config_name, _(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, cf->name, _(error_type));\n+\t\t    value, name, config_name, _(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, cf->name, _(error_type));\n+\t\t    value, name, config_name, _(error_type));\n \tdefault:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in %s: %s\"),\n-\t\t    value, name, cf->name, _(error_type));\n+\t\t    value, name, config_name, _(error_type));\n \t}\n }\n \n@@ -1352,7 +1361,7 @@ int git_config_int(const char *name, const char *value)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(the_reader.source, name, value);\n+\t\tdie_bad_number(&the_reader, name, value);\n \treturn ret;\n }\n \n@@ -1360,7 +1369,7 @@ int64_t git_config_int64(const char *name, const char *value)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(the_reader.source, name, value);\n+\t\tdie_bad_number(&the_reader, name, value);\n \treturn ret;\n }\n \n@@ -1368,7 +1377,7 @@ unsigned long git_config_ulong(const char *name, const char *value)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(the_reader.source, name, value);\n+\t\tdie_bad_number(&the_reader, name, value);\n \treturn ret;\n }\n \n@@ -1376,7 +1385,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(the_reader.source, name, value);\n+\t\tdie_bad_number(&the_reader, name, value);\n \treturn ret;\n }\n \n@@ -3840,14 +3849,23 @@ int parse_config_key(const char *var,\n \treturn 0;\n }\n \n-const char *current_config_origin_type(void)\n+static int reader_origin_type(struct config_reader *reader,\n+\t\t\t      enum config_origin_type *type)\n {\n-\tint type;\n \tif (the_reader.config_kvi)\n-\t\ttype = the_reader.config_kvi->origin_type;\n+\t\t*type = reader->config_kvi->origin_type;\n \telse if(the_reader.source)\n-\t\ttype = the_reader.source->origin_type;\n+\t\t*type = reader->source->origin_type;\n \telse\n+\t\treturn 1;\n+\treturn 0;\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 \tswitch (type) {\n@@ -3886,14 +3904,21 @@ const char *config_scope_name(enum config_scope scope)\n \t}\n }\n \n-const char *current_config_name(void)\n+static int reader_config_name(struct config_reader *reader, const char **out)\n {\n-\tconst char *name;\n \tif (the_reader.config_kvi)\n-\t\tname = the_reader.config_kvi->filename;\n+\t\t*out = reader->config_kvi->filename;\n \telse if (the_reader.source)\n-\t\tname = the_reader.source->name;\n+\t\t*out = reader->source->name;\n \telse\n+\t\treturn 1;\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 }\ndiff --git a/config.h b/config.h\nindex 7606246531a..66c8b996e15 100644\n--- a/config.h\n+++ b/config.h\n@@ -56,6 +56,7 @@ struct git_config_source {\n };\n \n enum config_origin_type {\n+\tCONFIG_ORIGIN_UNKNOWN = 0,\n \tCONFIG_ORIGIN_BLOB,\n \tCONFIG_ORIGIN_FILE,\n \tCONFIG_ORIGIN_STDIN,\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 4ba9eb65606..26e79168f6a 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -30,6 +30,9 @@\n  * iterate -> iterate over all values using git_config(), and print some\n  *            data for each\n  *\n+ * git_config_int -> iterate over all values using git_config() and print the\n+ *                   integer value for the entered key or die\n+ *\n  * Examples:\n  *\n  * To print the value with highest priority for key \"foo.bAr Baz.rock\":\n@@ -54,6 +57,17 @@ 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+{\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\tprintf(\"%d\\n\", parsed);\n+\t}\n+\treturn 0;\n+}\n+\n static int early_config_cb(const char *var, const char *value, void *vdata)\n {\n \tconst char *key = vdata;\n@@ -176,6 +190,9 @@ int cmd__config(int argc, const char **argv)\n \t} else if (!strcmp(argv[1], \"iterate\")) {\n \t\tgit_config(iterate_cb, NULL);\n \t\tgoto exit0;\n+\t} else if (argc == 3 && !strcmp(argv[1], \"git_config_int\")) {\n+\t\tgit_config(parse_int_cb, (void *) argv[2]);\n+\t\tgoto exit0;\n \t}\n \n \tdie(\"%s: Please check the syntax and the function name\", argv[0]);\ndiff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh\nindex b38e158d3b2..9733bed30a9 100755\n--- a/t/t1308-config-set.sh\n+++ b/t/t1308-config-set.sh\n@@ -120,6 +120,10 @@ test_expect_success 'find integer value for a key' '\n \tcheck_config get_int lamb.chop 65\n '\n \n+test_expect_success 'parse integer value during iteration' '\n+\tcheck_config git_config_int lamb.chop 65\n+'\n+\n test_expect_success 'find string value for a key' '\n \tcheck_config get_string case.baz hask &&\n \tcheck_config expect_code 1 get_string case.ba \"Value not found for \\\"case.ba\\\"\"\n@@ -134,6 +138,11 @@ test_expect_success 'find integer if value is non parse-able' '\n \tcheck_config expect_code 128 get_int lamb.head\n '\n \n+test_expect_success 'non parse-able integer value during iteration' '\n+\tcheck_config expect_code 128 git_config_int lamb.head 2>result &&\n+\tgrep \"fatal: bad numeric config value .* in file \\.git/config\" result\n+'\n+\n test_expect_success 'find bool value for the entered key' '\n \tcheck_config get_bool goat.head 1 &&\n \tcheck_config get_bool goat.skin 0 &&\n-- \ngitgitgadget\n\n"},{"id":"473584","messageId":"9aec9092fdf0f792492850c8eb4a82ae47a491c1.1678925506.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com","subject":"[PATCH v2 8/8] config.c: rename \"struct config_source cf\"","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-16T00:11:46Z","receivedAt":"2023-03-16T00:12:26Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThe \"cf\" name is a holdover from before 4d8dd1494e (config: make parsing\nstack struct independent from actual data source, 2013-07-12), when the\nstruct was named config_file. Since that acronym no longer makes sense,\nrename \"cf\" to \"cs\". In some places, we have \"struct config_set cs\", so\nto avoid conflict, rename those \"cs\" to \"set\" (\"config_set\" would be\nmore descriptive, but it's much longer and would require us to rewrap\nseveral lines).\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 262 +++++++++++++++++++++++++++----------------------------\n 1 file changed, 131 insertions(+), 131 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex da5f6381cde..ae171ab11c3 100644\n--- a/config.c\n+++ b/config.c\n@@ -203,7 +203,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 *cf, const char *path,\n+static int handle_path_include(struct config_source *cs, const char *path,\n \t\t\t       struct config_include_data *inc)\n {\n \tint ret = 0;\n@@ -225,14 +225,14 @@ static int handle_path_include(struct config_source *cf, const char *path,\n \tif (!is_absolute_path(path)) {\n \t\tchar *slash;\n \n-\t\tif (!cf || !cf->path) {\n+\t\tif (!cs || !cs->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(cf->path);\n+\t\tslash = find_last_dir_sep(cs->path);\n \t\tif (slash)\n-\t\t\tstrbuf_add(&buf, cf->path, slash - cf->path + 1);\n+\t\t\tstrbuf_add(&buf, cs->path, slash - cs->path + 1);\n \t\tstrbuf_addstr(&buf, path);\n \t\tpath = buf.buf;\n \t}\n@@ -240,8 +240,8 @@ static int handle_path_include(struct config_source *cf, 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    !cf ? \"<unknown>\" :\n-\t\t\t    cf->name ? cf->name :\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\tinc->depth--;\n@@ -258,7 +258,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 *cf,\n+static int prepare_include_condition_pattern(struct config_source *cs,\n \t\t\t\t\t     struct strbuf *pat)\n {\n \tstruct strbuf path = STRBUF_INIT;\n@@ -275,11 +275,11 @@ static int prepare_include_condition_pattern(struct config_source *cf,\n \tif (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {\n \t\tconst char *slash;\n \n-\t\tif (!cf || !cf->path)\n+\t\tif (!cs || !cs->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, cf->path, 1);\n+\t\tstrbuf_realpath(&path, cs->path, 1);\n \t\tslash = find_last_dir_sep(path.buf);\n \t\tif (!slash)\n \t\t\tBUG(\"how is this possible?\");\n@@ -294,7 +294,7 @@ static int prepare_include_condition_pattern(struct config_source *cf,\n \treturn prefix;\n }\n \n-static int include_by_gitdir(struct config_source *cf,\n+static int include_by_gitdir(struct config_source *cs,\n \t\t\t     const struct config_options *opts,\n \t\t\t     const char *cond, size_t cond_len, int icase)\n {\n@@ -311,7 +311,7 @@ static int include_by_gitdir(struct config_source *cf,\n \n \tstrbuf_realpath(&text, git_dir, 1);\n \tstrbuf_add(&pattern, cond, cond_len);\n-\tprefix = prepare_include_condition_pattern(cf, &pattern);\n+\tprefix = prepare_include_condition_pattern(cs, &pattern);\n \n again:\n \tif (prefix < 0)\n@@ -450,16 +450,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 *cf,\n+static int include_condition_is_true(struct config_source *cs,\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(cf, opts, cond, cond_len, 0);\n+\t\treturn include_by_gitdir(cs, 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(cf, opts, cond, cond_len, 1);\n+\t\treturn include_by_gitdir(cs, 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@@ -473,7 +473,7 @@ static int include_condition_is_true(struct config_source *cf,\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 *cf = inc->config_reader->source;\n+\tstruct config_source *cs = inc->config_reader->source;\n \tconst char *cond, *key;\n \tsize_t cond_len;\n \tint ret;\n@@ -487,16 +487,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(cf, value, inc);\n+\t\tret = handle_path_include(cs, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n-\t    cond && include_condition_is_true(cf, inc, cond, cond_len) &&\n+\t    cond && include_condition_is_true(cs, 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(cf, value, inc);\n+\t\tret = handle_path_include(cs, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -821,21 +821,21 @@ out:\n \treturn ret;\n }\n \n-static int get_next_char(struct config_source *cf)\n+static int get_next_char(struct config_source *cs)\n {\n-\tint c = cf->do_fgetc(cf);\n+\tint c = cs->do_fgetc(cs);\n \n \tif (c == '\\r') {\n \t\t/* DOS like systems */\n-\t\tc = cf->do_fgetc(cf);\n+\t\tc = cs->do_fgetc(cs);\n \t\tif (c != '\\n') {\n \t\t\tif (c != EOF)\n-\t\t\t\tcf->do_ungetc(c, cf);\n+\t\t\t\tcs->do_ungetc(c, cs);\n \t\t\tc = '\\r';\n \t\t}\n \t}\n \n-\tif (c != EOF && ++cf->total_len > INT_MAX) {\n+\tif (c != EOF && ++cs->total_len > INT_MAX) {\n \t\t/*\n \t\t * This is an absurdly long config file; refuse to parse\n \t\t * further in order to protect downstream code from integer\n@@ -843,38 +843,38 @@ static int get_next_char(struct config_source *cf)\n \t\t * but we can mark EOF and put trash in the return value,\n \t\t * which will trigger a parse error.\n \t\t */\n-\t\tcf->eof = 1;\n+\t\tcs->eof = 1;\n \t\treturn 0;\n \t}\n \n \tif (c == '\\n')\n-\t\tcf->linenr++;\n+\t\tcs->linenr++;\n \tif (c == EOF) {\n-\t\tcf->eof = 1;\n-\t\tcf->linenr++;\n+\t\tcs->eof = 1;\n+\t\tcs->linenr++;\n \t\tc = '\\n';\n \t}\n \treturn c;\n }\n \n-static char *parse_value(struct config_source *cf)\n+static char *parse_value(struct config_source *cs)\n {\n \tint quote = 0, comment = 0, space = 0;\n \n-\tstrbuf_reset(&cf->value);\n+\tstrbuf_reset(&cs->value);\n \tfor (;;) {\n-\t\tint c = get_next_char(cf);\n+\t\tint c = get_next_char(cs);\n \t\tif (c == '\\n') {\n \t\t\tif (quote) {\n-\t\t\t\tcf->linenr--;\n+\t\t\t\tcs->linenr--;\n \t\t\t\treturn NULL;\n \t\t\t}\n-\t\t\treturn cf->value.buf;\n+\t\t\treturn cs->value.buf;\n \t\t}\n \t\tif (comment)\n \t\t\tcontinue;\n \t\tif (isspace(c) && !quote) {\n-\t\t\tif (cf->value.len)\n+\t\t\tif (cs->value.len)\n \t\t\t\tspace++;\n \t\t\tcontinue;\n \t\t}\n@@ -885,9 +885,9 @@ static char *parse_value(struct config_source *cf)\n \t\t\t}\n \t\t}\n \t\tfor (; space; space--)\n-\t\t\tstrbuf_addch(&cf->value, ' ');\n+\t\t\tstrbuf_addch(&cs->value, ' ');\n \t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char(cf);\n+\t\t\tc = get_next_char(cs);\n \t\t\tswitch (c) {\n \t\t\tcase '\\n':\n \t\t\t\tcontinue;\n@@ -907,18 +907,18 @@ static char *parse_value(struct config_source *cf)\n \t\t\tdefault:\n \t\t\t\treturn NULL;\n \t\t\t}\n-\t\t\tstrbuf_addch(&cf->value, c);\n+\t\t\tstrbuf_addch(&cs->value, c);\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '\"') {\n \t\t\tquote = 1-quote;\n \t\t\tcontinue;\n \t\t}\n-\t\tstrbuf_addch(&cf->value, c);\n+\t\tstrbuf_addch(&cs->value, c);\n \t}\n }\n \n-static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n+static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \t\t     struct strbuf *name)\n {\n \tint c;\n@@ -927,8 +927,8 @@ static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n \n \t/* Get the full name */\n \tfor (;;) {\n-\t\tc = get_next_char(cf);\n-\t\tif (cf->eof)\n+\t\tc = get_next_char(cs);\n+\t\tif (cs->eof)\n \t\t\tbreak;\n \t\tif (!iskeychar(c))\n \t\t\tbreak;\n@@ -936,13 +936,13 @@ static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n \t}\n \n \twhile (c == ' ' || c == '\\t')\n-\t\tc = get_next_char(cf);\n+\t\tc = get_next_char(cs);\n \n \tvalue = NULL;\n \tif (c != '\\n') {\n \t\tif (c != '=')\n \t\t\treturn -1;\n-\t\tvalue = parse_value(cf);\n+\t\tvalue = parse_value(cs);\n \t\tif (!value)\n \t\t\treturn -1;\n \t}\n@@ -951,21 +951,21 @@ static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n \t * the line we just parsed during the call to fn to get\n \t * accurate line number in error messages.\n \t */\n-\tcf->linenr--;\n+\tcs->linenr--;\n \tret = fn(name->buf, value, data);\n \tif (ret >= 0)\n-\t\tcf->linenr++;\n+\t\tcs->linenr++;\n \treturn ret;\n }\n \n-static int get_extended_base_var(struct config_source *cf, struct strbuf *name,\n+static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n \t\t\t\t int c)\n {\n-\tcf->subsection_case_sensitive = 0;\n+\tcs->subsection_case_sensitive = 0;\n \tdo {\n \t\tif (c == '\\n')\n \t\t\tgoto error_incomplete_line;\n-\t\tc = get_next_char(cf);\n+\t\tc = get_next_char(cs);\n \t} while (isspace(c));\n \n \t/* We require the format to be '[base \"extension\"]' */\n@@ -974,13 +974,13 @@ static int get_extended_base_var(struct config_source *cf, struct strbuf *name,\n \tstrbuf_addch(name, '.');\n \n \tfor (;;) {\n-\t\tint c = get_next_char(cf);\n+\t\tint c = get_next_char(cs);\n \t\tif (c == '\\n')\n \t\t\tgoto error_incomplete_line;\n \t\tif (c == '\"')\n \t\t\tbreak;\n \t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char(cf);\n+\t\t\tc = get_next_char(cs);\n \t\t\tif (c == '\\n')\n \t\t\t\tgoto error_incomplete_line;\n \t\t}\n@@ -988,25 +988,25 @@ static int get_extended_base_var(struct config_source *cf, struct strbuf *name,\n \t}\n \n \t/* Final ']' */\n-\tif (get_next_char(cf) != ']')\n+\tif (get_next_char(cs) != ']')\n \t\treturn -1;\n \treturn 0;\n error_incomplete_line:\n-\tcf->linenr--;\n+\tcs->linenr--;\n \treturn -1;\n }\n \n-static int get_base_var(struct config_source *cf, struct strbuf *name)\n+static int get_base_var(struct config_source *cs, struct strbuf *name)\n {\n-\tcf->subsection_case_sensitive = 1;\n+\tcs->subsection_case_sensitive = 1;\n \tfor (;;) {\n-\t\tint c = get_next_char(cf);\n-\t\tif (cf->eof)\n+\t\tint c = get_next_char(cs);\n+\t\tif (cs->eof)\n \t\t\treturn -1;\n \t\tif (c == ']')\n \t\t\treturn 0;\n \t\tif (isspace(c))\n-\t\t\treturn get_extended_base_var(cf, name, c);\n+\t\t\treturn get_extended_base_var(cs, name, c);\n \t\tif (!iskeychar(c) && c != '.')\n \t\t\treturn -1;\n \t\tstrbuf_addch(name, tolower(c));\n@@ -1019,7 +1019,7 @@ struct parse_event_data {\n \tconst struct config_options *opts;\n };\n \n-static int do_event(struct config_source *cf, enum config_event_t type,\n+static int do_event(struct config_source *cs, enum config_event_t type,\n \t\t    struct parse_event_data *data)\n {\n \tsize_t offset;\n@@ -1031,7 +1031,7 @@ static int do_event(struct config_source *cf, enum config_event_t type,\n \t    data->previous_type == type)\n \t\treturn 0;\n \n-\toffset = cf->do_ftell(cf);\n+\toffset = cs->do_ftell(cs);\n \t/*\n \t * At EOF, the parser always \"inserts\" an extra '\\n', therefore\n \t * the end offset of the event is the current file position, otherwise\n@@ -1051,12 +1051,12 @@ static int do_event(struct config_source *cf, enum config_event_t type,\n \treturn 0;\n }\n \n-static int git_parse_source(struct config_source *cf, config_fn_t fn,\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 {\n \tint comment = 0;\n \tsize_t baselen = 0;\n-\tstruct strbuf *var = &cf->var;\n+\tstruct strbuf *var = &cs->var;\n \tint error_return = 0;\n \tchar *error_msg = NULL;\n \n@@ -1071,7 +1071,7 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,\n \tfor (;;) {\n \t\tint c;\n \n-\t\tc = get_next_char(cf);\n+\t\tc = get_next_char(cs);\n \t\tif (bomptr && *bomptr) {\n \t\t\t/* We are at the file beginning; skip UTF8-encoded BOM\n \t\t\t * if present. Sane editors won't put this in on their\n@@ -1088,12 +1088,12 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,\n \t\t\t}\n \t\t}\n \t\tif (c == '\\n') {\n-\t\t\tif (cf->eof) {\n-\t\t\t\tif (do_event(cf, CONFIG_EVENT_EOF, &event_data) < 0)\n+\t\t\tif (cs->eof) {\n+\t\t\t\tif (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\t\treturn 0;\n \t\t\t}\n-\t\t\tif (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n+\t\t\tif (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n \t\t\t\treturn -1;\n \t\t\tcomment = 0;\n \t\t\tcontinue;\n@@ -1101,23 +1101,23 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,\n \t\tif (comment)\n \t\t\tcontinue;\n \t\tif (isspace(c)) {\n-\t\t\tif (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n+\t\t\tif (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '#' || c == ';') {\n-\t\t\tif (do_event(cf, CONFIG_EVENT_COMMENT, &event_data) < 0)\n+\t\t\tif (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\tcomment = 1;\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '[') {\n-\t\t\tif (do_event(cf, CONFIG_EVENT_SECTION, &event_data) < 0)\n+\t\t\tif (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \n \t\t\t/* Reset prior to determining a new stem */\n \t\t\tstrbuf_reset(var);\n-\t\t\tif (get_base_var(cf, var) < 0 || var->len < 1)\n+\t\t\tif (get_base_var(cs, var) < 0 || var->len < 1)\n \t\t\t\tbreak;\n \t\t\tstrbuf_addch(var, '.');\n \t\t\tbaselen = var->len;\n@@ -1126,7 +1126,7 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,\n \t\tif (!isalpha(c))\n \t\t\tbreak;\n \n-\t\tif (do_event(cf, CONFIG_EVENT_ENTRY, &event_data) < 0)\n+\t\tif (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)\n \t\t\treturn -1;\n \n \t\t/*\n@@ -1136,42 +1136,42 @@ static int git_parse_source(struct config_source *cf, 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(cf, fn, data, var) < 0)\n+\t\tif (get_value(cs, fn, data, var) < 0)\n \t\t\tbreak;\n \t}\n \n-\tif (do_event(cf, CONFIG_EVENT_ERROR, &event_data) < 0)\n+\tif (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)\n \t\treturn -1;\n \n-\tswitch (cf->origin_type) {\n+\tswitch (cs->origin_type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in blob %s\"),\n-\t\t\t\t      cf->linenr, cf->name);\n+\t\t\t\t      cs->linenr, cs->name);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_FILE:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in file %s\"),\n-\t\t\t\t      cf->linenr, cf->name);\n+\t\t\t\t      cs->linenr, cs->name);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_STDIN:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in standard input\"),\n-\t\t\t\t      cf->linenr);\n+\t\t\t\t      cs->linenr);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_SUBMODULE_BLOB:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in submodule-blob %s\"),\n-\t\t\t\t       cf->linenr, cf->name);\n+\t\t\t\t       cs->linenr, cs->name);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_CMDLINE:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in command line %s\"),\n-\t\t\t\t       cf->linenr, cf->name);\n+\t\t\t\t       cs->linenr, cs->name);\n \t\tbreak;\n \tdefault:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in %s\"),\n-\t\t\t\t      cf->linenr, cf->name);\n+\t\t\t\t      cs->linenr, cs->name);\n \t}\n \n \tswitch (opts && opts->error_action ?\n \t\topts->error_action :\n-\t\tcf->default_error_action) {\n+\t\tcs->default_error_action) {\n \tcase CONFIG_ERROR_DIE:\n \t\tdie(\"%s\", error_msg);\n \t\tbreak;\n@@ -2281,13 +2281,13 @@ 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 *cs,\n+static void configset_iter(struct config_reader *reader, struct config_set *set,\n \t\t\t   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 = &cs->list;\n+\tstruct configset_list *list = &set->list;\n \n \tfor (i = 0; i < list->nr; i++) {\n \t\tentry = list->items[i].e;\n@@ -2352,7 +2352,7 @@ void read_very_early_config(config_fn_t cb, void *data)\n \tconfig_with_options(cb, data, NULL, &opts);\n }\n \n-static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)\n+static struct config_set_element *configset_find_element(struct config_set *set, const char *key)\n {\n \tstruct config_set_element k;\n \tstruct config_set_element *found_entry;\n@@ -2366,13 +2366,13 @@ static struct config_set_element *configset_find_element(struct config_set *cs,\n \n \thashmap_entry_init(&k.ent, strhash(normalized_key));\n \tk.key = normalized_key;\n-\tfound_entry = hashmap_get_entry(&cs->config_hash, &k, ent, NULL);\n+\tfound_entry = hashmap_get_entry(&set->config_hash, &k, ent, NULL);\n \tfree(normalized_key);\n \treturn found_entry;\n }\n \n static int configset_add_value(struct config_reader *reader,\n-\t\t\t       struct config_set *cs, const char *key,\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@@ -2380,7 +2380,7 @@ static int configset_add_value(struct config_reader *reader,\n \tstruct configset_list_item *l_item;\n \tstruct key_value_info *kv_info = xmalloc(sizeof(*kv_info));\n \n-\te = configset_find_element(cs, key);\n+\te = configset_find_element(set, key);\n \t/*\n \t * Since the keys are being fed by git_config*() callback mechanism, they\n \t * are already normalized. So simply add them without any further munging.\n@@ -2390,12 +2390,12 @@ static int configset_add_value(struct config_reader *reader,\n \t\thashmap_entry_init(&e->ent, strhash(key));\n \t\te->key = xstrdup(key);\n \t\tstring_list_init_dup(&e->value_list);\n-\t\thashmap_add(&cs->config_hash, &e->ent);\n+\t\thashmap_add(&set->config_hash, &e->ent);\n \t}\n \tsi = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));\n \n-\tALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc);\n-\tl_item = &cs->list.items[cs->list.nr++];\n+\tALLOC_GROW(set->list.items, set->list.nr + 1, set->list.alloc);\n+\tl_item = &set->list.items[set->list.nr++];\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n@@ -2430,33 +2430,33 @@ static int config_set_element_cmp(const void *cmp_data UNUSED,\n \treturn strcmp(e1->key, e2->key);\n }\n \n-void git_configset_init(struct config_set *cs)\n+void git_configset_init(struct config_set *set)\n {\n-\thashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0);\n-\tcs->hash_initialized = 1;\n-\tcs->list.nr = 0;\n-\tcs->list.alloc = 0;\n-\tcs->list.items = NULL;\n+\thashmap_init(&set->config_hash, config_set_element_cmp, NULL, 0);\n+\tset->hash_initialized = 1;\n+\tset->list.nr = 0;\n+\tset->list.alloc = 0;\n+\tset->list.items = NULL;\n }\n \n-void git_configset_clear(struct config_set *cs)\n+void git_configset_clear(struct config_set *set)\n {\n \tstruct config_set_element *entry;\n \tstruct hashmap_iter iter;\n-\tif (!cs->hash_initialized)\n+\tif (!set->hash_initialized)\n \t\treturn;\n \n-\thashmap_for_each_entry(&cs->config_hash, &iter, entry,\n+\thashmap_for_each_entry(&set->config_hash, &iter, entry,\n \t\t\t\tent /* member name */) {\n \t\tfree(entry->key);\n \t\tstring_list_clear(&entry->value_list, 1);\n \t}\n-\thashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent);\n-\tcs->hash_initialized = 0;\n-\tfree(cs->list.items);\n-\tcs->list.nr = 0;\n-\tcs->list.alloc = 0;\n-\tcs->list.items = NULL;\n+\thashmap_clear_and_free(&set->config_hash, struct config_set_element, ent);\n+\tset->hash_initialized = 0;\n+\tfree(set->list.items);\n+\tset->list.nr = 0;\n+\tset->list.alloc = 0;\n+\tset->list.items = NULL;\n }\n \n struct configset_add_data {\n@@ -2472,15 +2472,15 @@ static int config_set_callback(const char *key, const char *value, void *cb)\n \treturn 0;\n }\n \n-int git_configset_add_file(struct config_set *cs, const char *filename)\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 = cs;\n+\tdata.config_set = set;\n \treturn git_config_from_file(config_set_callback, filename, &data);\n }\n \n-int git_configset_get_value(struct config_set *cs, const char *key, const char **value)\n+int git_configset_get_value(struct config_set *set, const char *key, const char **value)\n {\n \tconst struct string_list *values = NULL;\n \t/*\n@@ -2488,7 +2488,7 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *\n \t * queried key in the files of the configset, the value returned will be the last\n \t * value in the value list for that key.\n \t */\n-\tvalues = git_configset_get_value_multi(cs, key);\n+\tvalues = git_configset_get_value_multi(set, key);\n \n \tif (!values)\n \t\treturn 1;\n@@ -2497,26 +2497,26 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *\n \treturn 0;\n }\n \n-const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)\n+const struct string_list *git_configset_get_value_multi(struct config_set *set, const char *key)\n {\n-\tstruct config_set_element *e = configset_find_element(cs, key);\n+\tstruct config_set_element *e = configset_find_element(set, key);\n \treturn e ? &e->value_list : NULL;\n }\n \n-int git_configset_get_string(struct config_set *cs, const char *key, char **dest)\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(cs, key, &value))\n+\tif (!git_configset_get_value(set, key, &value))\n \t\treturn git_config_string((const char **)dest, key, value);\n \telse\n \t\treturn 1;\n }\n \n-static int git_configset_get_string_tmp(struct config_set *cs, const char *key,\n+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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(key);\n \t\t*dest = value;\n@@ -2526,51 +2526,51 @@ static int git_configset_get_string_tmp(struct config_set *cs, const char *key,\n \t}\n }\n \n-int git_configset_get_int(struct config_set *cs, const char *key, int *dest)\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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_config_int(key, value);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n }\n \n-int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_config_ulong(key, value);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n }\n \n-int git_configset_get_bool(struct config_set *cs, const char *key, int *dest)\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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_config_bool(key, value);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n }\n \n-int git_configset_get_bool_or_int(struct config_set *cs, const char *key,\n+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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_config_bool_or_int(key, value, is_bool);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n }\n \n-int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest)\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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_parse_maybe_bool(value);\n \t\tif (*dest == -1)\n \t\t\treturn -1;\n@@ -2579,10 +2579,10 @@ int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *de\n \t\treturn 1;\n }\n \n-int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest)\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(cs, key, &value))\n+\tif (!git_configset_get_value(set, key, &value))\n \t\treturn git_config_pathname(dest, key, value);\n \telse\n \t\treturn 1;\n@@ -2973,7 +2973,7 @@ static int store_aux_event(enum config_event_t type,\n \t\t\t   size_t begin, size_t end, void *data)\n {\n \tstruct config_store_data *store = data;\n-\tstruct config_source *cf = store->config_reader->source;\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@@ -2983,10 +2983,10 @@ static int store_aux_event(enum config_event_t type,\n \tif (type == CONFIG_EVENT_SECTION) {\n \t\tint (*cmpfn)(const char *, const char *, size_t);\n \n-\t\tif (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')\n-\t\t\treturn error(_(\"invalid section name '%s'\"), cf->var.buf);\n+\t\tif (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')\n+\t\t\treturn error(_(\"invalid section name '%s'\"), cs->var.buf);\n \n-\t\tif (cf->subsection_case_sensitive)\n+\t\tif (cs->subsection_case_sensitive)\n \t\t\tcmpfn = strncasecmp;\n \t\telse\n \t\t\tcmpfn = strncmp;\n@@ -2994,8 +2994,8 @@ static int store_aux_event(enum config_event_t type,\n \t\t/* Is this the section we were looking for? */\n \t\tstore->is_keys_section =\n \t\t\tstore->parsed[store->parsed_nr].is_keys_section =\n-\t\t\tcf->var.len - 1 == store->baselen &&\n-\t\t\t!cmpfn(cf->var.buf, store->key, store->baselen);\n+\t\t\tcs->var.len - 1 == store->baselen &&\n+\t\t\t!cmpfn(cs->var.buf, store->key, store->baselen);\n \t\tif (store->is_keys_section) {\n \t\t\tstore->section_seen = 1;\n \t\t\tALLOC_GROW(store->seen, store->seen_nr + 1,\n-- \ngitgitgadget\n"},{"id":"473585","messageId":"kl6l3565wnyy.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/8] config.c: use struct for config reading state","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-16T00:15:33Z","receivedAt":"2023-03-16T00:15:53Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> As a result of moving the rename, the range-diff is quite noisy. The diff\n> between the final commits is might be helpful instead [2] (I'll also send a\n> diff to the ML).\n\ndiff --git a/config.c b/config.c\nindex 19bab84c47..ae171ab11c 100644\n--- a/config.c\n+++ b/config.c\n@@ -81,14 +81,18 @@ struct config_reader {\n \t */\n \tenum config_scope parsing_scope;\n };\n-/* Only public functions should reference the_reader. */\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 \tif (reader->config_kvi)\n-\t\tBUG(\"source should only be set when parsing a config source\");\n+\t\tBUG(\"source should not be set while iterating a config set\");\n \tif (reader->source)\n \t\ttop->prev = reader->source;\n \treader->source = top;\n@@ -108,7 +112,7 @@ 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 only be set when iterating through configset\");\n+\t\tBUG(\"kvi should not be set while parsing a config source\");\n \treader->config_kvi = kvi;\n }\n \n@@ -1308,39 +1312,48 @@ 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_source *cs, const char *name,\n+static void die_bad_number(struct config_reader *reader, const char *name,\n \t\t\t   const char *value)\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-\tif (!(cs && cs->name))\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 \t\tdie(_(bad_numeric), value, name, _(error_type));\n \n-\tswitch (cs->origin_type) {\n+\tswitch (config_origin) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in blob %s: %s\"),\n-\t\t    value, name, cs->name, _(error_type));\n+\t\t    value, name, config_name, _(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, cs->name, _(error_type));\n+\t\t    value, name, config_name, _(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, cs->name, _(error_type));\n+\t\t    value, name, config_name, _(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, cs->name, _(error_type));\n+\t\t    value, name, config_name, _(error_type));\n \tdefault:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in %s: %s\"),\n-\t\t    value, name, cs->name, _(error_type));\n+\t\t    value, name, config_name, _(error_type));\n \t}\n }\n \n@@ -1348,7 +1361,7 @@ int git_config_int(const char *name, const char *value)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(the_reader.source, name, value);\n+\t\tdie_bad_number(&the_reader, name, value);\n \treturn ret;\n }\n \n@@ -1356,7 +1369,7 @@ int64_t git_config_int64(const char *name, const char *value)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(the_reader.source, name, value);\n+\t\tdie_bad_number(&the_reader, name, value);\n \treturn ret;\n }\n \n@@ -1364,7 +1377,7 @@ unsigned long git_config_ulong(const char *name, const char *value)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(the_reader.source, name, value);\n+\t\tdie_bad_number(&the_reader, name, value);\n \treturn ret;\n }\n \n@@ -1372,7 +1385,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(the_reader.source, name, value);\n+\t\tdie_bad_number(&the_reader, name, value);\n \treturn ret;\n }\n \n@@ -2268,34 +2281,27 @@ int config_with_options(config_fn_t fn, void *data,\n \treturn ret;\n }\n \n-struct configset_iter_data {\n-\tstruct config_reader *config_reader;\n-\tvoid *inner;\n-};\n-#define CONFIGSET_ITER_INIT { 0 }\n-\n-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n+static void configset_iter(struct config_reader *reader, struct config_set *set,\n+\t\t\t   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 = &cs->list;\n-\tstruct configset_iter_data *iter_data = data;\n+\tstruct configset_list *list = &set->list;\n \n \tfor (i = 0; i < list->nr; i++) {\n-\t\tstruct key_value_info *kvi;\n \t\tentry = list->items[i].e;\n \t\tvalue_index = list->items[i].value_index;\n \t\tvalues = &entry->value_list;\n \n-\t\tkvi = values->items[value_index].util;\n-\t\tconfig_reader_set_kvi(iter_data->config_reader, kvi);\n+\t\tconfig_reader_set_kvi(reader, values->items[value_index].util);\n \n-\t\tif (fn(entry->key, values->items[value_index].string, iter_data->inner) < 0)\n-\t\t\tgit_die_config_linenr(entry->key, kvi->filename,\n-\t\t\t\t\t      kvi->linenr);\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(iter_data->config_reader, NULL);\n+\t\tconfig_reader_set_kvi(reader, NULL);\n \t}\n }\n \n@@ -2346,7 +2352,7 @@ void read_very_early_config(config_fn_t cb, void *data)\n \tconfig_with_options(cb, data, NULL, &opts);\n }\n \n-static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)\n+static struct config_set_element *configset_find_element(struct config_set *set, const char *key)\n {\n \tstruct config_set_element k;\n \tstruct config_set_element *found_entry;\n@@ -2360,13 +2366,13 @@ static struct config_set_element *configset_find_element(struct config_set *cs,\n \n \thashmap_entry_init(&k.ent, strhash(normalized_key));\n \tk.key = normalized_key;\n-\tfound_entry = hashmap_get_entry(&cs->config_hash, &k, ent, NULL);\n+\tfound_entry = hashmap_get_entry(&set->config_hash, &k, ent, NULL);\n \tfree(normalized_key);\n \treturn found_entry;\n }\n \n static int configset_add_value(struct config_reader *reader,\n-\t\t\t       struct config_set *cs, const char *key,\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@@ -2374,7 +2380,7 @@ static int configset_add_value(struct config_reader *reader,\n \tstruct configset_list_item *l_item;\n \tstruct key_value_info *kv_info = xmalloc(sizeof(*kv_info));\n \n-\te = configset_find_element(cs, key);\n+\te = configset_find_element(set, key);\n \t/*\n \t * Since the keys are being fed by git_config*() callback mechanism, they\n \t * are already normalized. So simply add them without any further munging.\n@@ -2384,12 +2390,12 @@ static int configset_add_value(struct config_reader *reader,\n \t\thashmap_entry_init(&e->ent, strhash(key));\n \t\te->key = xstrdup(key);\n \t\tstring_list_init_dup(&e->value_list);\n-\t\thashmap_add(&cs->config_hash, &e->ent);\n+\t\thashmap_add(&set->config_hash, &e->ent);\n \t}\n \tsi = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));\n \n-\tALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc);\n-\tl_item = &cs->list.items[cs->list.nr++];\n+\tALLOC_GROW(set->list.items, set->list.nr + 1, set->list.alloc);\n+\tl_item = &set->list.items[set->list.nr++];\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n@@ -2424,33 +2430,33 @@ static int config_set_element_cmp(const void *cmp_data UNUSED,\n \treturn strcmp(e1->key, e2->key);\n }\n \n-void git_configset_init(struct config_set *cs)\n+void git_configset_init(struct config_set *set)\n {\n-\thashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0);\n-\tcs->hash_initialized = 1;\n-\tcs->list.nr = 0;\n-\tcs->list.alloc = 0;\n-\tcs->list.items = NULL;\n+\thashmap_init(&set->config_hash, config_set_element_cmp, NULL, 0);\n+\tset->hash_initialized = 1;\n+\tset->list.nr = 0;\n+\tset->list.alloc = 0;\n+\tset->list.items = NULL;\n }\n \n-void git_configset_clear(struct config_set *cs)\n+void git_configset_clear(struct config_set *set)\n {\n \tstruct config_set_element *entry;\n \tstruct hashmap_iter iter;\n-\tif (!cs->hash_initialized)\n+\tif (!set->hash_initialized)\n \t\treturn;\n \n-\thashmap_for_each_entry(&cs->config_hash, &iter, entry,\n+\thashmap_for_each_entry(&set->config_hash, &iter, entry,\n \t\t\t\tent /* member name */) {\n \t\tfree(entry->key);\n \t\tstring_list_clear(&entry->value_list, 1);\n \t}\n-\thashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent);\n-\tcs->hash_initialized = 0;\n-\tfree(cs->list.items);\n-\tcs->list.nr = 0;\n-\tcs->list.alloc = 0;\n-\tcs->list.items = NULL;\n+\thashmap_clear_and_free(&set->config_hash, struct config_set_element, ent);\n+\tset->hash_initialized = 0;\n+\tfree(set->list.items);\n+\tset->list.nr = 0;\n+\tset->list.alloc = 0;\n+\tset->list.items = NULL;\n }\n \n struct configset_add_data {\n@@ -2466,15 +2472,15 @@ static int config_set_callback(const char *key, const char *value, void *cb)\n \treturn 0;\n }\n \n-int git_configset_add_file(struct config_set *cs, const char *filename)\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 = cs;\n+\tdata.config_set = set;\n \treturn git_config_from_file(config_set_callback, filename, &data);\n }\n \n-int git_configset_get_value(struct config_set *cs, const char *key, const char **value)\n+int git_configset_get_value(struct config_set *set, const char *key, const char **value)\n {\n \tconst struct string_list *values = NULL;\n \t/*\n@@ -2482,7 +2488,7 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *\n \t * queried key in the files of the configset, the value returned will be the last\n \t * value in the value list for that key.\n \t */\n-\tvalues = git_configset_get_value_multi(cs, key);\n+\tvalues = git_configset_get_value_multi(set, key);\n \n \tif (!values)\n \t\treturn 1;\n@@ -2491,26 +2497,26 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *\n \treturn 0;\n }\n \n-const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)\n+const struct string_list *git_configset_get_value_multi(struct config_set *set, const char *key)\n {\n-\tstruct config_set_element *e = configset_find_element(cs, key);\n+\tstruct config_set_element *e = configset_find_element(set, key);\n \treturn e ? &e->value_list : NULL;\n }\n \n-int git_configset_get_string(struct config_set *cs, const char *key, char **dest)\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(cs, key, &value))\n+\tif (!git_configset_get_value(set, key, &value))\n \t\treturn git_config_string((const char **)dest, key, value);\n \telse\n \t\treturn 1;\n }\n \n-static int git_configset_get_string_tmp(struct config_set *cs, const char *key,\n+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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(key);\n \t\t*dest = value;\n@@ -2520,51 +2526,51 @@ static int git_configset_get_string_tmp(struct config_set *cs, const char *key,\n \t}\n }\n \n-int git_configset_get_int(struct config_set *cs, const char *key, int *dest)\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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_config_int(key, value);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n }\n \n-int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_config_ulong(key, value);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n }\n \n-int git_configset_get_bool(struct config_set *cs, const char *key, int *dest)\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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_config_bool(key, value);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n }\n \n-int git_configset_get_bool_or_int(struct config_set *cs, const char *key,\n+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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_config_bool_or_int(key, value, is_bool);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n }\n \n-int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest)\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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_parse_maybe_bool(value);\n \t\tif (*dest == -1)\n \t\t\treturn -1;\n@@ -2573,10 +2579,10 @@ int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *de\n \t\treturn 1;\n }\n \n-int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest)\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(cs, key, &value))\n+\tif (!git_configset_get_value(set, key, &value))\n \t\treturn git_config_pathname(dest, key, value);\n \telse\n \t\treturn 1;\n@@ -2630,14 +2636,10 @@ static void repo_config_clear(struct repository *repo)\n \tgit_configset_clear(repo->config);\n }\n \n-void repo_config(struct repository *repo, config_fn_t fn, void *data_inner)\n+void repo_config(struct repository *repo, config_fn_t fn, void *data)\n {\n-\tstruct configset_iter_data data = CONFIGSET_ITER_INIT;\n-\tdata.inner = data_inner;\n-\tdata.config_reader = &the_reader;\n-\n \tgit_config_check_init(repo);\n-\tconfigset_iter(repo->config, fn, &data);\n+\tconfigset_iter(&the_reader, repo->config, fn, data);\n }\n \n int repo_config_get_value(struct repository *repo,\n@@ -2739,15 +2741,11 @@ static void read_protected_config(void)\n \tconfig_with_options(config_set_callback, &data, NULL, &opts);\n }\n \n-void git_protected_config(config_fn_t fn, void *data_inner)\n+void git_protected_config(config_fn_t fn, void *data)\n {\n-\tstruct configset_iter_data data = CONFIGSET_ITER_INIT;\n \tif (!protected_config.hash_initialized)\n \t\tread_protected_config();\n-\tdata.inner = data_inner;\n-\tdata.config_reader = &the_reader;\n-\n-\tconfigset_iter(&protected_config, fn, &data);\n+\tconfigset_iter(&the_reader, &protected_config, fn, data);\n }\n \n /* Functions used historically to read configuration from 'the_repository' */\n@@ -3851,14 +3849,23 @@ int parse_config_key(const char *var,\n \treturn 0;\n }\n \n-const char *current_config_origin_type(void)\n+static int reader_origin_type(struct config_reader *reader,\n+\t\t\t      enum config_origin_type *type)\n {\n-\tint type;\n \tif (the_reader.config_kvi)\n-\t\ttype = the_reader.config_kvi->origin_type;\n+\t\t*type = reader->config_kvi->origin_type;\n \telse if(the_reader.source)\n-\t\ttype = the_reader.source->origin_type;\n+\t\t*type = reader->source->origin_type;\n \telse\n+\t\treturn 1;\n+\treturn 0;\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 \tswitch (type) {\n@@ -3897,14 +3904,21 @@ const char *config_scope_name(enum config_scope scope)\n \t}\n }\n \n-const char *current_config_name(void)\n+static int reader_config_name(struct config_reader *reader, const char **out)\n {\n-\tconst char *name;\n \tif (the_reader.config_kvi)\n-\t\tname = the_reader.config_kvi->filename;\n+\t\t*out = reader->config_kvi->filename;\n \telse if (the_reader.source)\n-\t\tname = the_reader.source->name;\n+\t\t*out = reader->source->name;\n \telse\n+\t\treturn 1;\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 }\ndiff --git a/config.h b/config.h\nindex 7606246531..66c8b996e1 100644\n--- a/config.h\n+++ b/config.h\n@@ -56,6 +56,7 @@ struct git_config_source {\n };\n \n enum config_origin_type {\n+\tCONFIG_ORIGIN_UNKNOWN = 0,\n \tCONFIG_ORIGIN_BLOB,\n \tCONFIG_ORIGIN_FILE,\n \tCONFIG_ORIGIN_STDIN,\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 4ba9eb6560..26e79168f6 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -30,6 +30,9 @@\n  * iterate -> iterate over all values using git_config(), and print some\n  *            data for each\n  *\n+ * git_config_int -> iterate over all values using git_config() and print the\n+ *                   integer value for the entered key or die\n+ *\n  * Examples:\n  *\n  * To print the value with highest priority for key \"foo.bAr Baz.rock\":\n@@ -54,6 +57,17 @@ 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+{\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\tprintf(\"%d\\n\", parsed);\n+\t}\n+\treturn 0;\n+}\n+\n static int early_config_cb(const char *var, const char *value, void *vdata)\n {\n \tconst char *key = vdata;\n@@ -176,6 +190,9 @@ int cmd__config(int argc, const char **argv)\n \t} else if (!strcmp(argv[1], \"iterate\")) {\n \t\tgit_config(iterate_cb, NULL);\n \t\tgoto exit0;\n+\t} else if (argc == 3 && !strcmp(argv[1], \"git_config_int\")) {\n+\t\tgit_config(parse_int_cb, (void *) argv[2]);\n+\t\tgoto exit0;\n \t}\n \n \tdie(\"%s: Please check the syntax and the function name\", argv[0]);\ndiff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh\nindex b38e158d3b..9733bed30a 100755\n--- a/t/t1308-config-set.sh\n+++ b/t/t1308-config-set.sh\n@@ -120,6 +120,10 @@ test_expect_success 'find integer value for a key' '\n \tcheck_config get_int lamb.chop 65\n '\n \n+test_expect_success 'parse integer value during iteration' '\n+\tcheck_config git_config_int lamb.chop 65\n+'\n+\n test_expect_success 'find string value for a key' '\n \tcheck_config get_string case.baz hask &&\n \tcheck_config expect_code 1 get_string case.ba \"Value not found for \\\"case.ba\\\"\"\n@@ -134,6 +138,11 @@ test_expect_success 'find integer if value is non parse-able' '\n \tcheck_config expect_code 128 get_int lamb.head\n '\n \n+test_expect_success 'non parse-able integer value during iteration' '\n+\tcheck_config expect_code 128 git_config_int lamb.head 2>result &&\n+\tgrep \"fatal: bad numeric config value .* in file \\.git/config\" result\n+'\n+\n test_expect_success 'find bool value for the entered key' '\n \tcheck_config get_bool goat.head 1 &&\n \tcheck_config get_bool goat.skin 0 &&\n"},{"id":"473606","messageId":"20230316211616.312796-1-jonathantanmy@google.com","threadId":"59327","inReplyTo":"75d0f0efb7924fa6ce2387b1a8e4105d672a991f.1678925506.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 1/8] config.c: plumb config_source through static fns","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-03-16T21:16:16Z","receivedAt":"2023-03-16T21:16:32Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> but we will rename \"cf\" to \"cs\" by the end of the\n> series.\n\nLet's remember that this might not happen if we decide to drop the last\npatch. Other than that, everything looks good in this patch.\n \n"},{"id":"473607","messageId":"20230316211821.313426-1-jonathantanmy@google.com","threadId":"59327","inReplyTo":"7555da0b0e012a5fd28ca1a70a4a0897514cd607.1678925506.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/8] config.c: don't assign to \"cf_global\" directly","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-03-16T21:18:21Z","receivedAt":"2023-03-16T21:18:27Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> +static inline void config_reader_push_source(struct config_source *top)\n> +{\n> +\tif (cf_global)\n> +\t\ttop->prev = cf_global;\n\nDon't we want to set prev unconditionally here (i.e. set it to NULL if\ncf_global was NULL)?\n\n> +\tcf_global = top;\n> +}\n> +\n> +static inline struct config_source *config_reader_pop_source()\n> +{\n> +\tstruct config_source *ret;\n> +\tif (!cf_global)\n> +\t\tBUG(\"tried to pop config source, but we weren't reading config\");\n> +\tret = cf_global;\n> +\tcf_global = cf_global->prev;\n> +\treturn ret;\n> +}\n\n...since we use it unconditionally here.\n\nThe rest looks good.\n"},{"id":"473608","messageId":"20230316212231.314236-1-jonathantanmy@google.com","threadId":"59327","inReplyTo":"4347896f0a4896efe5b1410b72f5b583ad6bd9e0.1678925506.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 3/8] config.c: create config_reader and the_reader","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-03-16T21:22:31Z","receivedAt":"2023-03-16T21:22:42Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> -static inline struct config_source *config_reader_pop_source()\n> +static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n>  {\n>  \tstruct config_source *ret;\n> -\tif (!cf_global)\n> +\tif (!reader->source)\n>  \t\tBUG(\"tried to pop config source, but we weren't reading config\");\n> -\tret = cf_global;\n> -\tcf_global = cf_global->prev;\n> +\tret = reader->source;\n> +\treader->source = reader->source->prev;\n> +\t/* FIXME remove this when cf is removed. */\n> +\tcf_global = reader->source;\n>  \treturn ret;\n>  }\n\nIn the FIXME, it's cf_global not cf.\n\nEverything else looks good.\n"},{"id":"473609","messageId":"xmqq4jqkjsd2.fsf@gitster.g","threadId":"59327","inReplyTo":"20230316211821.313426-1-jonathantanmy@google.com","subject":"Re: [PATCH v2 2/8] config.c: don't assign to \"cf_global\" directly","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-16T21:31:21Z","receivedAt":"2023-03-16T21:31:25Z","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>> +static inline void config_reader_push_source(struct config_source *top)\n>> +{\n>> +\tif (cf_global)\n>> +\t\ttop->prev = cf_global;\n>\n> Don't we want to set prev unconditionally here (i.e. set it to NULL if\n> cf_global was NULL)?\n\nGood eyes.  You are absolutely right.\nThanks.\n\n>\n>> +\tcf_global = top;\n>> +}\n>> +\n>> +static inline struct config_source *config_reader_pop_source()\n>> +{\n>> +\tstruct config_source *ret;\n>> +\tif (!cf_global)\n>> +\t\tBUG(\"tried to pop config source, but we weren't reading config\");\n>> +\tret = cf_global;\n>> +\tcf_global = cf_global->prev;\n>> +\treturn ret;\n>> +}\n>\n> ...since we use it unconditionally here.\n>\n> The rest looks good.\n"},{"id":"473610","messageId":"20230316222200.320596-1-jonathantanmy@google.com","threadId":"59327","inReplyTo":"3c83d9535a037653c7de2d462a4df3a3c43a9308.1678925506.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 7/8] config: report cached filenames in die_bad_number()","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-03-16T22:22:00Z","receivedAt":"2023-03-16T22:23:31Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Ah, thanks for spotting this bug! It is a minor one, but this now makes\nme think that we should definitely do this refactoring of a struct\ncontaining all the relevant config state and passing it to functions as\nmuch as possible (as opposed to merely leaning towards the idea).\n\n\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> Fix this by refactoring the current_config_* functions into variants\n> that don't BUG() when we aren't reading config, and using the resulting\n> functions in die_bad_number(). Refactoring is needed because \"git config\n> --get[-regexp] --type=int\" parses the int value _after_ parsing the\n> config file, which will run into the BUG().\n\nYou say \"fix this\", but are there actually 2 bugs (so, \"fix these\")?\nFirstly, that BUG() is run into when invoking \"git config\" the way\nyou describe, and secondly, die_bad_number() only reading cf and not\nchecking kvi to see if anything's there. (I'm not sure how to reproduce\nthe latter, though.)\n\n> Also, plumb \"struct config_reader\" into the new functions. This isn't\n> necessary per se, but this generalizes better, so it might help us avoid\n> yet another refactor.\n\nHmm...I thought this would be desired because we don't want the_reader\nto be used from non-public functions anyway, so we can just state\nthat that is the reason (and not worry about using future refactors as\na justification).\n\nThe code itself looks good.\n"},{"id":"473611","messageId":"20230316222923.321352-1-jonathantanmy@google.com","threadId":"59327","inReplyTo":"pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/8] config.c: use struct for config reading state","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-03-16T22:29:23Z","receivedAt":"2023-03-16T22:30:11Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"So overall what this series does is to create a struct that contains\nconfig parsing state (roughly speaking...it's either parsing state or\na \"kvi\" that contains some of the parsing state that existed when its\nvalue was parsed), starts a pattern in config.c in which that struct\nis passed between functions as much as possible, and establishes a few\nfunctions like reader_origin_type() that make it convenient to work\non the basis of this struct as opposed to on the config_source and kvi\nindividually. And following this pattern, I think, likely would have\nenabled us to avoid the bug in patch 7/8 and enable us to avoid similar\nbugs in the future, so overall I'm happy with this series.\n\n\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>    Introduce 8/8 to get rid of the confusing acronym \"struct config_source\n>    cf\", but I don't mind ejecting it if it's too much churn.\n\nI think patch 8/8 is not worth including, so I didn't leave comments on\nit, but I'm fine if it ends up merged too.\n \n"},{"id":"473614","messageId":"kl6lpm98uwze.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"20230316211821.313426-1-jonathantanmy@google.com","subject":"Re: [PATCH v2 2/8] config.c: don't assign to \"cf_global\" directly","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-16T22:56:05Z","receivedAt":"2023-03-16T22:56:11Z","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>> +static inline void config_reader_push_source(struct config_source *top)\n>> +{\n>> +\tif (cf_global)\n>> +\t\ttop->prev = cf_global;\n>\n> Don't we want to set prev unconditionally here (i.e. set it to NULL if\n> cf_global was NULL)?\n>\n>> +\tcf_global = top;\n>> +}\n>> +\n>> +static inline struct config_source *config_reader_pop_source()\n>> +{\n>> +\tstruct config_source *ret;\n>> +\tif (!cf_global)\n>> +\t\tBUG(\"tried to pop config source, but we weren't reading config\");\n>> +\tret = cf_global;\n>> +\tcf_global = cf_global->prev;\n>> +\treturn ret;\n>> +}\n>\n> ...since we use it unconditionally here.\n\nAh, thanks. It's safe as of this patch, since we always zero-initialize\n\"struct config_source\" and nothing else sets \".prev\", but this asymmetry\nis a bug waiting to happen.\n"},{"id":"473617","messageId":"kl6lmt4cuwj4.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"20230316222200.320596-1-jonathantanmy@google.com","subject":"Re: [PATCH v2 7/8] config: report cached filenames in die_bad_number()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-16T23:05:51Z","receivedAt":"2023-03-16T23:06:04Z","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>>                                Refactoring is needed because \"git config\n>> --get[-regexp] --type=int\" parses the int value _after_ parsing the\n>> config file, which will run into the BUG().\n>\n> You say \"fix this\", but are there actually 2 bugs (so, \"fix these\")?\n> Firstly, that BUG() is run into when invoking \"git config\" the way\n> you describe, and secondly, die_bad_number() only reading cf and not\n> checking kvi to see if anything's there. (I'm not sure how to reproduce\n> the latter, though.)\n\nThere is actually only one bug (the latter). That is tested by the new\ntest I added in this patch. To reproduce it, we need:\n\n- To iterate a config_set (git_config() or repo_config() will suffice),\n  in which case the config_kvi is set, but not cf.\n- Then in the config_fn_t we pass to it, we call git_parse_int() on an\n  invalid number, which will result in die_bad_number(), which prints\n  the less specific message.\n\nThe former case isn't a bug. We never ran into the BUG() when invoking\n\"git config\" because die_bad_number() doesn't use current_* prior to\nthis patch (which is where the BUG() is). t1300:'invalid unit'\ndemonstrates that we print the correct message (and that we don't\nBUG()):\n\n  test_expect_success 'invalid unit' '\n    git config aninvalid.unit \"1auto\" &&\n    test_cmp_config 1auto aninvalid.unit &&\n    test_must_fail git config --int --get aninvalid.unit 2>actual &&\n    test_i18ngrep \"bad numeric config value .1auto. for .aninvalid.unit. in file .git/config: invalid unit\" actual\n  '\n\n\n(which is a good signal that I should probably reword the commit\nmessage)\n\n>\n>> Also, plumb \"struct config_reader\" into the new functions. This isn't\n>> necessary per se, but this generalizes better, so it might help us avoid\n>> yet another refactor.\n>\n> Hmm...I thought this would be desired because we don't want the_reader\n> to be used from non-public functions anyway, so we can just state\n> that that is the reason (and not worry about using future refactors as\n> a justification).\n\nAh, good point, thanks.\n"},{"id":"473622","messageId":"RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com","subject":"[RFC PATCH 0/5] bypass config.c global state with configset","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-03-17T05:01:05Z","receivedAt":"2023-03-17T05:01:22Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"On Thu, Mar 16 2023, Glen Choo via GitGitGadget wrote:\n\n> After reflecting on Ævar's responses on v1, I'm fairly convinced that\n> \"struct config_reader\" shouldn't exist in the long term. I've written my\n> thoughts on a good long term direction in the \"Leftover bits\" section. Based\n> on that, I've also updated my WIP libification patches [1] to remove \"struct\n> config_reader\" from the library interface, and think it looks a lot better\n> as a result.\n\nThat libification url\n(https://github.com/git/git/compare/master...chooglen:git:config-lib-parsing)\ndoesn't work for me, and I didn't find a branch with that name in your\npublished repo. So maybe you've already done all this\npost-libification work...\n\n> = Leftover bits\n>\n> We still need a global \"the_reader\" because config callbacks are reading\n> auxiliary information about the config (e.g. line number, file name) via\n> global functions (e.g. current_config_line(), current_config_name()). This\n> is either because the callback uses this info directly (like\n> builtin/config.c printing the filename and scope of the value) or for error\n> reporting (like git_parse_int() reporting the filename of the value it\n> failed to parse).\n>\n> If we had a way to plumb the state from \"struct config_reader\" to the config\n> callback functions, we could initialize \"struct config_reader\" in the config\n> machinery whenever we read config (instead of asking the caller to\n> initialize \"struct config_reader\" themselves), and config reading could\n> become a thread-safe operation. There isn't an obvious way to plumb this\n> state to config callbacks without adding an additional arg to config_fn_t\n> and incurring a lot of churn, but if we start replacing \"config_fn_t\" with\n> the configset API (which we've independently wanted for some time), this may\n> become feasible.\n\n...in any case. This RFC expands a bit on my comments on the v1 (at\n[1] and upthread). It doesn't get all the way there, but with the\nsmall change in 5/5 we've gotten rid of current_config_line(), the\n1-4/5 are trivial pre-refactorings to make that diff smaller\n(e.g. moving the \"struct key_value_info\" around in config.h).\n\nMaybe it still makes sense to go for this \"the_reader\" intermediate\nstep, but I can't help but think that we could just go for it all in\none leap, and that you've just got stuck on thinking that you needed\nto change \"config_fn_t\" for all its callers.\n\nAs the 5/5 here shows we have various orthagonal uses of the\n\"config_fn_t\" in config.c, and can just implement a new callback type\nfor the edge cases where we need the file & line info.\n\nThis still leave the current_config_name() etc, which\ne.g. builtin/config.c still uses. In your series you've needed to add\nthe new \"reader\" parameter for everything from do_config_from(), but\nif we're doing that can't we instead just go straight to passing a\n\"struct key_value_info *\" (perhaps with an added \"name\" field) all the\nway down, replacing \"cf->linenr\" etc?\n\nInstead you end up extending \"the_reader\" everywhere, including to\ne.g. configset_iter, which I think as the 5/5 here shows isn't needed,\nbut maybe I've missed something.\n\nSimilarly, you mention git_parse_int() wanting to report a filename\nand/or line number. I'm aware that it can do that, but it doesn't do\nso in the common case, e.g.:\n\n\tgit -c format.filenameMaxLength=abc log\n\tfatal: bad numeric config value 'abc' for 'format.filenamemaxlength': invalid unit\n\nAnd the same goes for writing it to e.g. ~/.gitconfig. It's only if\nyou use \"git config --file\" or similar that we'll report a filename.\n\nSo just as with the current_config_line() I wonder if you just grepped\nfor e.g. git_config_int() and thought because we have a lot of users\nof it that all of them would require this data, but for e.g. this\nlog.c caller (and most or all of the others) we'll be reading the\nnormal config, and aren't getting any useful info from\ndie_bad_number() that we wouldn't get from an error function that\ndidn't need the \"linenr\" etc.\n\n> And if we do this, \"struct config_reader\" itself will probably become\n> obsolete, because we'd be able to plumb only the relevant state for the\n> current operation, e.g. if we are parsing a config file, we'd pass only the\n> config file parsing state, instead of \"struct config_reader\", which also\n> contains config set iterating state. In such a scenario, we'd probably want\n> to pass \"struct key_value_info\" to the config callback, since that's all the\n> callback should be interested in anyway. Interestingly, this was proposed by\n> Junio back in [4], and we didn't do this back then out of concern for the\n> churn (just like in v1).\n\nI think we can make it even simpler than that, and from playing around\nwith builtin/config.c a bit after the 5/5 here I got a POC working\n(but am not posting it here, didn't have time to clean it up).\n\nWe can just make config_set_callback() and configset_iter()\nnon-static, so e.g. the builtin/config.c caller that implements\n\"--show-origin\" can keep its config_with_options(...) call, but\ninstead of \"streaming\" the config, it'll buffer it up into a\nconfigset.\n\nThe advantage of that is that with the configset API we'll get a\n\"struct key_value_info *\" for free on the other end. I.e. we'll\nconfigset_iter() with a fn=NULL, but with a defined \"config_kvi_fn_t\",\nwhich 5/5 is adding.\n\nBut I haven't done that work (and am not planning to finish this), but\nmaybe this helps.\n\nWe'll also need to track the equivalent of \"cf->linenr\" etc. while we\ndo the actual initial parse. I think it might be simpler to start by\nconverting those \"linenr\" to a \"struct key_value_info\" that's placed\nin the \"config_source\" right away.\n\nI.e. when we pass it to the error handlers we'll need to give them\naccess to the \"linenr\", but we don't want to provide e.g. \"eof\" (which\nis internal-only state).\n\nI wonder how much else you're converting here is actually dead code in\nthe end (or can trivially be made dead). E.g. the\ncurrent_config_line() change you make in 1/8 is never going to use the\n\"cf_global\" if combined with the 5/5 change here.\n\n1. https://lore.kernel.org/git/230308.867cvrziac.gmgdl@evledraar.gmail.com/\n\nÆvar Arnfjörð Bjarmason (5):\n  config.h: move up \"struct key_value_info\"\n  config.c: use \"enum config_origin_type\", not \"int\"\n  config API: add a config_origin_type_name() helper\n  config.c: refactor configset_iter()\n  config API: add and use a repo_config_kvi()\n\n builtin/remote.c       | 11 +++----\n config.c               | 67 ++++++++++++++++++++++++++----------------\n config.h               | 29 +++++++++++++-----\n t/helper/test-config.c | 13 ++++----\n t/t5505-remote.sh      |  7 +++--\n 5 files changed, 80 insertions(+), 47 deletions(-)\n\n-- \n2.40.0.rc1.1034.g5867a1b10c5\n\n"},{"id":"473623","messageId":"RFC-patch-1.5-13d383536bd-20230317T042408Z-avarab@gmail.com","threadId":"59327","inReplyTo":"RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com","subject":"[RFC PATCH 1/5] config.h: move up \"struct key_value_info\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-03-17T05:01:06Z","receivedAt":"2023-03-17T05:01:22Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Move the declaration of the \"struct key_value_info\" earlier in the\nfile, in a subsequent commit we'll need it shortly after this line.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n config.h | 14 +++++++-------\n 1 file changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/config.h b/config.h\nindex 7606246531a..571b92d674a 100644\n--- a/config.h\n+++ b/config.h\n@@ -110,6 +110,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@@ -612,13 +619,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-- \n2.40.0.rc1.1034.g5867a1b10c5\n\n"},{"id":"473624","messageId":"RFC-patch-2.5-643096b69c2-20230317T042408Z-avarab@gmail.com","threadId":"59327","inReplyTo":"RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com","subject":"[RFC PATCH 2/5] config.c: use \"enum config_origin_type\", not \"int\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-03-17T05:01:07Z","receivedAt":"2023-03-17T05:01:25Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change the current_config_origin_type() function to use the\nappropriate enum type for its \"type\" variable.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n config.c | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)\n\ndiff --git a/config.c b/config.c\nindex 00090a32fc3..a4105c456c3 100644\n--- a/config.c\n+++ b/config.c\n@@ -3768,7 +3768,8 @@ int parse_config_key(const char *var,\n \n const char *current_config_origin_type(void)\n {\n-\tint type;\n+\tenum config_origin_type type;\n+\n \tif (current_config_kvi)\n \t\ttype = current_config_kvi->origin_type;\n \telse if(cf)\n-- \n2.40.0.rc1.1034.g5867a1b10c5\n\n"},{"id":"473625","messageId":"RFC-patch-3.5-e0055e2865e-20230317T042408Z-avarab@gmail.com","threadId":"59327","inReplyTo":"RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com","subject":"[RFC PATCH 3/5] config API: add a config_origin_type_name() helper","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-03-17T05:01:08Z","receivedAt":"2023-03-17T05:01:28Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add a config_origin_type_name() helper function. In a subsequent\ncommit we'll want to invoke this part of current_config_origin_type()\nwithout requiring the global \"current_config_kvi\" or \"cf\" state.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n config.c | 25 +++++++++++++++----------\n config.h |  6 ++++++\n 2 files changed, 21 insertions(+), 10 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex a4105c456c3..a65e7bb36d3 100644\n--- a/config.c\n+++ b/config.c\n@@ -3766,17 +3766,8 @@ int parse_config_key(const char *var,\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;\n-\n-\tif (current_config_kvi)\n-\t\ttype = current_config_kvi->origin_type;\n-\telse if(cf)\n-\t\ttype = cf->origin_type;\n-\telse\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@@ -3793,6 +3784,20 @@ 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;\n+\n+\tif (current_config_kvi)\n+\t\ttype = current_config_kvi->origin_type;\n+\telse if(cf)\n+\t\ttype = cf->origin_type;\n+\telse\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) {\ndiff --git a/config.h b/config.h\nindex 571b92d674a..a9cb01e9405 100644\n--- a/config.h\n+++ b/config.h\n@@ -117,6 +117,12 @@ struct key_value_info {\n \tenum config_scope scope;\n };\n \n+/**\n+ * Given the \"enum config_origin_type origin_type\"\n+ * (e.g. CONFIG_ORIGIN_BLOB) return a string (e.g. \"blob\").\n+ */\n+const char *config_origin_type_name(enum config_origin_type type);\n+\n /**\n  * A config callback function takes three parameters:\n  *\n-- \n2.40.0.rc1.1034.g5867a1b10c5\n\n"},{"id":"473626","messageId":"RFC-patch-4.5-cf224010970-20230317T042408Z-avarab@gmail.com","threadId":"59327","inReplyTo":"RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com","subject":"[RFC PATCH 4/5] config.c: refactor configset_iter()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-03-17T05:01:09Z","receivedAt":"2023-03-17T05:01:29Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Use variables rather than long lines in configset_iter(), and use our\nown \"kvi\" rather than relying on the global \"current_config_kvi\"\nwithin this function.\n\nThere's no functional change here, but doing this will make a\nsubsequent functional change smaller.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n config.c | 17 +++++++++++------\n 1 file changed, 11 insertions(+), 6 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex a65e7bb36d3..230a98b0631 100644\n--- a/config.c\n+++ b/config.c\n@@ -2227,17 +2227,22 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \tstruct configset_list *list = &cs->list;\n \n \tfor (i = 0; i < list->nr; i++) {\n+\t\tconst char *key;\n+\t\tconst char *val;\n+\t\tstruct key_value_info *kvi;\n+\n \t\tentry = list->items[i].e;\n \t\tvalue_index = list->items[i].value_index;\n \t\tvalues = &entry->value_list;\n \n-\t\tcurrent_config_kvi = 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      current_config_kvi->filename,\n-\t\t\t\t\t      current_config_kvi->linenr);\n+\t\tkey = entry->key;\n+\t\tval = values->items[value_index].string;\n+\t\tkvi = values->items[value_index].util;\n \n+\t\tcurrent_config_kvi = kvi;\n+\t\tif (fn(key, val, data) < 0)\n+\t\t\tgit_die_config_linenr(entry->key, kvi->filename,\n+\t\t\t\t\t      kvi->linenr);\n \t\tcurrent_config_kvi = NULL;\n \t}\n }\n-- \n2.40.0.rc1.1034.g5867a1b10c5\n\n"},{"id":"473627","messageId":"RFC-patch-5.5-2b80d293c83-20230317T042408Z-avarab@gmail.com","threadId":"59327","inReplyTo":"RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com","subject":"[RFC PATCH 5/5] config API: add and use a repo_config_kvi()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-03-17T05:01:10Z","receivedAt":"2023-03-17T05:01:33Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Introduce a repo_config_kvi(), which is a repo_config() which calls a\n\"config_kvi_fn_t\", rather than the \"config_fn_t\" that repo_config()\nuses.\n\nThis allows us to pass along the \"struct key_value_info *\" directly,\nrather than having the callback grab it from the global\n\"current_config_kvi\" that we've been maintaining in\n\"configset_iter()\".\n\nThis change is an alternate direction to the topic at [1], this\nexpands on the vague suggestions I made in [2] to go in this\ndirection.\n\nAs this shows we can split apart the \"config_fn_t\", and thus avoid\nhaving to change the hundreds of existing \"config_fn_t\" callers. By\ndoing this we can already get rid of the current_config_kvi()\nfunction, as \"builtin/remote.c\" and \"t/helper/test-config.c\" were the\nonly users of it.\n\nThe change to \"t/t5505-remote.sh\" ensures that the change here to\nconfig_read_push_default() isn't breaking things. It's the only test\nthat would go through that codepath, but nothing asserted that we'd\nget the correct line number. Let's sanity check that, as well as the\nother callback data.\n\nThis leaves the other current_config_*() functions. Subsequent commits\nwill need to deal with those.\n\n1. https://lore.kernel.org/git/pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com/\n2. https://lore.kernel.org/git/230307.86wn3szrzu.gmgdl@evledraar.gmail.com/\n3. https://lore.kernel.org/git/230308.867cvrziac.gmgdl@evledraar.gmail.com/\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/remote.c       | 11 ++++++-----\n config.c               | 32 ++++++++++++++++++--------------\n config.h               |  9 ++++++++-\n t/helper/test-config.c | 13 +++++++------\n t/t5505-remote.sh      |  7 +++++--\n 5 files changed, 44 insertions(+), 28 deletions(-)\n\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex 729f6f3643a..c65bce05034 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -644,17 +644,18 @@ struct push_default_info\n };\n \n static int config_read_push_default(const char *key, const char *value,\n-\tvoid *cb)\n+\t\t\t\t    struct 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+\tif (kvi->filename)\n+\t\tstrbuf_addstr(&info->origin, kvi->filename);\n+\tinfo->linenr = kvi->linenr;\n \n \treturn 0;\n }\n@@ -663,7 +664,7 @@ static void handle_push_default(const char* old_name, const char* new_name)\n {\n \tstruct push_default_info push_default = {\n \t\told_name, CONFIG_SCOPE_UNKNOWN, STRBUF_INIT, -1 };\n-\tgit_config(config_read_push_default, &push_default);\n+\trepo_config_kvi(the_repository, config_read_push_default, &push_default);\n \tif (push_default.scope >= CONFIG_SCOPE_COMMAND)\n \t\t; /* pass */\n \telse if (push_default.scope >= CONFIG_SCOPE_LOCAL) {\ndiff --git a/config.c b/config.c\nindex 230a98b0631..1b3f534757c 100644\n--- a/config.c\n+++ b/config.c\n@@ -2219,7 +2219,8 @@ int config_with_options(config_fn_t fn, void *data,\n \treturn ret;\n }\n \n-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n+static void configset_iter(struct config_set *cs, config_fn_t fn,\n+\t\t\t   config_kvi_fn_t fn_kvi, void *data)\n {\n \tint i, value_index;\n \tstruct string_list *values;\n@@ -2230,6 +2231,7 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \t\tconst char *key;\n \t\tconst char *val;\n \t\tstruct key_value_info *kvi;\n+\t\tint ret;\n \n \t\tentry = list->items[i].e;\n \t\tvalue_index = list->items[i].value_index;\n@@ -2239,11 +2241,15 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \t\tval = values->items[value_index].string;\n \t\tkvi = values->items[value_index].util;\n \n-\t\tcurrent_config_kvi = kvi;\n-\t\tif (fn(key, val, data) < 0)\n+\t\tif (!fn_kvi)\n+\t\t\tcurrent_config_kvi = kvi;\n+\t\tret = fn_kvi ? fn_kvi(key, val, kvi, data) :\n+\t\t\tfn(key, val, data);\n+\t\tcurrent_config_kvi = NULL;\n+\n+\t\tif (ret < 0)\n \t\t\tgit_die_config_linenr(entry->key, kvi->filename,\n \t\t\t\t\t      kvi->linenr);\n-\t\tcurrent_config_kvi = NULL;\n \t}\n }\n \n@@ -2567,7 +2573,13 @@ 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(repo->config, fn, data);\n+\tconfigset_iter(repo->config, fn, NULL, data);\n+}\n+\n+void repo_config_kvi(struct repository *repo, config_kvi_fn_t fn, void *data)\n+{\n+\tgit_config_check_init(repo);\n+\tconfigset_iter(repo->config, NULL, fn, data);\n }\n \n int repo_config_get_value(struct repository *repo,\n@@ -2670,7 +2682,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(&protected_config, fn, data);\n+\tconfigset_iter(&protected_config, fn, NULL, data);\n }\n \n /* Functions used historically to read configuration from 'the_repository' */\n@@ -3843,14 +3855,6 @@ enum config_scope current_config_scope(void)\n \t\treturn current_parsing_scope;\n }\n \n-int current_config_line(void)\n-{\n-\tif (current_config_kvi)\n-\t\treturn current_config_kvi->linenr;\n-\telse\n-\t\treturn cf->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 a9cb01e9405..de5350dbee5 100644\n--- a/config.h\n+++ b/config.h\n@@ -143,6 +143,13 @@ const char *config_origin_type_name(enum config_origin_type type);\n  */\n typedef int (*config_fn_t)(const char *, const char *, void *);\n \n+/**\n+ * Like config_fn_t, but before the callback-specific data we'll get a\n+ * \"struct key_value_info\" indicating the origin of the config.\n+ */\n+typedef int (*config_kvi_fn_t)(const char *key, const char *var,\n+\t\t\t       struct key_value_info *kvi, void *data);\n+\n int git_default_config(const char *, const char *, void *);\n \n /**\n@@ -371,7 +378,6 @@ 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 \n /*\n  * Match and parse a config key of the form:\n@@ -498,6 +504,7 @@ int git_configset_get_pathname(struct config_set *cs, const char *key, const cha\n /* Functions for reading a repository's config */\n struct repository;\n void repo_config(struct repository *repo, config_fn_t fn, void *data);\n+void repo_config_kvi(struct repository *repo, config_kvi_fn_t fn, void *data);\n int repo_config_get_value(struct repository *repo,\n \t\t\t  const char *key, const char **value);\n const struct string_list *repo_config_get_value_multi(struct repository *repo,\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 4ba9eb65606..2ef67b18a0b 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -37,7 +37,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, void *data UNUSED)\n {\n \tstatic int nr;\n \n@@ -46,10 +47,10 @@ static int iterate_cb(const char *var, const char *value, void *data UNUSED)\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@@ -174,7 +175,7 @@ int cmd__config(int argc, const char **argv)\n \t\t\tgoto exit1;\n \t\t}\n \t} else if (!strcmp(argv[1], \"iterate\")) {\n-\t\tgit_config(iterate_cb, NULL);\n+\t\trepo_config_kvi(the_repository, iterate_cb, NULL);\n \t\tgoto exit0;\n \t}\n \ndiff --git a/t/t5505-remote.sh b/t/t5505-remote.sh\nindex 43b7bcd7159..d9659b9c65e 100755\n--- a/t/t5505-remote.sh\n+++ b/t/t5505-remote.sh\n@@ -853,8 +853,11 @@ test_expect_success 'rename a remote' '\n \t(\n \t\tcd four &&\n \t\tgit config branch.main.pushRemote origin &&\n-\t\tGIT_TRACE2_EVENT=$(pwd)/trace \\\n-\t\t\tgit remote rename --progress origin upstream &&\n+\t\tGIT_TRACE2_EVENT=$PWD/trace \\\n+\t\t\tgit remote rename --progress origin upstream 2>warn &&\n+\t\tgrep -F \"The global configuration remote.pushDefault\" warn &&\n+\t\tgrep \"/\\.gitconfig:2$\" warn &&\n+\t\tgrep \"remote '\\''origin'\\''\" warn &&\n \t\ttest_region progress \"Renaming remote references\" trace &&\n \t\tgrep \"pushRemote\" .git/config &&\n \t\ttest -z \"$(git for-each-ref refs/remotes/origin)\" &&\n-- \n2.40.0.rc1.1034.g5867a1b10c5\n\n"},{"id":"473653","messageId":"xmqqpm97gxh2.fsf@gitster.g","threadId":"59327","inReplyTo":"RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com","subject":"Re: [RFC PATCH 0/5] bypass config.c global state with configset","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-17T16:21:29Z","receivedAt":"2023-03-17T16:21:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\n> Ævar Arnfjörð Bjarmason (5):\n>   config.h: move up \"struct key_value_info\"\n>   config.c: use \"enum config_origin_type\", not \"int\"\n>   config API: add a config_origin_type_name() helper\n>   config.c: refactor configset_iter()\n>   config API: add and use a repo_config_kvi()\n\nI haven't reached the end of 5/5 but it is a shame to see that these\nobviously good and trivial clean-up patches like [1-4/5] have to be\nburied in an RFC patch and benefit of applying them alone becomes\nunclear only because it is done in an area that is actively worked\non by others.  The latter is unfortunately inherent to the approach\nof \"commenting by patches\" but hopefully we can see them reappear\nwhen the tree is quiescent.\n\nThanks.\n"},{"id":"473654","messageId":"kl6lcz57uytk.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com","subject":"Re: [RFC PATCH 0/5] bypass config.c global state with configset","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-17T16:28:39Z","receivedAt":"2023-03-17T16:28:53Z","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> That libification url\n> (https://github.com/git/git/compare/master...chooglen:git:config-lib-parsing)\n> doesn't work for me, and I didn't find a branch with that name in your\n> published repo. So maybe you've already done all this\n> post-libification work...\n\nArgh, sorry, I renamed the branch.\n\n  https://github.com/git/git/compare/master...chooglen:git:config/read-without-globals\n\n> ...in any case. This RFC expands a bit on my comments on the v1 (at\n> [1] and upthread). It doesn't get all the way there, but with the\n> small change in 5/5 we've gotten rid of current_config_line(), the\n> 1-4/5 are trivial pre-refactorings to make that diff smaller\n> (e.g. moving the \"struct key_value_info\" around in config.h).\n\nThanks. I haven't read through it yet, but it sounds promising :)\n"},{"id":"473656","messageId":"xmqqlejvguwj.fsf@gitster.g","threadId":"59327","inReplyTo":"RFC-patch-5.5-2b80d293c83-20230317T042408Z-avarab@gmail.com","subject":"Re: [RFC PATCH 5/5] config API: add and use a repo_config_kvi()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-17T17:17:00Z","receivedAt":"2023-03-17T17:17:06Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\n> Introduce a repo_config_kvi(), which is a repo_config() which calls a\n> \"config_kvi_fn_t\", rather than the \"config_fn_t\" that repo_config()\n> uses.\n>\n> This allows us to pass along the \"struct key_value_info *\" directly,\n> rather than having the callback grab it from the global\n> \"current_config_kvi\" that we've been maintaining in\n> \"configset_iter()\".\n\nNice that you can plumb kvi through the callchain but without having\nto touch the end-user-visible callback data *cb or the function\nsignature of the cllaback functions.\n\n> This leaves the other current_config_*() functions. Subsequent commits\n> will need to deal with those.\n\n"},{"id":"473666","messageId":"kl6la60buquj.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com","subject":"Re: [RFC PATCH 0/5] bypass config.c global state with configset","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-17T19:20:52Z","receivedAt":"2023-03-17T19:21:17Z","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> Maybe it still makes sense to go for this \"the_reader\" intermediate\n> step, but I can't help but think that we could just go for it all in\n> one leap, and that you've just got stuck on thinking that you needed\n> to change \"config_fn_t\" for all its callers.\n>\n> As the 5/5 here shows we have various orthagonal uses of the\n> \"config_fn_t\" in config.c, and can just implement a new callback type\n> for the edge cases where we need the file & line info.\n>\n> This still leave the current_config_name() etc, which\n> e.g. builtin/config.c still uses. In your series you've needed to add\n> the new \"reader\" parameter for everything from do_config_from(), but\n> if we're doing that can't we instead just go straight to passing a\n> \"struct key_value_info *\" (perhaps with an added \"name\" field) all the\n> way down, replacing \"cf->linenr\" etc?\n\nIn the end state, I also think we should be passing \"struct\nkey_value_info *\" around instead of \"cf\", so I think we are seeing\n\"the_reader\" in the same way (as a transitional state).\n\nI considered the \"repo_config_kvi() + config_fn_kvi_t\" as well, but I\nrejected it (before discussion on the list, whoops) because I didn't\nwant to add _yet another_ set of parallel config APIs, e.g. we already\nhave repo_config(), git_config(), configset*(),\ngit_config_from_file*(). Multiplying that by 2 to add *_kvi() seems like\nway too much, especially when it seems clear that our current definition\nof config_fn_t has some problems.\n\nMaybe we could deprecate the non-*_kvi(), and have both as a\ntransitional state? It might work, but I think biting the bullet and\nchanging config_fn_t would be easier actually.\n\nI'll try applying your series on top of my 1/8 (and maybe 7/8, see next\nreply) and extending it to cover \"cf\" (instead of just\ncurrent_config_kvi()) to see whether the *_kvi() approach saves a lot of\nunnecessary plumbing. I'd still feel very strongly about getting rid of\nall of the non *_kvi() versions, though, but maybe that would happen in\na cleanup topic.\n\n> Similarly, you mention git_parse_int() wanting to report a filename\n> and/or line number. I'm aware that it can do that, but it doesn't do\n> so in the common case, e.g.:\n>\n> \tgit -c format.filenameMaxLength=abc log\n> \tfatal: bad numeric config value 'abc' for 'format.filenamemaxlength': invalid unit\n>\n> And the same goes for writing it to e.g. ~/.gitconfig. It's only if\n> you use \"git config --file\" or similar that we'll report a filename.\n\nThat's true, but I think that's a bug, not a feature. See 7/8 [1] where\nI addressed it.\n\n1.  https://lore.kernel.org/git/3c83d9535a037653c7de2d462a4df3a3c43a9308.1678925506.git.gitgitgadget@gmail.com/\n\n> We can just make config_set_callback() and configset_iter()\n> non-static, so e.g. the builtin/config.c caller that implements\n> \"--show-origin\" can keep its config_with_options(...) call, but\n> instead of \"streaming\" the config, it'll buffer it up into a\n> configset.\n\nHm, so to extrapolate, we could make it so that nobody outside of\nconfig.c uses the *config_from_file() APIs directly. Instead, all reads\nget buffered up into a configset. That might not be a bad idea. It would\ndefinitely help with some of your goals of config API surface reduction.\n\nThis would be friendlier in cases where we were already creating custom\nconfigsets (I know we have some of those, but I don't recall where), but\nin cases where we were reading the file directly (e.g.\nbuiltin/config.c), we'd be taking a memory and runtime hit. I'm not sure\nhow I (or others) feel about that yet.\n"},{"id":"473672","messageId":"20230317205909.1089309-1-jonathantanmy@google.com","threadId":"59327","inReplyTo":"RFC-patch-5.5-2b80d293c83-20230317T042408Z-avarab@gmail.com","subject":"Re: [RFC PATCH 5/5] config API: add and use a repo_config_kvi()","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-03-17T20:59:08Z","receivedAt":"2023-03-17T20:59:39Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n> This change is an alternate direction to the topic at [1], this\n> expands on the vague suggestions I made in [2] to go in this\n> direction.\n\nAre you suggesting that we merge your patches instead of Glen's? (Or\nmaybe merge your patches and a subset of Glen's, but I'm not sure which\nsubset.)\n\n> As this shows we can split apart the \"config_fn_t\", and thus avoid\n> having to change the hundreds of existing \"config_fn_t\" callers. By\n> doing this we can already get rid of the current_config_kvi()\n> function, as \"builtin/remote.c\" and \"t/helper/test-config.c\" were the\n> only users of it.\n\n[snip]\n\n> This leaves the other current_config_*() functions. Subsequent commits\n> will need to deal with those.\n\nI think we already know that splitting config_fn_t is possible but the\nchange will be quite widespread.\n"},{"id":"473678","messageId":"kl6ljzzfdkd6.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"kl6la60buquj.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [RFC PATCH 0/5] bypass config.c global state with configset","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-17T23:32:53Z","receivedAt":"2023-03-17T23:33:00Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n>> This still leave the current_config_name() etc, which\n>> e.g. builtin/config.c still uses. In your series you've needed to add\n>> the new \"reader\" parameter for everything from do_config_from(), but\n>> if we're doing that can't we instead just go straight to passing a\n>> \"struct key_value_info *\" (perhaps with an added \"name\" field) all the\n>> way down, replacing \"cf->linenr\" etc?\n>\n> In the end state, I also think we should be passing \"struct\n> key_value_info *\" around instead of \"cf\", so I think we are seeing\n> \"the_reader\" in the same way (as a transitional state).\n>\n> I considered the \"repo_config_kvi() + config_fn_kvi_t\" as well, but I\n> rejected it (before discussion on the list, whoops) because I didn't\n> want to add _yet another_ set of parallel config APIs, e.g. we already\n> have repo_config(), git_config(), configset*(),\n> git_config_from_file*(). Multiplying that by 2 to add *_kvi() seems like\n> way too much, especially when it seems clear that our current definition\n> of config_fn_t has some problems.\n>\n> Maybe we could deprecate the non-*_kvi(), and have both as a\n> transitional state? It might work, but I think biting the bullet and\n> changing config_fn_t would be easier actually.\n>\n> I'll try applying your series on top of my 1/8 (and maybe 7/8, see next\n> reply) and extending it to cover \"cf\" (instead of just\n> current_config_kvi()) to see whether the *_kvi() approach saves a lot of\n> unnecessary plumbing. I'd still feel very strongly about getting rid of\n> all of the non *_kvi() versions, though, but maybe that would happen in\n> a cleanup topic.\n\nAs mentioned above, I think having both \"config_fn_t\" and\nconfig_kvi_fn_t\" would make sense if we found a good way to extend it to\nthe functions that use \"cf\" (parsing config syntax), not the ones that\nuse \"current_config_kvi\" (iterating a config set).\n\nI think technical difficulty is not a barrier here:\n\n- Constructing a \"struct key_value_info\" out of \"cf\" is trivial\n\n- Supporting both \"config_fn_t\" and \"config_kvi_fn_t\" with one\n  implementation is also doable in theory. One approach would be to use\n  only *_kvi() internally, and then adapt the external \"config_fn_t\"\n  like:\n\n    struct adapt_nonkvi {\n          config_fn_t fn;\n          void *data;\n    };\n\n    static int adapt_nonkvi_fn(const char *key, const char *value,\n                              struct key_value_info *kvi UNUSED, void *cb)\n    {\n          struct adapt_nonkvi *adapt = cb;\n          return adapt->fn(key, value, adapt->data);\n    }\n\nThe real cost is that there are so many functions we'd need to adapt (I\ncounted 12 functions that accept config_fn_t in config.h). I think I got\nthrough about 30% of it before thinking that it was too much work to try\nto avoid adjusting config_fn_t.\n\nI still strongly believe that we shouldn't have both config_fn_t and\nconfig_kvi_fn_t in the long run, and we should converge on one.\nIt's plausible that if we support both as an intermediate state, we'll\nnever do the actual cleanup, so I think the extra cost is not worth it.\n"},{"id":"474337","messageId":"pull.1463.v3.git.git.1680025914.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v2.git.git.1678925506.gitgitgadget@gmail.com","subject":"[PATCH v3 0/8] config.c: use struct for config reading state","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-28T17:51:46Z","receivedAt":"2023-03-28T17:52:05Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Note to Junio: 8/8 (which renames \"cs\" -> \"set\") conflicts with\nab/config-multi-and-nonbool. I previously said that I'd rebase this, but\npresumably a remerge-diff is more ergonomic + flexible (let me know if I'm\nmistaken), so I'll send a remerge-diff in a reply (I don't trust GGG not to\nmangle the patch :/).\n\nAfter sending this out, I'll see if cocci can make it easy enough to change\nconfig_fn_t. If so, I'll probably go with that approach for the future\n'libified' patches, which should also line up nicely with what Ævar\nsuggested.\n\n= Changes in v3\n\n * Adjust the *_push() function to be unconditional (like the *_pop()\n   function)\n\n * Various commit message and comment cleanups\n\n= Changes in v2\n\n * To reduce churn, don't rename \"struct config_source cf\" to \"cs\" early in\n   the series. Instead, rename the global \"cf\" to \"cf_global\", and leave the\n   existing \"cf\"s untouched.\n   \n   Introduce 8/8 to get rid of the confusing acronym \"struct config_source\n   cf\", but I don't mind ejecting it if it's too much churn.\n\n * Adjust 5/8 so to pass \"struct config_reader\" through args instead of\n   \"*data\". v1 made the mistake of thinking \"*data\" was being passed to a\n   callback, but it wasn't.\n\n * Add a 7/8 to fix a bug in die_bad_number(). I included this because it\n   overlaps a little bit with the refactor here, but I don't mind ejecting\n   this either.\n\n * Assorted BUG() message clarifications.\n\n= Description\n\nThis series prepares for config.[ch] to be libified as as part of the\nlibification effort that Emily described in [1]. One of the first goals is\nto read config from a file, but the trouble with how config.c is written\ntoday is that all reading operations rely on global state, so before turning\nthat into a library, we'd want to make that state non-global.\n\nThis series doesn't remove all of the global state, but it gets us closer to\nthat goal by extracting the global config reading state into \"struct\nconfig_reader\" and plumbing it through the config reading machinery. This\nmakes it possible to reuse the config machinery without global state, and to\nenforce some constraints on \"struct config_reader\", which makes it more\npredictable and easier to remove in the long run.\n\nThis process is very similar to how we've plumbed \"struct repository\" and\nother 'context objects' in the past, except:\n\n * The global state (named \"the_reader\") for the git process lives in a\n   config.c static variable, and not on \"the_repository\". See 3/6 for the\n   rationale.\n\n * I've stopped short of adding \"struct config_reader\" to config.h public\n   functions, since that would affect non-config.c callers.\n\nAdditionally, I've included a bugfix for die_bad_number() that became clear\nas I did this refactor.\n\n= Leftover bits\n\nWe still need a global \"the_reader\" because config callbacks are reading\nauxiliary information about the config (e.g. line number, file name) via\nglobal functions (e.g. current_config_line(), current_config_name()). This\nis either because the callback uses this info directly (like\nbuiltin/config.c printing the filename and scope of the value) or for error\nreporting (like git_parse_int() reporting the filename of the value it\nfailed to parse).\n\nIf we had a way to plumb the state from \"struct config_reader\" to the config\ncallback functions, we could initialize \"struct config_reader\" in the config\nmachinery whenever we read config (instead of asking the caller to\ninitialize \"struct config_reader\" themselves), and config reading could\nbecome a thread-safe operation. There isn't an obvious way to plumb this\nstate to config callbacks without adding an additional arg to config_fn_t\nand incurring a lot of churn, but if we start replacing \"config_fn_t\" with\nthe configset API (which we've independently wanted for some time), this may\nbecome feasible.\n\nAnd if we do this, \"struct config_reader\" itself will probably become\nobsolete, because we'd be able to plumb only the relevant state for the\ncurrent operation, e.g. if we are parsing a config file, we'd pass only the\nconfig file parsing state, instead of \"struct config_reader\", which also\ncontains config set iterating state. In such a scenario, we'd probably want\nto pass \"struct key_value_info\" to the config callback, since that's all the\ncallback should be interested in anyway. Interestingly, this was proposed by\nJunio back in [2], and we didn't do this back then out of concern for the\nchurn (just like in v1).\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\nGlen Choo (8):\n  config.c: plumb config_source through static fns\n  config.c: don't assign to \"cf_global\" directly\n  config.c: create config_reader and the_reader\n  config.c: plumb the_reader through callbacks\n  config.c: remove current_config_kvi\n  config.c: remove current_parsing_scope\n  config: report cached filenames in die_bad_number()\n  config.c: rename \"struct config_source cf\"\n\n config.c               | 584 ++++++++++++++++++++++++-----------------\n config.h               |   1 +\n t/helper/test-config.c |  17 ++\n t/t1308-config-set.sh  |   9 +\n 4 files changed, 368 insertions(+), 243 deletions(-)\n\n\nbase-commit: dadc8e6dacb629f46aee39bde90b6f09b73722eb\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1463%2Fchooglen%2Fconfig%2Fstructify-reading-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1463/chooglen/config/structify-reading-v3\nPull-Request: https://github.com/git/git/pull/1463\n\nRange-diff vs v2:\n\n 1:  75d0f0efb79 = 1:  75d0f0efb79 config.c: plumb config_source through static fns\n 2:  7555da0b0e0 ! 2:  39db7d8596a config.c: don't assign to \"cf_global\" directly\n     @@ config.c: static struct key_value_info *current_config_kvi;\n       \n      +static inline void config_reader_push_source(struct config_source *top)\n      +{\n     -+\tif (cf_global)\n     -+\t\ttop->prev = cf_global;\n     ++\ttop->prev = cf_global;\n      +\tcf_global = top;\n      +}\n      +\n 3:  4347896f0a4 ! 3:  72774fd08f3 config.c: create config_reader and the_reader\n     @@ config.c: static struct key_value_info *current_config_kvi;\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 (cf_global)\n     --\t\ttop->prev = cf_global;\n     +-\ttop->prev = cf_global;\n      -\tcf_global = top;\n     -+\tif (reader->source)\n     -+\t\ttop->prev = reader->source;\n     ++\ttop->prev = reader->source;\n      +\treader->source = top;\n      +\t/* FIXME remove this when cf_global is removed. */\n      +\tcf_global = reader->source;\n     @@ config.c: static struct key_value_info *current_config_kvi;\n      -\tcf_global = cf_global->prev;\n      +\tret = reader->source;\n      +\treader->source = reader->source->prev;\n     -+\t/* FIXME remove this when cf is removed. */\n     ++\t/* FIXME remove this when cf_global is removed. */\n      +\tcf_global = reader->source;\n       \treturn ret;\n       }\n 4:  22b69971749 ! 4:  e02dddd560f config.c: plumb the_reader through callbacks\n     @@ config.c: static struct config_reader the_reader;\n       \n       /*\n      @@ config.c: static inline void config_reader_push_source(struct config_reader *reader,\n     - \tif (reader->source)\n     - \t\ttop->prev = reader->source;\n     + {\n     + \ttop->prev = reader->source;\n       \treader->source = top;\n      -\t/* FIXME remove this when cf_global is removed. */\n      -\tcf_global = reader->source;\n     @@ config.c: static inline struct config_source *config_reader_pop_source(struct co\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     --\t/* FIXME remove this when cf is removed. */\n     +-\t/* FIXME remove this when cf_global is removed. */\n      -\tcf_global = reader->source;\n       \treturn ret;\n       }\n 5:  afb6e3e318d ! 5:  c79eaf74f89 config.c: remove current_config_kvi\n     @@ config.c: static enum config_scope current_parsing_scope;\n       {\n      +\tif (reader->config_kvi)\n      +\t\tBUG(\"source should not be set while iterating a config set\");\n     - \tif (reader->source)\n     - \t\ttop->prev = reader->source;\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 6:  a57e35163ae = 6:  05d9ffa21f6 config.c: remove current_parsing_scope\n 7:  3c83d9535a0 ! 7:  eb843e6f08d config: report cached filenames in die_bad_number()\n     @@ Commit message\n      \n          Fix this by refactoring the current_config_* functions into variants\n          that don't BUG() when we aren't reading config, and using the resulting\n     -    functions in die_bad_number(). Refactoring is needed because \"git config\n     -    --get[-regexp] --type=int\" parses the int value _after_ parsing the\n     -    config file, which will run into the BUG().\n     +    functions in die_bad_number(). \"git config --get[-regexp] --type=int\"\n     +    cannot use the non-refactored version because it parses the int value\n     +    _after_ parsing the config file, which would run into the BUG().\n      \n     -    Also, plumb \"struct config_reader\" into the new functions. This isn't\n     -    necessary per se, but this generalizes better, so it might help us avoid\n     -    yet another refactor.\n     +    Since the refactored functions aren't public, they use \"struct\n     +    config_reader\".\n      \n          1. https://lore.kernel.org/git/20160518223712.GA18317@sigill.intra.peff.net/\n      \n 8:  9aec9092fdf = 8:  ab800aa104c config.c: rename \"struct config_source cf\"\n\n-- \ngitgitgadget\n"},{"id":"474338","messageId":"75d0f0efb7924fa6ce2387b1a8e4105d672a991f.1680025914.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v3.git.git.1680025914.gitgitgadget@gmail.com","subject":"[PATCH v3 1/8] config.c: plumb config_source through static fns","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-28T17:51:47Z","receivedAt":"2023-03-28T17:52: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\nThis reduces the direct dependence on the global \"struct config_source\",\nwhich will make it easier to remove in a later commit.\n\nTo minimize the changes we need to make, we rename the current variable\nfrom \"cf\" to \"cf_global\", and the plumbed arg uses the old name \"cf\".\nThis is a little unfortunate, since we now have the confusingly named\n\"struct config_source cf\" everywhere (which is a holdover from before\n4d8dd1494e (config: make parsing stack struct independent from actual\ndata source, 2013-07-12), when the struct used to be called\n\"config_file\"), but we will rename \"cf\" to \"cs\" by the end of the\nseries.\n\nIn some cases (public functions and config callback functions), there\nisn't an obvious way to plumb \"struct config_source\" through function\nargs. As a workaround, add references to \"cf_global\" that we'll address\nin later commits.\n\nThe remaining references to \"cf_global\" are direct assignments to\n\"cf_global\", which we'll also address in a later commit.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 153 ++++++++++++++++++++++++++++++-------------------------\n 1 file changed, 84 insertions(+), 69 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 00090a32fc3..e4a76739365 100644\n--- a/config.c\n+++ b/config.c\n@@ -54,8 +54,8 @@ struct config_source {\n  * These variables record the \"current\" config source, which\n  * can be accessed by parsing callbacks.\n  *\n- * The \"cf\" variable will be non-NULL only when we are actually parsing a real\n- * config source (file, blob, cmdline, etc).\n+ * The \"cf_global\" variable will be non-NULL only when we are actually\n+ * parsing a real config source (file, blob, cmdline, etc).\n  *\n  * The \"current_config_kvi\" variable will be non-NULL only when we are feeding\n  * cached config from a configset into a callback.\n@@ -66,15 +66,16 @@ struct config_source {\n  * or it's a function which can be reused for non-config purposes, and should\n  * fall back to some sane behavior).\n  */\n-static struct config_source *cf;\n+static struct config_source *cf_global;\n static struct key_value_info *current_config_kvi;\n \n /*\n  * Similar to the variables above, this gives access to the \"scope\" of the\n  * current value (repo, global, etc). For cached values, it can be found via\n  * the current_config_kvi as above. During parsing, the current value can be\n- * found in this variable. It's not part of \"cf\" because it transcends a single\n- * file (i.e., a file included from .git/config is still in \"repo\" scope).\n+ * found in this variable. It's not part of \"cf_global\" because it transcends a\n+ * single file (i.e., a file included from .git/config is still in \"repo\"\n+ * scope).\n  */\n static enum config_scope current_parsing_scope;\n \n@@ -156,7 +157,8 @@ 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(const char *path, struct config_include_data *inc)\n+static int handle_path_include(struct config_source *cf, const char *path,\n+\t\t\t       struct config_include_data *inc)\n {\n \tint ret = 0;\n \tstruct strbuf buf = STRBUF_INIT;\n@@ -210,7 +212,8 @@ 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 strbuf *pat)\n+static int prepare_include_condition_pattern(struct config_source *cf,\n+\t\t\t\t\t     struct strbuf *pat)\n {\n \tstruct strbuf path = STRBUF_INIT;\n \tchar *expanded;\n@@ -245,7 +248,8 @@ static int prepare_include_condition_pattern(struct strbuf *pat)\n \treturn prefix;\n }\n \n-static int include_by_gitdir(const struct config_options *opts,\n+static int include_by_gitdir(struct config_source *cf,\n+\t\t\t     const struct config_options *opts,\n \t\t\t     const char *cond, size_t cond_len, int icase)\n {\n \tstruct strbuf text = STRBUF_INIT;\n@@ -261,7 +265,7 @@ static int include_by_gitdir(const struct config_options *opts,\n \n \tstrbuf_realpath(&text, git_dir, 1);\n \tstrbuf_add(&pattern, cond, cond_len);\n-\tprefix = prepare_include_condition_pattern(&pattern);\n+\tprefix = prepare_include_condition_pattern(cf, &pattern);\n \n again:\n \tif (prefix < 0)\n@@ -342,14 +346,14 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tstruct config_source *store_cf = cf;\n+\tstruct config_source *store_cf = cf_global;\n \tstruct key_value_info *store_kvi = current_config_kvi;\n \tenum config_scope store_scope = current_parsing_scope;\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tcf = NULL;\n+\tcf_global = NULL;\n \tcurrent_config_kvi = NULL;\n \tcurrent_parsing_scope = 0;\n \n@@ -357,7 +361,7 @@ static void populate_remote_urls(struct config_include_data *inc)\n \tstring_list_init_dup(inc->remote_urls);\n \tconfig_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);\n \n-\tcf = store_cf;\n+\tcf_global = store_cf;\n \tcurrent_config_kvi = store_kvi;\n \tcurrent_parsing_scope = store_scope;\n }\n@@ -406,15 +410,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_include_data *inc,\n+static int include_condition_is_true(struct config_source *cf,\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(opts, cond, cond_len, 0);\n+\t\treturn include_by_gitdir(cf, 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(opts, cond, cond_len, 1);\n+\t\treturn include_by_gitdir(cf, 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@@ -441,16 +446,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(value, inc);\n+\t\tret = handle_path_include(cf_global, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n-\t    cond && include_condition_is_true(inc, cond, cond_len) &&\n+\t    cond && include_condition_is_true(cf_global, 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(value, inc);\n+\t\tret = handle_path_include(cf_global, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -713,9 +718,9 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tstruct config_source source;\n \n \tmemset(&source, 0, sizeof(source));\n-\tsource.prev = cf;\n+\tsource.prev = cf_global;\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n-\tcf = &source;\n+\tcf_global = &source;\n \n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n@@ -773,11 +778,11 @@ out:\n \tstrbuf_release(&envvar);\n \tstrvec_clear(&to_free);\n \tfree(envw);\n-\tcf = source.prev;\n+\tcf_global = source.prev;\n \treturn ret;\n }\n \n-static int get_next_char(void)\n+static int get_next_char(struct config_source *cf)\n {\n \tint c = cf->do_fgetc(cf);\n \n@@ -813,13 +818,13 @@ static int get_next_char(void)\n \treturn c;\n }\n \n-static char *parse_value(void)\n+static char *parse_value(struct config_source *cf)\n {\n \tint quote = 0, comment = 0, space = 0;\n \n \tstrbuf_reset(&cf->value);\n \tfor (;;) {\n-\t\tint c = get_next_char();\n+\t\tint c = get_next_char(cf);\n \t\tif (c == '\\n') {\n \t\t\tif (quote) {\n \t\t\t\tcf->linenr--;\n@@ -843,7 +848,7 @@ static char *parse_value(void)\n \t\tfor (; space; space--)\n \t\t\tstrbuf_addch(&cf->value, ' ');\n \t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char();\n+\t\t\tc = get_next_char(cf);\n \t\t\tswitch (c) {\n \t\t\tcase '\\n':\n \t\t\t\tcontinue;\n@@ -874,7 +879,8 @@ static char *parse_value(void)\n \t}\n }\n \n-static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n+static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n+\t\t     struct strbuf *name)\n {\n \tint c;\n \tchar *value;\n@@ -882,7 +888,7 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n \n \t/* Get the full name */\n \tfor (;;) {\n-\t\tc = get_next_char();\n+\t\tc = get_next_char(cf);\n \t\tif (cf->eof)\n \t\t\tbreak;\n \t\tif (!iskeychar(c))\n@@ -891,13 +897,13 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n \t}\n \n \twhile (c == ' ' || c == '\\t')\n-\t\tc = get_next_char();\n+\t\tc = get_next_char(cf);\n \n \tvalue = NULL;\n \tif (c != '\\n') {\n \t\tif (c != '=')\n \t\t\treturn -1;\n-\t\tvalue = parse_value();\n+\t\tvalue = parse_value(cf);\n \t\tif (!value)\n \t\t\treturn -1;\n \t}\n@@ -913,13 +919,14 @@ static int get_value(config_fn_t fn, void *data, struct strbuf *name)\n \treturn ret;\n }\n \n-static int get_extended_base_var(struct strbuf *name, int c)\n+static int get_extended_base_var(struct config_source *cf, struct strbuf *name,\n+\t\t\t\t int c)\n {\n \tcf->subsection_case_sensitive = 0;\n \tdo {\n \t\tif (c == '\\n')\n \t\t\tgoto error_incomplete_line;\n-\t\tc = get_next_char();\n+\t\tc = get_next_char(cf);\n \t} while (isspace(c));\n \n \t/* We require the format to be '[base \"extension\"]' */\n@@ -928,13 +935,13 @@ static int get_extended_base_var(struct strbuf *name, int c)\n \tstrbuf_addch(name, '.');\n \n \tfor (;;) {\n-\t\tint c = get_next_char();\n+\t\tint c = get_next_char(cf);\n \t\tif (c == '\\n')\n \t\t\tgoto error_incomplete_line;\n \t\tif (c == '\"')\n \t\t\tbreak;\n \t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char();\n+\t\t\tc = get_next_char(cf);\n \t\t\tif (c == '\\n')\n \t\t\t\tgoto error_incomplete_line;\n \t\t}\n@@ -942,7 +949,7 @@ static int get_extended_base_var(struct strbuf *name, int c)\n \t}\n \n \t/* Final ']' */\n-\tif (get_next_char() != ']')\n+\tif (get_next_char(cf) != ']')\n \t\treturn -1;\n \treturn 0;\n error_incomplete_line:\n@@ -950,17 +957,17 @@ error_incomplete_line:\n \treturn -1;\n }\n \n-static int get_base_var(struct strbuf *name)\n+static int get_base_var(struct config_source *cf, struct strbuf *name)\n {\n \tcf->subsection_case_sensitive = 1;\n \tfor (;;) {\n-\t\tint c = get_next_char();\n+\t\tint c = get_next_char(cf);\n \t\tif (cf->eof)\n \t\t\treturn -1;\n \t\tif (c == ']')\n \t\t\treturn 0;\n \t\tif (isspace(c))\n-\t\t\treturn get_extended_base_var(name, c);\n+\t\t\treturn get_extended_base_var(cf, name, c);\n \t\tif (!iskeychar(c) && c != '.')\n \t\t\treturn -1;\n \t\tstrbuf_addch(name, tolower(c));\n@@ -973,7 +980,8 @@ struct parse_event_data {\n \tconst struct config_options *opts;\n };\n \n-static int do_event(enum config_event_t type, struct parse_event_data *data)\n+static int do_event(struct config_source *cf, enum config_event_t type,\n+\t\t    struct parse_event_data *data)\n {\n \tsize_t offset;\n \n@@ -1004,8 +1012,8 @@ static int do_event(enum config_event_t type, struct parse_event_data *data)\n \treturn 0;\n }\n \n-static int git_parse_source(config_fn_t fn, void *data,\n-\t\t\t    const struct config_options *opts)\n+static int git_parse_source(struct config_source *cf, config_fn_t fn,\n+\t\t\t    void *data, const struct config_options *opts)\n {\n \tint comment = 0;\n \tsize_t baselen = 0;\n@@ -1024,7 +1032,7 @@ static int git_parse_source(config_fn_t fn, void *data,\n \tfor (;;) {\n \t\tint c;\n \n-\t\tc = get_next_char();\n+\t\tc = get_next_char(cf);\n \t\tif (bomptr && *bomptr) {\n \t\t\t/* We are at the file beginning; skip UTF8-encoded BOM\n \t\t\t * if present. Sane editors won't put this in on their\n@@ -1042,11 +1050,11 @@ static int git_parse_source(config_fn_t fn, void *data,\n \t\t}\n \t\tif (c == '\\n') {\n \t\t\tif (cf->eof) {\n-\t\t\t\tif (do_event(CONFIG_EVENT_EOF, &event_data) < 0)\n+\t\t\t\tif (do_event(cf, CONFIG_EVENT_EOF, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\t\treturn 0;\n \t\t\t}\n-\t\t\tif (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n+\t\t\tif (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n \t\t\t\treturn -1;\n \t\t\tcomment = 0;\n \t\t\tcontinue;\n@@ -1054,23 +1062,23 @@ static int git_parse_source(config_fn_t fn, void *data,\n \t\tif (comment)\n \t\t\tcontinue;\n \t\tif (isspace(c)) {\n-\t\t\tif (do_event(CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n+\t\t\tif (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '#' || c == ';') {\n-\t\t\tif (do_event(CONFIG_EVENT_COMMENT, &event_data) < 0)\n+\t\t\tif (do_event(cf, CONFIG_EVENT_COMMENT, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\tcomment = 1;\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '[') {\n-\t\t\tif (do_event(CONFIG_EVENT_SECTION, &event_data) < 0)\n+\t\t\tif (do_event(cf, CONFIG_EVENT_SECTION, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \n \t\t\t/* Reset prior to determining a new stem */\n \t\t\tstrbuf_reset(var);\n-\t\t\tif (get_base_var(var) < 0 || var->len < 1)\n+\t\t\tif (get_base_var(cf, var) < 0 || var->len < 1)\n \t\t\t\tbreak;\n \t\t\tstrbuf_addch(var, '.');\n \t\t\tbaselen = var->len;\n@@ -1079,7 +1087,7 @@ static int git_parse_source(config_fn_t fn, void *data,\n \t\tif (!isalpha(c))\n \t\t\tbreak;\n \n-\t\tif (do_event(CONFIG_EVENT_ENTRY, &event_data) < 0)\n+\t\tif (do_event(cf, CONFIG_EVENT_ENTRY, &event_data) < 0)\n \t\t\treturn -1;\n \n \t\t/*\n@@ -1089,11 +1097,11 @@ static int git_parse_source(config_fn_t fn, void *data,\n \t\t */\n \t\tstrbuf_setlen(var, baselen);\n \t\tstrbuf_addch(var, tolower(c));\n-\t\tif (get_value(fn, data, var) < 0)\n+\t\tif (get_value(cf, fn, data, var) < 0)\n \t\t\tbreak;\n \t}\n \n-\tif (do_event(CONFIG_EVENT_ERROR, &event_data) < 0)\n+\tif (do_event(cf, CONFIG_EVENT_ERROR, &event_data) < 0)\n \t\treturn -1;\n \n \tswitch (cf->origin_type) {\n@@ -1266,7 +1274,8 @@ int git_parse_ssize_t(const char *value, ssize_t *ret)\n }\n \n NORETURN\n-static void die_bad_number(const char *name, const char *value)\n+static void die_bad_number(struct config_source *cf, const char *name,\n+\t\t\t   const char *value)\n {\n \tconst char *error_type = (errno == ERANGE) ?\n \t\tN_(\"out of range\") : N_(\"invalid unit\");\n@@ -1304,7 +1313,7 @@ int git_config_int(const char *name, const char *value)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(name, value);\n+\t\tdie_bad_number(cf_global, name, value);\n \treturn ret;\n }\n \n@@ -1312,7 +1321,7 @@ int64_t git_config_int64(const char *name, const char *value)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(name, value);\n+\t\tdie_bad_number(cf_global, name, value);\n \treturn ret;\n }\n \n@@ -1320,7 +1329,7 @@ unsigned long git_config_ulong(const char *name, const char *value)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(name, value);\n+\t\tdie_bad_number(cf_global, name, value);\n \treturn ret;\n }\n \n@@ -1328,7 +1337,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(name, value);\n+\t\tdie_bad_number(cf_global, name, value);\n \treturn ret;\n }\n \n@@ -1940,20 +1949,20 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,\n \tint ret;\n \n \t/* push config-file parsing state stack */\n-\ttop->prev = cf;\n+\ttop->prev = cf_global;\n \ttop->linenr = 1;\n \ttop->eof = 0;\n \ttop->total_len = 0;\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n-\tcf = top;\n+\tcf_global = top;\n \n-\tret = git_parse_source(fn, data, opts);\n+\tret = git_parse_source(top, fn, data, opts);\n \n \t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n \tstrbuf_release(&top->var);\n-\tcf = top->prev;\n+\tcf_global = top->prev;\n \n \treturn ret;\n }\n@@ -2334,12 +2343,12 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (!cf)\n+\tif (!cf_global)\n \t\tBUG(\"configset_add_value has no source\");\n-\tif (cf->name) {\n-\t\tkv_info->filename = strintern(cf->name);\n-\t\tkv_info->linenr = cf->linenr;\n-\t\tkv_info->origin_type = cf->origin_type;\n+\tif (cf_global->name) {\n+\t\tkv_info->filename = strintern(cf_global->name);\n+\t\tkv_info->linenr = cf_global->linenr;\n+\t\tkv_info->origin_type = cf_global->origin_type;\n \t} else {\n \t\t/* for values read from `git_config_from_parameters()` */\n \t\tkv_info->filename = NULL;\n@@ -2891,6 +2900,12 @@ static int store_aux_event(enum config_event_t type,\n \t\t\t   size_t begin, size_t end, void *data)\n {\n \tstruct config_store_data *store = data;\n+\t/*\n+\t * FIXME Keep using \"cf\" so that we can avoid rewrapping a\n+\t * really long line below. Remove this when \"cf\" gets plumbed\n+\t * correctly.\n+\t */\n+\tstruct config_source *cf = cf_global;\n \n \tALLOC_GROW(store->parsed, store->parsed_nr + 1, store->parsed_alloc);\n \tstore->parsed[store->parsed_nr].begin = begin;\n@@ -3771,8 +3786,8 @@ const char *current_config_origin_type(void)\n \tint type;\n \tif (current_config_kvi)\n \t\ttype = current_config_kvi->origin_type;\n-\telse if(cf)\n-\t\ttype = cf->origin_type;\n+\telse if(cf_global)\n+\t\ttype = cf_global->origin_type;\n \telse\n \t\tBUG(\"current_config_origin_type called outside config callback\");\n \n@@ -3817,8 +3832,8 @@ const char *current_config_name(void)\n \tconst char *name;\n \tif (current_config_kvi)\n \t\tname = current_config_kvi->filename;\n-\telse if (cf)\n-\t\tname = cf->name;\n+\telse if (cf_global)\n+\t\tname = cf_global->name;\n \telse\n \t\tBUG(\"current_config_name called outside config callback\");\n \treturn name ? name : \"\";\n@@ -3837,7 +3852,7 @@ int current_config_line(void)\n \tif (current_config_kvi)\n \t\treturn current_config_kvi->linenr;\n \telse\n-\t\treturn cf->linenr;\n+\t\treturn cf_global->linenr;\n }\n \n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n-- \ngitgitgadget\n\n"},{"id":"474339","messageId":"39db7d8596ad6498d8fe6fe976ab85a5959e9949.1680025914.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v3.git.git.1680025914.gitgitgadget@gmail.com","subject":"[PATCH v3 2/8] config.c: don't assign to \"cf_global\" directly","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-28T17:51:48Z","receivedAt":"2023-03-28T17:52: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\nTo make \"cf_global\" easier to remove, replace all direct assignments to\nit with function calls. This refactor has an additional maintainability\nbenefit: all of these functions were manually implementing stack\npop/push semantics on \"struct config_source\", so replacing them with\nfunction calls allows us to only implement this logic once.\n\nIn this process, perform some now-obvious clean ups:\n\n- Drop some unnecessary \"cf_global\" assignments in\n  populate_remote_urls(). Since it was introduced in 399b198489 (config:\n  include file if remote URL matches a glob, 2022-01-18), it has stored\n  and restored the value of \"cf_global\" to ensure that it doesn't get\n  accidentally mutated. However, this was never necessary since\n  \"do_config_from()\" already pushes/pops \"cf_global\" further down the\n  call chain.\n\n- Zero out every \"struct config_source\" with a dedicated initializer.\n  This matters because the \"struct config_source\" is assigned to\n  \"cf_global\" and we later 'pop the stack' by assigning \"cf_global =\n  cf_global->prev\", but \"cf_global->prev\" could be pointing to\n  uninitialized garbage.\n\n  Fortunately, this has never bothered us since we never try to read\n  \"cf_global\" except while iterating through config, in which case,\n  \"cf_global\" is either set to a sensible value (when parsing a file),\n  or it is ignored (when iterating a configset). Later in the series,\n  zero-ing out memory will also let us enforce the constraint that\n  \"cf_global\" and \"current_config_kvi\" are never non-NULL together.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 37 ++++++++++++++++++++++++-------------\n 1 file changed, 24 insertions(+), 13 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex e4a76739365..6627fad71cf 100644\n--- a/config.c\n+++ b/config.c\n@@ -49,6 +49,7 @@ struct config_source {\n \tint (*do_ungetc)(int c, struct config_source *conf);\n \tlong (*do_ftell)(struct config_source *c);\n };\n+#define CONFIG_SOURCE_INIT { 0 }\n \n /*\n  * These variables record the \"current\" config source, which\n@@ -79,6 +80,22 @@ static struct key_value_info *current_config_kvi;\n  */\n static enum config_scope current_parsing_scope;\n \n+static inline void config_reader_push_source(struct config_source *top)\n+{\n+\ttop->prev = cf_global;\n+\tcf_global = top;\n+}\n+\n+static inline struct config_source *config_reader_pop_source()\n+{\n+\tstruct config_source *ret;\n+\tif (!cf_global)\n+\t\tBUG(\"tried to pop config source, but we weren't reading config\");\n+\tret = cf_global;\n+\tcf_global = cf_global->prev;\n+\treturn ret;\n+}\n+\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -346,14 +363,12 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tstruct config_source *store_cf = cf_global;\n \tstruct key_value_info *store_kvi = current_config_kvi;\n \tenum config_scope store_scope = current_parsing_scope;\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tcf_global = NULL;\n \tcurrent_config_kvi = NULL;\n \tcurrent_parsing_scope = 0;\n \n@@ -361,7 +376,6 @@ static void populate_remote_urls(struct config_include_data *inc)\n \tstring_list_init_dup(inc->remote_urls);\n \tconfig_with_options(add_remote_url, inc->remote_urls, inc->config_source, &opts);\n \n-\tcf_global = store_cf;\n \tcurrent_config_kvi = store_kvi;\n \tcurrent_parsing_scope = store_scope;\n }\n@@ -715,12 +729,10 @@ 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;\n+\tstruct config_source source = CONFIG_SOURCE_INIT;\n \n-\tmemset(&source, 0, sizeof(source));\n-\tsource.prev = cf_global;\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n-\tcf_global = &source;\n+\tconfig_reader_push_source(&source);\n \n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n@@ -778,7 +790,7 @@ out:\n \tstrbuf_release(&envvar);\n \tstrvec_clear(&to_free);\n \tfree(envw);\n-\tcf_global = source.prev;\n+\tconfig_reader_pop_source();\n \treturn ret;\n }\n \n@@ -1949,20 +1961,19 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,\n \tint ret;\n \n \t/* push config-file parsing state stack */\n-\ttop->prev = cf_global;\n \ttop->linenr = 1;\n \ttop->eof = 0;\n \ttop->total_len = 0;\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n-\tcf_global = top;\n+\tconfig_reader_push_source(top);\n \n \tret = git_parse_source(top, fn, data, opts);\n \n \t/* pop config-file parsing state stack */\n \tstrbuf_release(&top->value);\n \tstrbuf_release(&top->var);\n-\tcf_global = top->prev;\n+\tconfig_reader_pop_source();\n \n \treturn ret;\n }\n@@ -1972,7 +1983,7 @@ static int do_config_from_file(config_fn_t fn,\n \t\tconst char *name, const char *path, FILE *f,\n \t\tvoid *data, const struct config_options *opts)\n {\n-\tstruct config_source top;\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n \n \ttop.u.file = f;\n@@ -2024,7 +2035,7 @@ int git_config_from_mem(config_fn_t fn,\n \t\t\tconst char *name, const char *buf, size_t len,\n \t\t\tvoid *data, const struct config_options *opts)\n {\n-\tstruct config_source top;\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n \n \ttop.u.buf.buf = buf;\n \ttop.u.buf.len = len;\n-- \ngitgitgadget\n\n"},{"id":"474340","messageId":"72774fd08f3eb9ff1d449814637e584692ba2bfc.1680025914.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v3.git.git.1680025914.gitgitgadget@gmail.com","subject":"[PATCH v3 3/8] config.c: create config_reader and the_reader","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-28T17:51:49Z","receivedAt":"2023-03-28T17:52: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\nCreate \"struct config_reader\" to hold the state of the config source\ncurrently being read. Then, create a static instance of it,\n\"the_reader\", and use \"the_reader.source\" to replace references to\n\"cf_global\" in public functions.\n\nThis doesn't create much immediate benefit (since we're mostly replacing\nstatic variables with a bigger static variable), but it prepares us for\na future where this state doesn't have to be global; \"struct\nconfig_reader\" (or a similar struct) could be provided by the caller, or\nconstructed internally by a function like \"do_config_from()\".\n\nA more typical approach would be to put this struct on \"the_repository\",\nbut that's a worse fit for this use case since config reading is not\nscoped to a repository. E.g. we can read config before the repository is\nknown (\"read_very_early_config()\"), blatantly ignore the repo\n(\"read_protected_config()\"), or read only from a file\n(\"git_config_from_file()\"). This is especially evident in t5318 and\nt9210, where test-tool and scalar parse config but don't fully\ninitialize \"the_repository\".\n\nWe could have also replaced the references to \"cf_global\" in callback\nfunctions (which are the only ones left), but we'll eventually plumb\n\"the_reader\" through the callback \"*data\" arg, so that would be\nunnecessary churn. Until we remove \"cf_global\" altogether, add logic to\n\"config_reader_*_source()\" to keep \"cf_global\" and \"the_reader.source\"\nin sync.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 82 +++++++++++++++++++++++++++++++++++---------------------\n 1 file changed, 51 insertions(+), 31 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 6627fad71cf..3a28b397c4d 100644\n--- a/config.c\n+++ b/config.c\n@@ -51,6 +51,16 @@ struct config_source {\n };\n #define CONFIG_SOURCE_INIT { 0 }\n \n+struct config_reader {\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 /*\n  * These variables record the \"current\" config source, which\n  * can be accessed by parsing callbacks.\n@@ -66,6 +76,9 @@ struct config_source {\n  * at the variables, it's either a bug for it to be called in the first place,\n  * or it's a function which can be reused for non-config purposes, and should\n  * fall back to some sane behavior).\n+ *\n+ * FIXME \"cf_global\" has been replaced by \"the_reader.source\", remove\n+ * \"cf_global\" once we plumb \"the_reader\" through all of the callback functions.\n  */\n static struct config_source *cf_global;\n static struct key_value_info *current_config_kvi;\n@@ -80,19 +93,24 @@ static struct key_value_info *current_config_kvi;\n  */\n static enum config_scope current_parsing_scope;\n \n-static inline void config_reader_push_source(struct config_source *top)\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 = cf_global;\n-\tcf_global = top;\n+\ttop->prev = reader->source;\n+\treader->source = top;\n+\t/* FIXME remove this when cf_global is removed. */\n+\tcf_global = reader->source;\n }\n \n-static inline struct config_source *config_reader_pop_source()\n+static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n {\n \tstruct config_source *ret;\n-\tif (!cf_global)\n+\tif (!reader->source)\n \t\tBUG(\"tried to pop config source, but we weren't reading config\");\n-\tret = cf_global;\n-\tcf_global = cf_global->prev;\n+\tret = reader->source;\n+\treader->source = reader->source->prev;\n+\t/* FIXME remove this when cf_global is removed. */\n+\tcf_global = reader->source;\n \treturn ret;\n }\n \n@@ -732,7 +750,7 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tstruct config_source source = CONFIG_SOURCE_INIT;\n \n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n-\tconfig_reader_push_source(&source);\n+\tconfig_reader_push_source(&the_reader, &source);\n \n \tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n \tif (env) {\n@@ -790,7 +808,7 @@ out:\n \tstrbuf_release(&envvar);\n \tstrvec_clear(&to_free);\n \tfree(envw);\n-\tconfig_reader_pop_source();\n+\tconfig_reader_pop_source(&the_reader);\n \treturn ret;\n }\n \n@@ -1325,7 +1343,7 @@ int git_config_int(const char *name, const char *value)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(cf_global, name, value);\n+\t\tdie_bad_number(the_reader.source, name, value);\n \treturn ret;\n }\n \n@@ -1333,7 +1351,7 @@ int64_t git_config_int64(const char *name, const char *value)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(cf_global, name, value);\n+\t\tdie_bad_number(the_reader.source, name, value);\n \treturn ret;\n }\n \n@@ -1341,7 +1359,7 @@ unsigned long git_config_ulong(const char *name, const char *value)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(cf_global, name, value);\n+\t\tdie_bad_number(the_reader.source, name, value);\n \treturn ret;\n }\n \n@@ -1349,7 +1367,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(cf_global, name, value);\n+\t\tdie_bad_number(the_reader.source, name, value);\n \treturn ret;\n }\n \n@@ -1955,7 +1973,8 @@ 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_source *top, config_fn_t fn, void *data,\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  const struct config_options *opts)\n {\n \tint ret;\n@@ -1966,22 +1985,23 @@ static int do_config_from(struct config_source *top, config_fn_t fn, void *data,\n \ttop->total_len = 0;\n \tstrbuf_init(&top->value, 1024);\n \tstrbuf_init(&top->var, 1024);\n-\tconfig_reader_push_source(top);\n+\tconfig_reader_push_source(reader, top);\n \n \tret = git_parse_source(top, fn, 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();\n+\tconfig_reader_pop_source(reader);\n \n \treturn ret;\n }\n \n-static int do_config_from_file(config_fn_t fn,\n-\t\tconst enum config_origin_type origin_type,\n-\t\tconst char *name, const char *path, FILE *f,\n-\t\tvoid *data, const struct config_options *opts)\n+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 {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n@@ -1996,15 +2016,15 @@ static int do_config_from_file(config_fn_t fn,\n \ttop.do_ftell = config_file_ftell;\n \n \tflockfile(f);\n-\tret = do_config_from(&top, fn, data, opts);\n+\tret = do_config_from(reader, &top, fn, data, opts);\n \tfunlockfile(f);\n \treturn ret;\n }\n \n static int git_config_from_stdin(config_fn_t fn, void *data)\n {\n-\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n-\t\t\t\t   data, NULL);\n+\treturn do_config_from_file(&the_reader, fn, CONFIG_ORIGIN_STDIN, \"\",\n+\t\t\t\t   NULL, stdin, data, NULL);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2018,8 +2038,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(fn, CONFIG_ORIGIN_FILE, filename,\n-\t\t\t\t\t  filename, f, data, opts);\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\tfclose(f);\n \t}\n \treturn ret;\n@@ -2048,7 +2068,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(&top, fn, data, opts);\n+\treturn do_config_from(&the_reader, &top, fn, data, opts);\n }\n \n int git_config_from_blob_oid(config_fn_t fn,\n@@ -3797,8 +3817,8 @@ const char *current_config_origin_type(void)\n \tint type;\n \tif (current_config_kvi)\n \t\ttype = current_config_kvi->origin_type;\n-\telse if(cf_global)\n-\t\ttype = cf_global->origin_type;\n+\telse if(the_reader.source)\n+\t\ttype = the_reader.source->origin_type;\n \telse\n \t\tBUG(\"current_config_origin_type called outside config callback\");\n \n@@ -3843,8 +3863,8 @@ const char *current_config_name(void)\n \tconst char *name;\n \tif (current_config_kvi)\n \t\tname = current_config_kvi->filename;\n-\telse if (cf_global)\n-\t\tname = cf_global->name;\n+\telse if (the_reader.source)\n+\t\tname = the_reader.source->name;\n \telse\n \t\tBUG(\"current_config_name called outside config callback\");\n \treturn name ? name : \"\";\n@@ -3863,7 +3883,7 @@ int current_config_line(void)\n \tif (current_config_kvi)\n \t\treturn current_config_kvi->linenr;\n \telse\n-\t\treturn cf_global->linenr;\n+\t\treturn the_reader.source->linenr;\n }\n \n int lookup_config(const char **mapping, int nr_mapping, const char *var)\n-- \ngitgitgadget\n\n"},{"id":"474341","messageId":"e02dddd560f68a6f1abe38bb9161de783ca04be2.1680025914.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v3.git.git.1680025914.gitgitgadget@gmail.com","subject":"[PATCH v3 4/8] config.c: plumb the_reader through callbacks","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-28T17:51:50Z","receivedAt":"2023-03-28T17:52: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\nThe remaining references to \"cf_global\" are in config callback\nfunctions. Remove them by plumbing \"struct config_reader\" via the\n\"*data\" arg.\n\nIn both of the callbacks here, we are only reading from\n\"reader->source\". So in the long run, if we had a way to expose readonly\ninformation from \"reader->source\" (probably in the form of \"struct\nkey_value_info\"), we could undo this patch (i.e. remove \"struct\nconfig_reader\" fom \"*data\").\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 74 ++++++++++++++++++++++++++++++++------------------------\n 1 file changed, 43 insertions(+), 31 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 3a28b397c4d..cb6ff134f5f 100644\n--- a/config.c\n+++ b/config.c\n@@ -62,6 +62,9 @@ struct config_reader {\n static struct config_reader the_reader;\n \n /*\n+ * FIXME The comments are temporarily out of date since \"cf_global\" has been\n+ * moved to the_reader, but not current_*.\n+ *\n  * These variables record the \"current\" config source, which\n  * can be accessed by parsing callbacks.\n  *\n@@ -76,11 +79,7 @@ static struct config_reader the_reader;\n  * at the variables, it's either a bug for it to be called in the first place,\n  * or it's a function which can be reused for non-config purposes, and should\n  * fall back to some sane behavior).\n- *\n- * FIXME \"cf_global\" has been replaced by \"the_reader.source\", remove\n- * \"cf_global\" once we plumb \"the_reader\" through all of the callback functions.\n  */\n-static struct config_source *cf_global;\n static struct key_value_info *current_config_kvi;\n \n /*\n@@ -98,8 +97,6 @@ static inline void config_reader_push_source(struct config_reader *reader,\n {\n \ttop->prev = reader->source;\n \treader->source = top;\n-\t/* FIXME remove this when cf_global is removed. */\n-\tcf_global = reader->source;\n }\n \n static inline struct config_source *config_reader_pop_source(struct config_reader *reader)\n@@ -109,8 +106,6 @@ static inline struct config_source *config_reader_pop_source(struct config_reade\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-\t/* FIXME remove this when cf_global is removed. */\n-\tcf_global = reader->source;\n \treturn ret;\n }\n \n@@ -175,6 +170,7 @@ 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@@ -465,6 +461,7 @@ static int include_condition_is_true(struct config_source *cf,\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 *cf = inc->config_reader->source;\n \tconst char *cond, *key;\n \tsize_t cond_len;\n \tint ret;\n@@ -478,16 +475,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(cf_global, value, inc);\n+\t\tret = handle_path_include(cf, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n-\t    cond && include_condition_is_true(cf_global, inc, cond, cond_len) &&\n+\t    cond && include_condition_is_true(cf, 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(cf_global, value, inc);\n+\t\tret = handle_path_include(cf, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -2228,6 +2225,7 @@ 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@@ -2348,7 +2346,9 @@ static struct config_set_element *configset_find_element(struct config_set *cs,\n \treturn found_entry;\n }\n \n-static int configset_add_value(struct config_set *cs, const char *key, const char *value)\n+static int configset_add_value(struct config_reader *reader,\n+\t\t\t       struct config_set *cs, const char *key,\n+\t\t\t       const char *value)\n {\n \tstruct config_set_element *e;\n \tstruct string_list_item *si;\n@@ -2374,12 +2374,12 @@ static int configset_add_value(struct config_set *cs, const char *key, const cha\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n-\tif (!cf_global)\n+\tif (!reader->source)\n \t\tBUG(\"configset_add_value has no source\");\n-\tif (cf_global->name) {\n-\t\tkv_info->filename = strintern(cf_global->name);\n-\t\tkv_info->linenr = cf_global->linenr;\n-\t\tkv_info->origin_type = cf_global->origin_type;\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} else {\n \t\t/* for values read from `git_config_from_parameters()` */\n \t\tkv_info->filename = NULL;\n@@ -2434,16 +2434,25 @@ void git_configset_clear(struct config_set *cs)\n \tcs->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, void *cb)\n {\n-\tstruct config_set *cs = cb;\n-\tconfigset_add_value(cs, key, value);\n+\tstruct configset_add_data *data = cb;\n+\tconfigset_add_value(data->config_reader, data->config_set, key, value);\n \treturn 0;\n }\n \n int git_configset_add_file(struct config_set *cs, const char *filename)\n {\n-\treturn git_config_from_file(config_set_callback, filename, cs);\n+\tstruct configset_add_data data = CONFIGSET_ADD_INIT;\n+\tdata.config_reader = &the_reader;\n+\tdata.config_set = cs;\n+\treturn git_config_from_file(config_set_callback, filename, &data);\n }\n \n int git_configset_get_value(struct config_set *cs, const char *key, const char **value)\n@@ -2558,6 +2567,7 @@ int git_configset_get_pathname(struct config_set *cs, const char *key, const cha\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@@ -2569,8 +2579,10 @@ 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, repo->config, NULL, &opts) < 0)\n+\tif (config_with_options(config_set_callback, &data, 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@@ -2696,9 +2708,12 @@ 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-\tconfig_with_options(config_set_callback, &protected_config,\n-\t\t\t    NULL, &opts);\n+\tdata.config_set = &protected_config;\n+\tdata.config_reader = &the_reader;\n+\tconfig_with_options(config_set_callback, &data, NULL, &opts);\n }\n \n void git_protected_config(config_fn_t fn, void *data)\n@@ -2883,6 +2898,7 @@ 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@@ -2897,6 +2913,7 @@ struct config_store_data {\n \tunsigned int parsed_nr, parsed_alloc, *seen, seen_nr, seen_alloc;\n \tunsigned int key_seen:1, section_seen:1, is_keys_section:1;\n };\n+#define CONFIG_STORE_INIT { 0 }\n \n static void config_store_data_clear(struct config_store_data *store)\n {\n@@ -2931,12 +2948,7 @@ static int store_aux_event(enum config_event_t type,\n \t\t\t   size_t begin, size_t end, void *data)\n {\n \tstruct config_store_data *store = data;\n-\t/*\n-\t * FIXME Keep using \"cf\" so that we can avoid rewrapping a\n-\t * really long line below. Remove this when \"cf\" gets plumbed\n-\t * correctly.\n-\t */\n-\tstruct config_source *cf = cf_global;\n+\tstruct config_source *cf = 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@@ -3254,9 +3266,9 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \tchar *filename_buf = NULL;\n \tchar *contents = NULL;\n \tsize_t contents_sz;\n-\tstruct config_store_data store;\n+\tstruct config_store_data store = CONFIG_STORE_INIT;\n \n-\tmemset(&store, 0, sizeof(store));\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-- \ngitgitgadget\n\n"},{"id":"474342","messageId":"c79eaf74f89803dbc117a80ef6da65898aaa23e5.1680025914.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v3.git.git.1680025914.gitgitgadget@gmail.com","subject":"[PATCH v3 5/8] config.c: remove current_config_kvi","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-28T17:51:51Z","receivedAt":"2023-03-28T17:52: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\nAdd \".config_kvi\" to \"struct config_reader\" and replace\n\"current_config_kvi\" with \"the_reader.config_kvi\", plumbing \"struct\nconfig_reader\" where necesssary.\n\nAlso, introduce a setter function for \".config_kvi\", which allows us to\nenforce the contraint that only one of \".source\" and \".config_kvi\" can\nbe set at a time (as documented in the comments). Because of this\nconstraint, we know that \"populate_remote_urls()\" was never touching\n\"current_config_kvi\" when iterating through config files, so it doesn't\nneed to store and restore that value.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 82 +++++++++++++++++++++++++++++---------------------------\n 1 file changed, 43 insertions(+), 39 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex cb6ff134f5f..71ee36f069b 100644\n--- a/config.c\n+++ b/config.c\n@@ -52,7 +52,24 @@ 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  * Where possible, prefer to accept \"struct config_reader\" as an arg than to use\n@@ -61,27 +78,6 @@ struct config_reader {\n  */\n static struct config_reader the_reader;\n \n-/*\n- * FIXME The comments are temporarily out of date since \"cf_global\" has been\n- * moved to the_reader, but not current_*.\n- *\n- * These variables record the \"current\" config source, which\n- * can be accessed by parsing callbacks.\n- *\n- * The \"cf_global\" variable will be non-NULL only when we are actually\n- * parsing a real config source (file, blob, cmdline, etc).\n- *\n- * The \"current_config_kvi\" variable will be non-NULL only when we are feeding\n- * cached config from a configset into a callback.\n- *\n- * They should generally never be non-NULL at the same time. If they are both\n- * NULL, then we aren't parsing anything (and depending on the function looking\n- * at the variables, it's either a bug for it to be called in the first place,\n- * or it's a function which can be reused for non-config purposes, and should\n- * fall back to some sane behavior).\n- */\n-static struct key_value_info *current_config_kvi;\n-\n /*\n  * Similar to the variables above, this gives access to the \"scope\" of the\n  * current value (repo, global, etc). For cached values, it can be found via\n@@ -95,6 +91,8 @@ static enum config_scope current_parsing_scope;\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@@ -109,6 +107,14 @@ 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+\tif (kvi && reader->source)\n+\t\tBUG(\"kvi should not be set while parsing a config source\");\n+\treader->config_kvi = kvi;\n+}\n+\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -377,20 +383,17 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tstruct key_value_info *store_kvi = current_config_kvi;\n \tenum config_scope store_scope = current_parsing_scope;\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tcurrent_config_kvi = NULL;\n \tcurrent_parsing_scope = 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-\tcurrent_config_kvi = store_kvi;\n \tcurrent_parsing_scope = store_scope;\n }\n \n@@ -2257,7 +2260,8 @@ int config_with_options(config_fn_t fn, void *data,\n \treturn ret;\n }\n \n-static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n+static void configset_iter(struct config_reader *reader, struct config_set *cs,\n+\t\t\t   config_fn_t fn, void *data)\n {\n \tint i, value_index;\n \tstruct string_list *values;\n@@ -2269,14 +2273,14 @@ static void configset_iter(struct config_set *cs, config_fn_t fn, void *data)\n \t\tvalue_index = list->items[i].value_index;\n \t\tvalues = &entry->value_list;\n \n-\t\tcurrent_config_kvi = values->items[value_index].util;\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      current_config_kvi->filename,\n-\t\t\t\t\t      current_config_kvi->linenr);\n+\t\t\t\t\t      reader->config_kvi->filename,\n+\t\t\t\t\t      reader->config_kvi->linenr);\n \n-\t\tcurrent_config_kvi = NULL;\n+\t\tconfig_reader_set_kvi(reader, NULL);\n \t}\n }\n \n@@ -2614,7 +2618,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(repo->config, fn, data);\n+\tconfigset_iter(&the_reader, repo->config, fn, data);\n }\n \n int repo_config_get_value(struct repository *repo,\n@@ -2720,7 +2724,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(&protected_config, fn, data);\n+\tconfigset_iter(&the_reader, &protected_config, fn, data);\n }\n \n /* Functions used historically to read configuration from 'the_repository' */\n@@ -3827,8 +3831,8 @@ int parse_config_key(const char *var,\n const char *current_config_origin_type(void)\n {\n \tint type;\n-\tif (current_config_kvi)\n-\t\ttype = current_config_kvi->origin_type;\n+\tif (the_reader.config_kvi)\n+\t\ttype = the_reader.config_kvi->origin_type;\n \telse if(the_reader.source)\n \t\ttype = the_reader.source->origin_type;\n \telse\n@@ -3873,8 +3877,8 @@ const char *config_scope_name(enum config_scope scope)\n const char *current_config_name(void)\n {\n \tconst char *name;\n-\tif (current_config_kvi)\n-\t\tname = current_config_kvi->filename;\n+\tif (the_reader.config_kvi)\n+\t\tname = the_reader.config_kvi->filename;\n \telse if (the_reader.source)\n \t\tname = the_reader.source->name;\n \telse\n@@ -3884,16 +3888,16 @@ const char *current_config_name(void)\n \n enum config_scope current_config_scope(void)\n {\n-\tif (current_config_kvi)\n-\t\treturn current_config_kvi->scope;\n+\tif (the_reader.config_kvi)\n+\t\treturn the_reader.config_kvi->scope;\n \telse\n \t\treturn current_parsing_scope;\n }\n \n int current_config_line(void)\n {\n-\tif (current_config_kvi)\n-\t\treturn current_config_kvi->linenr;\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-- \ngitgitgadget\n\n"},{"id":"474343","messageId":"05d9ffa21f6440a7a2876a704d777cc82a121b6b.1680025914.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v3.git.git.1680025914.gitgitgadget@gmail.com","subject":"[PATCH v3 6/8] config.c: remove current_parsing_scope","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-28T17:51:52Z","receivedAt":"2023-03-28T17:52: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\nAdd \".parsing_scope\" to \"struct config_reader\" and replace\n\"current_parsing_scope\" with \"the_reader.parsing_scope. Adjust the\ncomment slightly to make it clearer that the scope applies to the config\nsource (not the current value), and should only be set when parsing a\nconfig source.\n\nAs such, \".parsing_scope\" (only set when parsing config sources) and\n\".config_kvi\" (only set when iterating a config set) should not be\nset together, so enforce this with a setter function.\n\nUnlike previous commits, \"populate_remote_urls()\" still needs to store\nand restore the 'scope' value because it could have touched\n\"current_parsing_scope\" (\"config_with_options()\" can set the scope).\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 63 +++++++++++++++++++++++++++++++++-----------------------\n 1 file changed, 37 insertions(+), 26 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 71ee36f069b..3756322ec96 100644\n--- a/config.c\n+++ b/config.c\n@@ -70,6 +70,16 @@ 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@@ -78,16 +88,6 @@ struct config_reader {\n  */\n static struct config_reader the_reader;\n \n-/*\n- * Similar to the variables above, this gives access to the \"scope\" of the\n- * current value (repo, global, etc). For cached values, it can be found via\n- * the current_config_kvi as above. During parsing, the current value can be\n- * found in this variable. It's not part of \"cf_global\" because it transcends a\n- * single file (i.e., a file included from .git/config is still in \"repo\"\n- * scope).\n- */\n-static enum config_scope current_parsing_scope;\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@@ -110,11 +110,19 @@ 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)\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@@ -383,18 +391,18 @@ static void populate_remote_urls(struct config_include_data *inc)\n {\n \tstruct config_options opts;\n \n-\tenum config_scope store_scope = current_parsing_scope;\n+\tenum config_scope store_scope = inc->config_reader->parsing_scope;\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n \n-\tcurrent_parsing_scope = 0;\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-\tcurrent_parsing_scope = store_scope;\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@@ -2159,7 +2167,8 @@ int git_config_system(void)\n \treturn !git_env_bool(\"GIT_CONFIG_NOSYSTEM\", 0);\n }\n \n-static int do_git_config_sequence(const struct config_options *opts,\n+static int do_git_config_sequence(struct config_reader *reader,\n+\t\t\t\t  const struct config_options *opts,\n \t\t\t\t  config_fn_t fn, void *data)\n {\n \tint ret = 0;\n@@ -2167,7 +2176,7 @@ static int do_git_config_sequence(const struct config_options *opts,\n \tchar *xdg_config = NULL;\n \tchar *user_config = NULL;\n \tchar *repo_config;\n-\tenum config_scope prev_parsing_scope = current_parsing_scope;\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@@ -2176,13 +2185,13 @@ static int do_git_config_sequence(const struct config_options *opts,\n \telse\n \t\trepo_config = NULL;\n \n-\tcurrent_parsing_scope = CONFIG_SCOPE_SYSTEM;\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 \n-\tcurrent_parsing_scope = 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@@ -2191,12 +2200,12 @@ static int do_git_config_sequence(const struct config_options *opts,\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 \n-\tcurrent_parsing_scope = 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 \n-\tcurrent_parsing_scope = 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@@ -2204,11 +2213,11 @@ static int do_git_config_sequence(const struct config_options *opts,\n \t\tfree(path);\n \t}\n \n-\tcurrent_parsing_scope = CONFIG_SCOPE_COMMAND;\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-\tcurrent_parsing_scope = prev_parsing_scope;\n+\tconfig_reader_set_scope(reader, prev_parsing_scope);\n \tfree(system_config);\n \tfree(xdg_config);\n \tfree(user_config);\n@@ -2221,6 +2230,7 @@ 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@@ -2234,7 +2244,7 @@ int config_with_options(config_fn_t fn, void *data,\n \t}\n \n \tif (config_source)\n-\t\tcurrent_parsing_scope = config_source->scope;\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@@ -2250,13 +2260,14 @@ 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\tdata);\n \t} else {\n-\t\tret = do_git_config_sequence(opts, fn, data);\n+\t\tret = do_git_config_sequence(&the_reader, 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@@ -2390,7 +2401,7 @@ static int configset_add_value(struct config_reader *reader,\n \t\tkv_info->linenr = -1;\n \t\tkv_info->origin_type = CONFIG_ORIGIN_CMDLINE;\n \t}\n-\tkv_info->scope = current_parsing_scope;\n+\tkv_info->scope = reader->parsing_scope;\n \tsi->util = kv_info;\n \n \treturn 0;\n@@ -3891,7 +3902,7 @@ 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 current_parsing_scope;\n+\t\treturn the_reader.parsing_scope;\n }\n \n int current_config_line(void)\n-- \ngitgitgadget\n\n"},{"id":"474344","messageId":"eb843e6f08dcc143b71378750cca5efe553a377d.1680025914.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v3.git.git.1680025914.gitgitgadget@gmail.com","subject":"[PATCH v3 7/8] config: report cached filenames in die_bad_number()","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-28T17:51:53Z","receivedAt":"2023-03-28T17:52: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\nIf, when parsing numbers from config, die_bad_number() is called, it\nreports the filename and config source type if we were parsing a config\nfile, but not if we were iterating a config_set (it defaults to a less\nspecific error message). Most call sites don't parse config files\nbecause config is typically read once and cached, so we only report\nfilename and config source type in \"git config --type\" (since \"git\nconfig\" always parses config files).\n\nThis could have been fixed when we taught the current_config_*\nfunctions to respect config_set values (0d44a2dacc (config: return\nconfigset value for current_config_ functions, 2016-05-26), but it was\nhard to spot then and we might have just missed it (I didn't find\nmention of die_bad_number() in the original ML discussion [1].)\n\nFix this by refactoring the current_config_* functions into variants\nthat don't BUG() when we aren't reading config, and using the resulting\nfunctions in die_bad_number(). \"git config --get[-regexp] --type=int\"\ncannot use the non-refactored version because it parses the int value\n_after_ parsing the config file, which would run into the BUG().\n\nSince the refactored functions aren't public, they use \"struct\nconfig_reader\".\n\n1. https://lore.kernel.org/git/20160518223712.GA18317@sigill.intra.peff.net/\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c               | 65 +++++++++++++++++++++++++++++-------------\n config.h               |  1 +\n t/helper/test-config.c | 17 +++++++++++\n t/t1308-config-set.sh  |  9 ++++++\n 4 files changed, 72 insertions(+), 20 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 3756322ec96..46c5a9a7d51 100644\n--- a/config.c\n+++ b/config.c\n@@ -1311,39 +1311,48 @@ 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_source *cf, const char *name,\n+static void die_bad_number(struct config_reader *reader, const char *name,\n \t\t\t   const char *value)\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-\tif (!(cf && cf->name))\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 \t\tdie(_(bad_numeric), value, name, _(error_type));\n \n-\tswitch (cf->origin_type) {\n+\tswitch (config_origin) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in blob %s: %s\"),\n-\t\t    value, name, cf->name, _(error_type));\n+\t\t    value, name, config_name, _(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, cf->name, _(error_type));\n+\t\t    value, name, config_name, _(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, cf->name, _(error_type));\n+\t\t    value, name, config_name, _(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, cf->name, _(error_type));\n+\t\t    value, name, config_name, _(error_type));\n \tdefault:\n \t\tdie(_(\"bad numeric config value '%s' for '%s' in %s: %s\"),\n-\t\t    value, name, cf->name, _(error_type));\n+\t\t    value, name, config_name, _(error_type));\n \t}\n }\n \n@@ -1351,7 +1360,7 @@ int git_config_int(const char *name, const char *value)\n {\n \tint ret;\n \tif (!git_parse_int(value, &ret))\n-\t\tdie_bad_number(the_reader.source, name, value);\n+\t\tdie_bad_number(&the_reader, name, value);\n \treturn ret;\n }\n \n@@ -1359,7 +1368,7 @@ int64_t git_config_int64(const char *name, const char *value)\n {\n \tint64_t ret;\n \tif (!git_parse_int64(value, &ret))\n-\t\tdie_bad_number(the_reader.source, name, value);\n+\t\tdie_bad_number(&the_reader, name, value);\n \treturn ret;\n }\n \n@@ -1367,7 +1376,7 @@ unsigned long git_config_ulong(const char *name, const char *value)\n {\n \tunsigned long ret;\n \tif (!git_parse_ulong(value, &ret))\n-\t\tdie_bad_number(the_reader.source, name, value);\n+\t\tdie_bad_number(&the_reader, name, value);\n \treturn ret;\n }\n \n@@ -1375,7 +1384,7 @@ ssize_t git_config_ssize_t(const char *name, const char *value)\n {\n \tssize_t ret;\n \tif (!git_parse_ssize_t(value, &ret))\n-\t\tdie_bad_number(the_reader.source, name, value);\n+\t\tdie_bad_number(&the_reader, name, value);\n \treturn ret;\n }\n \n@@ -3839,14 +3848,23 @@ int parse_config_key(const char *var,\n \treturn 0;\n }\n \n-const char *current_config_origin_type(void)\n+static int reader_origin_type(struct config_reader *reader,\n+\t\t\t      enum config_origin_type *type)\n {\n-\tint type;\n \tif (the_reader.config_kvi)\n-\t\ttype = the_reader.config_kvi->origin_type;\n+\t\t*type = reader->config_kvi->origin_type;\n \telse if(the_reader.source)\n-\t\ttype = the_reader.source->origin_type;\n+\t\t*type = reader->source->origin_type;\n \telse\n+\t\treturn 1;\n+\treturn 0;\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 \tswitch (type) {\n@@ -3885,14 +3903,21 @@ const char *config_scope_name(enum config_scope scope)\n \t}\n }\n \n-const char *current_config_name(void)\n+static int reader_config_name(struct config_reader *reader, const char **out)\n {\n-\tconst char *name;\n \tif (the_reader.config_kvi)\n-\t\tname = the_reader.config_kvi->filename;\n+\t\t*out = reader->config_kvi->filename;\n \telse if (the_reader.source)\n-\t\tname = the_reader.source->name;\n+\t\t*out = reader->source->name;\n \telse\n+\t\treturn 1;\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 }\ndiff --git a/config.h b/config.h\nindex 7606246531a..66c8b996e15 100644\n--- a/config.h\n+++ b/config.h\n@@ -56,6 +56,7 @@ struct git_config_source {\n };\n \n enum config_origin_type {\n+\tCONFIG_ORIGIN_UNKNOWN = 0,\n \tCONFIG_ORIGIN_BLOB,\n \tCONFIG_ORIGIN_FILE,\n \tCONFIG_ORIGIN_STDIN,\ndiff --git a/t/helper/test-config.c b/t/helper/test-config.c\nindex 4ba9eb65606..26e79168f6a 100644\n--- a/t/helper/test-config.c\n+++ b/t/helper/test-config.c\n@@ -30,6 +30,9 @@\n  * iterate -> iterate over all values using git_config(), and print some\n  *            data for each\n  *\n+ * git_config_int -> iterate over all values using git_config() and print the\n+ *                   integer value for the entered key or die\n+ *\n  * Examples:\n  *\n  * To print the value with highest priority for key \"foo.bAr Baz.rock\":\n@@ -54,6 +57,17 @@ 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+{\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\tprintf(\"%d\\n\", parsed);\n+\t}\n+\treturn 0;\n+}\n+\n static int early_config_cb(const char *var, const char *value, void *vdata)\n {\n \tconst char *key = vdata;\n@@ -176,6 +190,9 @@ int cmd__config(int argc, const char **argv)\n \t} else if (!strcmp(argv[1], \"iterate\")) {\n \t\tgit_config(iterate_cb, NULL);\n \t\tgoto exit0;\n+\t} else if (argc == 3 && !strcmp(argv[1], \"git_config_int\")) {\n+\t\tgit_config(parse_int_cb, (void *) argv[2]);\n+\t\tgoto exit0;\n \t}\n \n \tdie(\"%s: Please check the syntax and the function name\", argv[0]);\ndiff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh\nindex b38e158d3b2..9733bed30a9 100755\n--- a/t/t1308-config-set.sh\n+++ b/t/t1308-config-set.sh\n@@ -120,6 +120,10 @@ test_expect_success 'find integer value for a key' '\n \tcheck_config get_int lamb.chop 65\n '\n \n+test_expect_success 'parse integer value during iteration' '\n+\tcheck_config git_config_int lamb.chop 65\n+'\n+\n test_expect_success 'find string value for a key' '\n \tcheck_config get_string case.baz hask &&\n \tcheck_config expect_code 1 get_string case.ba \"Value not found for \\\"case.ba\\\"\"\n@@ -134,6 +138,11 @@ test_expect_success 'find integer if value is non parse-able' '\n \tcheck_config expect_code 128 get_int lamb.head\n '\n \n+test_expect_success 'non parse-able integer value during iteration' '\n+\tcheck_config expect_code 128 git_config_int lamb.head 2>result &&\n+\tgrep \"fatal: bad numeric config value .* in file \\.git/config\" result\n+'\n+\n test_expect_success 'find bool value for the entered key' '\n \tcheck_config get_bool goat.head 1 &&\n \tcheck_config get_bool goat.skin 0 &&\n-- \ngitgitgadget\n\n"},{"id":"474345","messageId":"ab800aa104c32e00c357aa450c5fd13cbe791ed2.1680025914.git.gitgitgadget@gmail.com","threadId":"59327","inReplyTo":"pull.1463.v3.git.git.1680025914.gitgitgadget@gmail.com","subject":"[PATCH v3 8/8] config.c: rename \"struct config_source cf\"","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-03-28T17:51:54Z","receivedAt":"2023-03-28T17:52:32Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThe \"cf\" name is a holdover from before 4d8dd1494e (config: make parsing\nstack struct independent from actual data source, 2013-07-12), when the\nstruct was named config_file. Since that acronym no longer makes sense,\nrename \"cf\" to \"cs\". In some places, we have \"struct config_set cs\", so\nto avoid conflict, rename those \"cs\" to \"set\" (\"config_set\" would be\nmore descriptive, but it's much longer and would require us to rewrap\nseveral lines).\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 262 +++++++++++++++++++++++++++----------------------------\n 1 file changed, 131 insertions(+), 131 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 46c5a9a7d51..cf571d45282 100644\n--- a/config.c\n+++ b/config.c\n@@ -202,7 +202,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 *cf, const char *path,\n+static int handle_path_include(struct config_source *cs, const char *path,\n \t\t\t       struct config_include_data *inc)\n {\n \tint ret = 0;\n@@ -224,14 +224,14 @@ static int handle_path_include(struct config_source *cf, const char *path,\n \tif (!is_absolute_path(path)) {\n \t\tchar *slash;\n \n-\t\tif (!cf || !cf->path) {\n+\t\tif (!cs || !cs->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(cf->path);\n+\t\tslash = find_last_dir_sep(cs->path);\n \t\tif (slash)\n-\t\t\tstrbuf_add(&buf, cf->path, slash - cf->path + 1);\n+\t\t\tstrbuf_add(&buf, cs->path, slash - cs->path + 1);\n \t\tstrbuf_addstr(&buf, path);\n \t\tpath = buf.buf;\n \t}\n@@ -239,8 +239,8 @@ static int handle_path_include(struct config_source *cf, 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    !cf ? \"<unknown>\" :\n-\t\t\t    cf->name ? cf->name :\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\tinc->depth--;\n@@ -257,7 +257,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 *cf,\n+static int prepare_include_condition_pattern(struct config_source *cs,\n \t\t\t\t\t     struct strbuf *pat)\n {\n \tstruct strbuf path = STRBUF_INIT;\n@@ -274,11 +274,11 @@ static int prepare_include_condition_pattern(struct config_source *cf,\n \tif (pat->buf[0] == '.' && is_dir_sep(pat->buf[1])) {\n \t\tconst char *slash;\n \n-\t\tif (!cf || !cf->path)\n+\t\tif (!cs || !cs->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, cf->path, 1);\n+\t\tstrbuf_realpath(&path, cs->path, 1);\n \t\tslash = find_last_dir_sep(path.buf);\n \t\tif (!slash)\n \t\t\tBUG(\"how is this possible?\");\n@@ -293,7 +293,7 @@ static int prepare_include_condition_pattern(struct config_source *cf,\n \treturn prefix;\n }\n \n-static int include_by_gitdir(struct config_source *cf,\n+static int include_by_gitdir(struct config_source *cs,\n \t\t\t     const struct config_options *opts,\n \t\t\t     const char *cond, size_t cond_len, int icase)\n {\n@@ -310,7 +310,7 @@ static int include_by_gitdir(struct config_source *cf,\n \n \tstrbuf_realpath(&text, git_dir, 1);\n \tstrbuf_add(&pattern, cond, cond_len);\n-\tprefix = prepare_include_condition_pattern(cf, &pattern);\n+\tprefix = prepare_include_condition_pattern(cs, &pattern);\n \n again:\n \tif (prefix < 0)\n@@ -449,16 +449,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 *cf,\n+static int include_condition_is_true(struct config_source *cs,\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(cf, opts, cond, cond_len, 0);\n+\t\treturn include_by_gitdir(cs, 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(cf, opts, cond, cond_len, 1);\n+\t\treturn include_by_gitdir(cs, 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@@ -472,7 +472,7 @@ static int include_condition_is_true(struct config_source *cf,\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 *cf = inc->config_reader->source;\n+\tstruct config_source *cs = inc->config_reader->source;\n \tconst char *cond, *key;\n \tsize_t cond_len;\n \tint ret;\n@@ -486,16 +486,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(cf, value, inc);\n+\t\tret = handle_path_include(cs, value, inc);\n \n \tif (!parse_config_key(var, \"includeif\", &cond, &cond_len, &key) &&\n-\t    cond && include_condition_is_true(cf, inc, cond, cond_len) &&\n+\t    cond && include_condition_is_true(cs, 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(cf, value, inc);\n+\t\tret = handle_path_include(cs, value, inc);\n \t\tinc->fn = old_fn;\n \t}\n \n@@ -820,21 +820,21 @@ out:\n \treturn ret;\n }\n \n-static int get_next_char(struct config_source *cf)\n+static int get_next_char(struct config_source *cs)\n {\n-\tint c = cf->do_fgetc(cf);\n+\tint c = cs->do_fgetc(cs);\n \n \tif (c == '\\r') {\n \t\t/* DOS like systems */\n-\t\tc = cf->do_fgetc(cf);\n+\t\tc = cs->do_fgetc(cs);\n \t\tif (c != '\\n') {\n \t\t\tif (c != EOF)\n-\t\t\t\tcf->do_ungetc(c, cf);\n+\t\t\t\tcs->do_ungetc(c, cs);\n \t\t\tc = '\\r';\n \t\t}\n \t}\n \n-\tif (c != EOF && ++cf->total_len > INT_MAX) {\n+\tif (c != EOF && ++cs->total_len > INT_MAX) {\n \t\t/*\n \t\t * This is an absurdly long config file; refuse to parse\n \t\t * further in order to protect downstream code from integer\n@@ -842,38 +842,38 @@ static int get_next_char(struct config_source *cf)\n \t\t * but we can mark EOF and put trash in the return value,\n \t\t * which will trigger a parse error.\n \t\t */\n-\t\tcf->eof = 1;\n+\t\tcs->eof = 1;\n \t\treturn 0;\n \t}\n \n \tif (c == '\\n')\n-\t\tcf->linenr++;\n+\t\tcs->linenr++;\n \tif (c == EOF) {\n-\t\tcf->eof = 1;\n-\t\tcf->linenr++;\n+\t\tcs->eof = 1;\n+\t\tcs->linenr++;\n \t\tc = '\\n';\n \t}\n \treturn c;\n }\n \n-static char *parse_value(struct config_source *cf)\n+static char *parse_value(struct config_source *cs)\n {\n \tint quote = 0, comment = 0, space = 0;\n \n-\tstrbuf_reset(&cf->value);\n+\tstrbuf_reset(&cs->value);\n \tfor (;;) {\n-\t\tint c = get_next_char(cf);\n+\t\tint c = get_next_char(cs);\n \t\tif (c == '\\n') {\n \t\t\tif (quote) {\n-\t\t\t\tcf->linenr--;\n+\t\t\t\tcs->linenr--;\n \t\t\t\treturn NULL;\n \t\t\t}\n-\t\t\treturn cf->value.buf;\n+\t\t\treturn cs->value.buf;\n \t\t}\n \t\tif (comment)\n \t\t\tcontinue;\n \t\tif (isspace(c) && !quote) {\n-\t\t\tif (cf->value.len)\n+\t\t\tif (cs->value.len)\n \t\t\t\tspace++;\n \t\t\tcontinue;\n \t\t}\n@@ -884,9 +884,9 @@ static char *parse_value(struct config_source *cf)\n \t\t\t}\n \t\t}\n \t\tfor (; space; space--)\n-\t\t\tstrbuf_addch(&cf->value, ' ');\n+\t\t\tstrbuf_addch(&cs->value, ' ');\n \t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char(cf);\n+\t\t\tc = get_next_char(cs);\n \t\t\tswitch (c) {\n \t\t\tcase '\\n':\n \t\t\t\tcontinue;\n@@ -906,18 +906,18 @@ static char *parse_value(struct config_source *cf)\n \t\t\tdefault:\n \t\t\t\treturn NULL;\n \t\t\t}\n-\t\t\tstrbuf_addch(&cf->value, c);\n+\t\t\tstrbuf_addch(&cs->value, c);\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '\"') {\n \t\t\tquote = 1-quote;\n \t\t\tcontinue;\n \t\t}\n-\t\tstrbuf_addch(&cf->value, c);\n+\t\tstrbuf_addch(&cs->value, c);\n \t}\n }\n \n-static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n+static int get_value(struct config_source *cs, config_fn_t fn, void *data,\n \t\t     struct strbuf *name)\n {\n \tint c;\n@@ -926,8 +926,8 @@ static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n \n \t/* Get the full name */\n \tfor (;;) {\n-\t\tc = get_next_char(cf);\n-\t\tif (cf->eof)\n+\t\tc = get_next_char(cs);\n+\t\tif (cs->eof)\n \t\t\tbreak;\n \t\tif (!iskeychar(c))\n \t\t\tbreak;\n@@ -935,13 +935,13 @@ static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n \t}\n \n \twhile (c == ' ' || c == '\\t')\n-\t\tc = get_next_char(cf);\n+\t\tc = get_next_char(cs);\n \n \tvalue = NULL;\n \tif (c != '\\n') {\n \t\tif (c != '=')\n \t\t\treturn -1;\n-\t\tvalue = parse_value(cf);\n+\t\tvalue = parse_value(cs);\n \t\tif (!value)\n \t\t\treturn -1;\n \t}\n@@ -950,21 +950,21 @@ static int get_value(struct config_source *cf, config_fn_t fn, void *data,\n \t * the line we just parsed during the call to fn to get\n \t * accurate line number in error messages.\n \t */\n-\tcf->linenr--;\n+\tcs->linenr--;\n \tret = fn(name->buf, value, data);\n \tif (ret >= 0)\n-\t\tcf->linenr++;\n+\t\tcs->linenr++;\n \treturn ret;\n }\n \n-static int get_extended_base_var(struct config_source *cf, struct strbuf *name,\n+static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n \t\t\t\t int c)\n {\n-\tcf->subsection_case_sensitive = 0;\n+\tcs->subsection_case_sensitive = 0;\n \tdo {\n \t\tif (c == '\\n')\n \t\t\tgoto error_incomplete_line;\n-\t\tc = get_next_char(cf);\n+\t\tc = get_next_char(cs);\n \t} while (isspace(c));\n \n \t/* We require the format to be '[base \"extension\"]' */\n@@ -973,13 +973,13 @@ static int get_extended_base_var(struct config_source *cf, struct strbuf *name,\n \tstrbuf_addch(name, '.');\n \n \tfor (;;) {\n-\t\tint c = get_next_char(cf);\n+\t\tint c = get_next_char(cs);\n \t\tif (c == '\\n')\n \t\t\tgoto error_incomplete_line;\n \t\tif (c == '\"')\n \t\t\tbreak;\n \t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char(cf);\n+\t\t\tc = get_next_char(cs);\n \t\t\tif (c == '\\n')\n \t\t\t\tgoto error_incomplete_line;\n \t\t}\n@@ -987,25 +987,25 @@ static int get_extended_base_var(struct config_source *cf, struct strbuf *name,\n \t}\n \n \t/* Final ']' */\n-\tif (get_next_char(cf) != ']')\n+\tif (get_next_char(cs) != ']')\n \t\treturn -1;\n \treturn 0;\n error_incomplete_line:\n-\tcf->linenr--;\n+\tcs->linenr--;\n \treturn -1;\n }\n \n-static int get_base_var(struct config_source *cf, struct strbuf *name)\n+static int get_base_var(struct config_source *cs, struct strbuf *name)\n {\n-\tcf->subsection_case_sensitive = 1;\n+\tcs->subsection_case_sensitive = 1;\n \tfor (;;) {\n-\t\tint c = get_next_char(cf);\n-\t\tif (cf->eof)\n+\t\tint c = get_next_char(cs);\n+\t\tif (cs->eof)\n \t\t\treturn -1;\n \t\tif (c == ']')\n \t\t\treturn 0;\n \t\tif (isspace(c))\n-\t\t\treturn get_extended_base_var(cf, name, c);\n+\t\t\treturn get_extended_base_var(cs, name, c);\n \t\tif (!iskeychar(c) && c != '.')\n \t\t\treturn -1;\n \t\tstrbuf_addch(name, tolower(c));\n@@ -1018,7 +1018,7 @@ struct parse_event_data {\n \tconst struct config_options *opts;\n };\n \n-static int do_event(struct config_source *cf, enum config_event_t type,\n+static int do_event(struct config_source *cs, enum config_event_t type,\n \t\t    struct parse_event_data *data)\n {\n \tsize_t offset;\n@@ -1030,7 +1030,7 @@ static int do_event(struct config_source *cf, enum config_event_t type,\n \t    data->previous_type == type)\n \t\treturn 0;\n \n-\toffset = cf->do_ftell(cf);\n+\toffset = cs->do_ftell(cs);\n \t/*\n \t * At EOF, the parser always \"inserts\" an extra '\\n', therefore\n \t * the end offset of the event is the current file position, otherwise\n@@ -1050,12 +1050,12 @@ static int do_event(struct config_source *cf, enum config_event_t type,\n \treturn 0;\n }\n \n-static int git_parse_source(struct config_source *cf, config_fn_t fn,\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 {\n \tint comment = 0;\n \tsize_t baselen = 0;\n-\tstruct strbuf *var = &cf->var;\n+\tstruct strbuf *var = &cs->var;\n \tint error_return = 0;\n \tchar *error_msg = NULL;\n \n@@ -1070,7 +1070,7 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,\n \tfor (;;) {\n \t\tint c;\n \n-\t\tc = get_next_char(cf);\n+\t\tc = get_next_char(cs);\n \t\tif (bomptr && *bomptr) {\n \t\t\t/* We are at the file beginning; skip UTF8-encoded BOM\n \t\t\t * if present. Sane editors won't put this in on their\n@@ -1087,12 +1087,12 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,\n \t\t\t}\n \t\t}\n \t\tif (c == '\\n') {\n-\t\t\tif (cf->eof) {\n-\t\t\t\tif (do_event(cf, CONFIG_EVENT_EOF, &event_data) < 0)\n+\t\t\tif (cs->eof) {\n+\t\t\t\tif (do_event(cs, CONFIG_EVENT_EOF, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\t\treturn 0;\n \t\t\t}\n-\t\t\tif (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n+\t\t\tif (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n \t\t\t\treturn -1;\n \t\t\tcomment = 0;\n \t\t\tcontinue;\n@@ -1100,23 +1100,23 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,\n \t\tif (comment)\n \t\t\tcontinue;\n \t\tif (isspace(c)) {\n-\t\t\tif (do_event(cf, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n+\t\t\tif (do_event(cs, CONFIG_EVENT_WHITESPACE, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '#' || c == ';') {\n-\t\t\tif (do_event(cf, CONFIG_EVENT_COMMENT, &event_data) < 0)\n+\t\t\tif (do_event(cs, CONFIG_EVENT_COMMENT, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \t\t\tcomment = 1;\n \t\t\tcontinue;\n \t\t}\n \t\tif (c == '[') {\n-\t\t\tif (do_event(cf, CONFIG_EVENT_SECTION, &event_data) < 0)\n+\t\t\tif (do_event(cs, CONFIG_EVENT_SECTION, &event_data) < 0)\n \t\t\t\t\treturn -1;\n \n \t\t\t/* Reset prior to determining a new stem */\n \t\t\tstrbuf_reset(var);\n-\t\t\tif (get_base_var(cf, var) < 0 || var->len < 1)\n+\t\t\tif (get_base_var(cs, var) < 0 || var->len < 1)\n \t\t\t\tbreak;\n \t\t\tstrbuf_addch(var, '.');\n \t\t\tbaselen = var->len;\n@@ -1125,7 +1125,7 @@ static int git_parse_source(struct config_source *cf, config_fn_t fn,\n \t\tif (!isalpha(c))\n \t\t\tbreak;\n \n-\t\tif (do_event(cf, CONFIG_EVENT_ENTRY, &event_data) < 0)\n+\t\tif (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)\n \t\t\treturn -1;\n \n \t\t/*\n@@ -1135,42 +1135,42 @@ static int git_parse_source(struct config_source *cf, 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(cf, fn, data, var) < 0)\n+\t\tif (get_value(cs, fn, data, var) < 0)\n \t\t\tbreak;\n \t}\n \n-\tif (do_event(cf, CONFIG_EVENT_ERROR, &event_data) < 0)\n+\tif (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)\n \t\treturn -1;\n \n-\tswitch (cf->origin_type) {\n+\tswitch (cs->origin_type) {\n \tcase CONFIG_ORIGIN_BLOB:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in blob %s\"),\n-\t\t\t\t      cf->linenr, cf->name);\n+\t\t\t\t      cs->linenr, cs->name);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_FILE:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in file %s\"),\n-\t\t\t\t      cf->linenr, cf->name);\n+\t\t\t\t      cs->linenr, cs->name);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_STDIN:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in standard input\"),\n-\t\t\t\t      cf->linenr);\n+\t\t\t\t      cs->linenr);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_SUBMODULE_BLOB:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in submodule-blob %s\"),\n-\t\t\t\t       cf->linenr, cf->name);\n+\t\t\t\t       cs->linenr, cs->name);\n \t\tbreak;\n \tcase CONFIG_ORIGIN_CMDLINE:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in command line %s\"),\n-\t\t\t\t       cf->linenr, cf->name);\n+\t\t\t\t       cs->linenr, cs->name);\n \t\tbreak;\n \tdefault:\n \t\terror_msg = xstrfmt(_(\"bad config line %d in %s\"),\n-\t\t\t\t      cf->linenr, cf->name);\n+\t\t\t\t      cs->linenr, cs->name);\n \t}\n \n \tswitch (opts && opts->error_action ?\n \t\topts->error_action :\n-\t\tcf->default_error_action) {\n+\t\tcs->default_error_action) {\n \tcase CONFIG_ERROR_DIE:\n \t\tdie(\"%s\", error_msg);\n \t\tbreak;\n@@ -2280,13 +2280,13 @@ 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 *cs,\n+static void configset_iter(struct config_reader *reader, struct config_set *set,\n \t\t\t   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 = &cs->list;\n+\tstruct configset_list *list = &set->list;\n \n \tfor (i = 0; i < list->nr; i++) {\n \t\tentry = list->items[i].e;\n@@ -2351,7 +2351,7 @@ void read_very_early_config(config_fn_t cb, void *data)\n \tconfig_with_options(cb, data, NULL, &opts);\n }\n \n-static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)\n+static struct config_set_element *configset_find_element(struct config_set *set, const char *key)\n {\n \tstruct config_set_element k;\n \tstruct config_set_element *found_entry;\n@@ -2365,13 +2365,13 @@ static struct config_set_element *configset_find_element(struct config_set *cs,\n \n \thashmap_entry_init(&k.ent, strhash(normalized_key));\n \tk.key = normalized_key;\n-\tfound_entry = hashmap_get_entry(&cs->config_hash, &k, ent, NULL);\n+\tfound_entry = hashmap_get_entry(&set->config_hash, &k, ent, NULL);\n \tfree(normalized_key);\n \treturn found_entry;\n }\n \n static int configset_add_value(struct config_reader *reader,\n-\t\t\t       struct config_set *cs, const char *key,\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@@ -2379,7 +2379,7 @@ static int configset_add_value(struct config_reader *reader,\n \tstruct configset_list_item *l_item;\n \tstruct key_value_info *kv_info = xmalloc(sizeof(*kv_info));\n \n-\te = configset_find_element(cs, key);\n+\te = configset_find_element(set, key);\n \t/*\n \t * Since the keys are being fed by git_config*() callback mechanism, they\n \t * are already normalized. So simply add them without any further munging.\n@@ -2389,12 +2389,12 @@ static int configset_add_value(struct config_reader *reader,\n \t\thashmap_entry_init(&e->ent, strhash(key));\n \t\te->key = xstrdup(key);\n \t\tstring_list_init_dup(&e->value_list);\n-\t\thashmap_add(&cs->config_hash, &e->ent);\n+\t\thashmap_add(&set->config_hash, &e->ent);\n \t}\n \tsi = string_list_append_nodup(&e->value_list, xstrdup_or_null(value));\n \n-\tALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc);\n-\tl_item = &cs->list.items[cs->list.nr++];\n+\tALLOC_GROW(set->list.items, set->list.nr + 1, set->list.alloc);\n+\tl_item = &set->list.items[set->list.nr++];\n \tl_item->e = e;\n \tl_item->value_index = e->value_list.nr - 1;\n \n@@ -2429,33 +2429,33 @@ static int config_set_element_cmp(const void *cmp_data UNUSED,\n \treturn strcmp(e1->key, e2->key);\n }\n \n-void git_configset_init(struct config_set *cs)\n+void git_configset_init(struct config_set *set)\n {\n-\thashmap_init(&cs->config_hash, config_set_element_cmp, NULL, 0);\n-\tcs->hash_initialized = 1;\n-\tcs->list.nr = 0;\n-\tcs->list.alloc = 0;\n-\tcs->list.items = NULL;\n+\thashmap_init(&set->config_hash, config_set_element_cmp, NULL, 0);\n+\tset->hash_initialized = 1;\n+\tset->list.nr = 0;\n+\tset->list.alloc = 0;\n+\tset->list.items = NULL;\n }\n \n-void git_configset_clear(struct config_set *cs)\n+void git_configset_clear(struct config_set *set)\n {\n \tstruct config_set_element *entry;\n \tstruct hashmap_iter iter;\n-\tif (!cs->hash_initialized)\n+\tif (!set->hash_initialized)\n \t\treturn;\n \n-\thashmap_for_each_entry(&cs->config_hash, &iter, entry,\n+\thashmap_for_each_entry(&set->config_hash, &iter, entry,\n \t\t\t\tent /* member name */) {\n \t\tfree(entry->key);\n \t\tstring_list_clear(&entry->value_list, 1);\n \t}\n-\thashmap_clear_and_free(&cs->config_hash, struct config_set_element, ent);\n-\tcs->hash_initialized = 0;\n-\tfree(cs->list.items);\n-\tcs->list.nr = 0;\n-\tcs->list.alloc = 0;\n-\tcs->list.items = NULL;\n+\thashmap_clear_and_free(&set->config_hash, struct config_set_element, ent);\n+\tset->hash_initialized = 0;\n+\tfree(set->list.items);\n+\tset->list.nr = 0;\n+\tset->list.alloc = 0;\n+\tset->list.items = NULL;\n }\n \n struct configset_add_data {\n@@ -2471,15 +2471,15 @@ static int config_set_callback(const char *key, const char *value, void *cb)\n \treturn 0;\n }\n \n-int git_configset_add_file(struct config_set *cs, const char *filename)\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 = cs;\n+\tdata.config_set = set;\n \treturn git_config_from_file(config_set_callback, filename, &data);\n }\n \n-int git_configset_get_value(struct config_set *cs, const char *key, const char **value)\n+int git_configset_get_value(struct config_set *set, const char *key, const char **value)\n {\n \tconst struct string_list *values = NULL;\n \t/*\n@@ -2487,7 +2487,7 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *\n \t * queried key in the files of the configset, the value returned will be the last\n \t * value in the value list for that key.\n \t */\n-\tvalues = git_configset_get_value_multi(cs, key);\n+\tvalues = git_configset_get_value_multi(set, key);\n \n \tif (!values)\n \t\treturn 1;\n@@ -2496,26 +2496,26 @@ int git_configset_get_value(struct config_set *cs, const char *key, const char *\n \treturn 0;\n }\n \n-const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)\n+const struct string_list *git_configset_get_value_multi(struct config_set *set, const char *key)\n {\n-\tstruct config_set_element *e = configset_find_element(cs, key);\n+\tstruct config_set_element *e = configset_find_element(set, key);\n \treturn e ? &e->value_list : NULL;\n }\n \n-int git_configset_get_string(struct config_set *cs, const char *key, char **dest)\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(cs, key, &value))\n+\tif (!git_configset_get_value(set, key, &value))\n \t\treturn git_config_string((const char **)dest, key, value);\n \telse\n \t\treturn 1;\n }\n \n-static int git_configset_get_string_tmp(struct config_set *cs, const char *key,\n+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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(key);\n \t\t*dest = value;\n@@ -2525,51 +2525,51 @@ static int git_configset_get_string_tmp(struct config_set *cs, const char *key,\n \t}\n }\n \n-int git_configset_get_int(struct config_set *cs, const char *key, int *dest)\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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_config_int(key, value);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n }\n \n-int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_config_ulong(key, value);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n }\n \n-int git_configset_get_bool(struct config_set *cs, const char *key, int *dest)\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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_config_bool(key, value);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n }\n \n-int git_configset_get_bool_or_int(struct config_set *cs, const char *key,\n+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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_config_bool_or_int(key, value, is_bool);\n \t\treturn 0;\n \t} else\n \t\treturn 1;\n }\n \n-int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest)\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(cs, key, &value)) {\n+\tif (!git_configset_get_value(set, key, &value)) {\n \t\t*dest = git_parse_maybe_bool(value);\n \t\tif (*dest == -1)\n \t\t\treturn -1;\n@@ -2578,10 +2578,10 @@ int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *de\n \t\treturn 1;\n }\n \n-int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest)\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(cs, key, &value))\n+\tif (!git_configset_get_value(set, key, &value))\n \t\treturn git_config_pathname(dest, key, value);\n \telse\n \t\treturn 1;\n@@ -2972,7 +2972,7 @@ static int store_aux_event(enum config_event_t type,\n \t\t\t   size_t begin, size_t end, void *data)\n {\n \tstruct config_store_data *store = data;\n-\tstruct config_source *cf = store->config_reader->source;\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@@ -2982,10 +2982,10 @@ static int store_aux_event(enum config_event_t type,\n \tif (type == CONFIG_EVENT_SECTION) {\n \t\tint (*cmpfn)(const char *, const char *, size_t);\n \n-\t\tif (cf->var.len < 2 || cf->var.buf[cf->var.len - 1] != '.')\n-\t\t\treturn error(_(\"invalid section name '%s'\"), cf->var.buf);\n+\t\tif (cs->var.len < 2 || cs->var.buf[cs->var.len - 1] != '.')\n+\t\t\treturn error(_(\"invalid section name '%s'\"), cs->var.buf);\n \n-\t\tif (cf->subsection_case_sensitive)\n+\t\tif (cs->subsection_case_sensitive)\n \t\t\tcmpfn = strncasecmp;\n \t\telse\n \t\t\tcmpfn = strncmp;\n@@ -2993,8 +2993,8 @@ static int store_aux_event(enum config_event_t type,\n \t\t/* Is this the section we were looking for? */\n \t\tstore->is_keys_section =\n \t\t\tstore->parsed[store->parsed_nr].is_keys_section =\n-\t\t\tcf->var.len - 1 == store->baselen &&\n-\t\t\t!cmpfn(cf->var.buf, store->key, store->baselen);\n+\t\t\tcs->var.len - 1 == store->baselen &&\n+\t\t\t!cmpfn(cs->var.buf, store->key, store->baselen);\n \t\tif (store->is_keys_section) {\n \t\t\tstore->section_seen = 1;\n \t\t\tALLOC_GROW(store->seen, store->seen_nr + 1,\n-- \ngitgitgadget\n"},{"id":"474348","messageId":"kl6llejgdacq.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"pull.1463.v3.git.git.1680025914.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 0/8] config.c: use struct for config reading state","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-28T18:00:53Z","receivedAt":"2023-03-28T18:00:59Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Note to Junio: 8/8 (which renames \"cs\" -> \"set\") conflicts with\n> ab/config-multi-and-nonbool. I previously said that I'd rebase this, but\n> presumably a remerge-diff is more ergonomic + flexible (let me know if I'm\n> mistaken), so I'll send a remerge-diff in a reply (I don't trust GGG not to\n> mangle the patch :/).\n\n\n  diff --git a/config.c b/config.c\n  remerge CONFLICT (content): Merge conflict in config.c\n  index b17658e1ba..159c404d0c 100644\n  --- a/config.c\n  +++ b/config.c\n  @@ -2351,15 +2351,9 @@ void read_very_early_config(config_fn_t cb, void *data)\n    config_with_options(cb, data, NULL, &opts);\n  }\n\n  -<<<<<<< HEAD\n  -static struct config_set_element *configset_find_element(struct config_set *set, const char *key)\n  -||||||| c000d91638\n  -static struct config_set_element *configset_find_element(struct config_set *cs, const char *key)\n  -=======\n  RESULT_MUST_BE_USED\n  -static int configset_find_element(struct config_set *cs, const char *key,\n  +static int configset_find_element(struct config_set *set, const char *key,\n            struct config_set_element **dest)\n  ->>>>>>> gitster/ab/config-multi-and-nonbool\n  {\n    struct config_set_element k;\n    struct config_set_element *found_entry;\n  @@ -2392,15 +2386,9 @@ static int configset_add_value(struct config_reader *reader,\n    struct key_value_info *kv_info = xmalloc(sizeof(*kv_info));\n    int ret;\n\n  -<<<<<<< HEAD\n  -\te = configset_find_element(set, key);\n  -||||||| c000d91638\n  -\te = configset_find_element(cs, key);\n  -=======\n  -\tret = configset_find_element(cs, key, &e);\n  +\tret = configset_find_element(set, key, &e);\n    if (ret)\n      return ret;\n  ->>>>>>> gitster/ab/config-multi-and-nonbool\n    /*\n    * Since the keys are being fed by git_config*() callback mechanism, they\n    * are already normalized. So simply add them without any further munging.\n  @@ -2510,40 +2498,21 @@ int git_configset_get_value(struct config_set *set, const char *key, const char\n    * queried key in the files of the configset, the value returned will be the last\n    * value in the value list for that key.\n    */\n  -<<<<<<< HEAD\n  -\tvalues = git_configset_get_value_multi(set, key);\n  -||||||| c000d91638\n  -\tvalues = git_configset_get_value_multi(cs, key);\n  -=======\n  -\tif ((ret = git_configset_get_value_multi(cs, key, &values)))\n  +\tif ((ret = git_configset_get_value_multi(set, key, &values)))\n      return ret;\n  ->>>>>>> gitster/ab/config-multi-and-nonbool\n\n    assert(values->nr > 0);\n    *value = values->items[values->nr - 1].string;\n    return 0;\n  }\n\n  -<<<<<<< HEAD\n  -const struct string_list *git_configset_get_value_multi(struct config_set *set, const char *key)\n  -||||||| c000d91638\n  -const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key)\n  -=======\n  -int git_configset_get_value_multi(struct config_set *cs, const char *key,\n  +int git_configset_get_value_multi(struct config_set *set, const char *key,\n            const struct string_list **dest)\n  ->>>>>>> gitster/ab/config-multi-and-nonbool\n  -{\n  -<<<<<<< HEAD\n  -\tstruct config_set_element *e = configset_find_element(set, key);\n  -\treturn e ? &e->value_list : NULL;\n  -||||||| c000d91638\n  -\tstruct config_set_element *e = configset_find_element(cs, key);\n  -\treturn e ? &e->value_list : NULL;\n  -=======\n  +{\n    struct config_set_element *e;\n    int ret;\n\n  -\tif ((ret = configset_find_element(cs, key, &e)))\n  +\tif ((ret = configset_find_element(set, key, &e)))\n      return ret;\n    else if (!e)\n      return 1;\n  @@ -2557,12 +2526,12 @@ static int check_multi_string(struct string_list_item *item, void *util)\n    return item->string ? 0 : config_error_nonbool(util);\n  }\n\n  -int git_configset_get_string_multi(struct config_set *cs, const char *key,\n  +int git_configset_get_string_multi(struct config_set *set, const char *key,\n            const struct string_list **dest)\n  {\n    int ret;\n\n  -\tif ((ret = git_configset_get_value_multi(cs, key, dest)))\n  +\tif ((ret = git_configset_get_value_multi(set, key, dest)))\n      return ret;\n    if ((ret = for_each_string_list((struct string_list *)*dest,\n            check_multi_string, (void *)key)))\n  @@ -2571,17 +2540,16 @@ int git_configset_get_string_multi(struct config_set *cs, const char *key,\n    return 0;\n  }\n\n  -int git_configset_get(struct config_set *cs, const char *key)\n  +int git_configset_get(struct config_set *set, const char *key)\n  {\n    struct config_set_element *e;\n    int ret;\n\n  -\tif ((ret = configset_find_element(cs, key, &e)))\n  +\tif ((ret = configset_find_element(set, key, &e)))\n      return ret;\n    else if (!e)\n      return 1;\n    return 0;\n  ->>>>>>> gitster/ab/config-multi-and-nonbool\n  }\n\n  int git_configset_get_string(struct config_set *set, const char *key, char **dest)\n"},{"id":"474361","messageId":"xmqqsfdoeiqu.fsf@gitster.g","threadId":"59327","inReplyTo":"kl6llejgdacq.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v3 0/8] config.c: use struct for config reading state","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-28T20:14:17Z","receivedAt":"2023-03-28T20:14:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> \"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n>> Note to Junio: 8/8 (which renames \"cs\" -> \"set\") conflicts with\n>> ab/config-multi-and-nonbool. I previously said that I'd rebase this, but\n>> presumably a remerge-diff is more ergonomic + flexible (let me know if I'm\n>> mistaken), so I'll send a remerge-diff in a reply (I don't trust GGG not to\n>> mangle the patch :/).\n\nA patch that is indented by two places would not be mechanically\napplicable anyway and even without such indentation, the hunk-side\nmarkers like c000d91638, gitster/ab/config-multi-and-nonbool, etc.\nyou have in the patch would be different from conflicts I would see,\nso it wouldn't be very useful.  There should probably be a mode\nwhere remerge-diff would omit these labels to help mechanical\napplication of such a \"patch\".\n\nIn any case, remerge-diff certainly helps eyeballing the result.\n\nIn this case, because the difference between v2 and v3 does not\noverlap with the area that v2 (or v3 for that matter) would conflict\nwith other topics in flight, my rerere database can clealy reuse the\nrecorded resolution for conflicts between this iteration and the\ntopics in flight just fine.  I'll push out the integration result\nafter I am done with other topics.\n\nThanks.\n"},{"id":"474381","messageId":"kl6lilekd0xa.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"xmqqsfdoeiqu.fsf@gitster.g","subject":"Re: [PATCH v3 0/8] config.c: use struct for config reading state","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-28T21:24:33Z","receivedAt":"2023-03-28T21:24:49Z","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> Glen Choo <chooglen@google.com> writes:\n>\n>> \"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>>\n>>> Note to Junio: 8/8 (which renames \"cs\" -> \"set\") conflicts with\n>>> ab/config-multi-and-nonbool. I previously said that I'd rebase this, but\n>>> presumably a remerge-diff is more ergonomic + flexible (let me know if I'm\n>>> mistaken), so I'll send a remerge-diff in a reply (I don't trust GGG not to\n>>> mangle the patch :/).\n>\n> A patch that is indented by two places would not be mechanically\n> applicable anyway and even without such indentation, the hunk-side\n> markers like c000d91638, gitster/ab/config-multi-and-nonbool, etc.\n> you have in the patch would be different from conflicts I would see,\n> so it wouldn't be very useful.  There should probably be a mode\n> where remerge-diff would omit these labels to help mechanical\n> application of such a \"patch\".\n\nAh, bummer :(\n\nI was thinking about that as well. remerge-diff actually outputs\nleft and right in \"--format=reference\", which I then manually modified\nto reflect the conflicted version in the working tree, giving the final\nresult here. I wonder what remerge-diff or \"git apply\" could do instead.\n"},{"id":"474403","messageId":"230329.86sfdnvlke.gmgdl@evledraar.gmail.com","threadId":"59327","inReplyTo":"72774fd08f3eb9ff1d449814637e584692ba2bfc.1680025914.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 3/8] config.c: create config_reader and the_reader","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-03-29T10:41:32Z","receivedAt":"2023-03-29T11:33:51Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Mar 28 2023, Glen Choo via GitGitGadget wrote:\n\n> From: Glen Choo <chooglen@google.com>\n>\n> Create \"struct config_reader\" to hold the state of the config source\n> currently being read. Then, create a static instance of it,\n> \"the_reader\", and use \"the_reader.source\" to replace references to\n> \"cf_global\" in public functions.\n>\n> This doesn't create much immediate benefit (since we're mostly replacing\n> static variables with a bigger static variable), but it prepares us for\n> a future where this state doesn't have to be global; \"struct\n> config_reader\" (or a similar struct) could be provided by the caller, or\n> constructed internally by a function like \"do_config_from()\".\n>\n> A more typical approach would be to put this struct on \"the_repository\",\n> but that's a worse fit for this use case since config reading is not\n> scoped to a repository. E.g. we can read config before the repository is\n> known (\"read_very_early_config()\"), blatantly ignore the repo\n> (\"read_protected_config()\"), or read only from a file\n> (\"git_config_from_file()\"). This is especially evident in t5318 and\n> t9210, where test-tool and scalar parse config but don't fully\n> initialize \"the_repository\".\n\nI don't mean to just rehash previous discussion\n(i.e. https://lore.kernel.org/git/230307.86wn3szrzu.gmgdl@evledraar.gmail.com/\nand downthread). I get that you think sticking this in a \"struct\nrepository *\" here isn't clean, and would prefer to not conflate the\ntwo.\n\nFair enough.\n\nBut I think this paragraph still does a bad job of justifying this\ndirection with reference to existing code.\n\nWhy? Because from reading it you get the impression that with\nread_very_early_config() and read_protected_config() \"config reading is\nnot scoped to a repository\", but \"scoped to\" is doing a *lot* of work\nhere.\n\nAt the start of read_very_early_config() we do:\n\n\tstruct config_options opts = { 0 };\n\t[...]\n\topts.ignore_repo = 1;\n\topts.ignore_worktree = 1;\n\nAnd then call config_with_options(), which does:\n\n\tstruct config_include_data inc = CONFIG_INCLUDE_INIT;\n\nAnd that struct has:\n\n\tstruct git_config_source *config_source;\n\nWhich in turn has:\n\n\t/* The repository if blob is not NULL; leave blank for the_repository */\n\tstruct repository *repo;\n\tconst char *blob;\n\nThe read_protected_config() is then another thin wrapper for\nconfig_with_options().\n\nSo, so far the reader might be genuinely confused, since we already have\na \"repo\" in scope why can't we use it for this cache? Even if just\nreading the system config etc.\n\nFor *those* cases I think what I *think* you're going for is that while\nwe have a \"struct repository\" already, we don't want to use it for our\n\"cache\", and instead have a file-scoped one.\n\nPersonally, I don't see how it's cleaner to always use a file-scope\nrather than piggy-back on the global we almost always have (or provide a\nfallback), but let's not get on that topic again :)\n\nNow, the case that *is* special on the other hand is\ngit_config_from_file(), there we really don't have a \"repository\" at\nall, as it never gets the \"struct config_include_data inc\", or a\n\"git_config_source\".\n\nBut if we dig a bit into those cases there's 3x users of\ngit_config_from_file() outside of config.c itself:\n\n * setup.c, to read only repo's \"config.worktree\"\n * setup.c, to read only repo \"config\"\n * sequencer.c, to read \"sequencer/opts\"\n\nFor the former two, I think the only thing that's needed is something\nlike this, along with a corresponding change to\ndo_git_config_sequence():\n\n\tdiff --git a/config.h b/config.h\n\tindex 7606246531a..b8a3de4eb93 100644\n\t--- a/config.h\n\t+++ b/config.h\n\t@@ -85,7 +85,10 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n\t \n\t struct config_options {\n\t        unsigned int respect_includes : 1;\n\t+       unsigned int ignore_system : 1;\n\t+       unsigned int ignore_global : 1;\n\t        unsigned int ignore_repo : 1;\n\t+       unsigned int ignore_local : 1;\n\t        unsigned int ignore_worktree : 1;\n\t        unsigned int ignore_cmdline : 1;\n\t        unsigned int system_gently : 1;\n\nI.e. we actually *do* have a repo there, we just haven't bridged the gap\nof \"ignore most of its config\" so we can use config_with_options()\nthere.\n\nThe sequencer.c case is trickier, but presumably for such isolated\nreading we could have a lower-level function which would return the\nequivalent of a \"key_value_info\" on errors or whatever.\n\n\nAnyway, I'm fine with this direction for now, but given the above & my\nprevious RFC\nhttps://lore.kernel.org/git/RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com/\nI can't help but think we're taking two steps forward & one step\nbackwards for some of this.\n\nI.e. are we assuming no \"repo\", but per the above we really do have one,\nbut we just don't pass it because we don't have a \"read only the\nworktree config part\", or whatever?\n\nDitto the line number relaying for builtin/config.c, which as my RFC\nshowed we have one or two API users that care, which we can just\nconvert...\n"},{"id":"474404","messageId":"230329.86o7obvkie.gmgdl@evledraar.gmail.com","threadId":"59327","inReplyTo":"kl6la60buquj.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [RFC PATCH 0/5] bypass config.c global state with configset","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2023-03-29T11:53:06Z","receivedAt":"2023-03-29T11:57:01Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Mar 17 2023, Glen Choo wrote:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n> [...]\n>> Similarly, you mention git_parse_int() wanting to report a filename\n>> and/or line number. I'm aware that it can do that, but it doesn't do\n>> so in the common case, e.g.:\n>>\n>> \tgit -c format.filenameMaxLength=abc log\n>> \tfatal: bad numeric config value 'abc' for 'format.filenamemaxlength': invalid unit\n>>\n>> And the same goes for writing it to e.g. ~/.gitconfig. It's only if\n>> you use \"git config --file\" or similar that we'll report a filename.\n>\n> That's true, but I think that's a bug, not a feature. See 7/8 [1] where\n> I addressed it.\n>\n> 1.  https://lore.kernel.org/git/3c83d9535a037653c7de2d462a4df3a3c43a9308.1678925506.git.gitgitgadget@gmail.com/\n>\n>> We can just make config_set_callback() and configset_iter()\n>> non-static, so e.g. the builtin/config.c caller that implements\n>> \"--show-origin\" can keep its config_with_options(...) call, but\n>> instead of \"streaming\" the config, it'll buffer it up into a\n>> configset.\n>\n> Hm, so to extrapolate, we could make it so that nobody outside of\n> config.c uses the *config_from_file() APIs directly. Instead, all reads\n> get buffered up into a configset. That might not be a bad idea. It would\n> definitely help with some of your goals of config API surface reduction.\n>\n> This would be friendlier in cases where we were already creating custom\n> configsets (I know we have some of those, but I don't recall where), but\n> in cases where we were reading the file directly (e.g.\n> builtin/config.c), we'd be taking a memory and runtime hit. I'm not sure\n> how I (or others) feel about that yet.\n\nI'm pretty sure that in the end this wouldn't matter, i.e. the time it\ntakes to parse the config is trivial, and the users of these APIs like\n\"git config -l --show-origin\" aren't performance-senitive.\n\nBut for the general case & if you're concerned about this a trivial\naddition on top of what I suggested would be to pass a streaming\ncallback to config_set_callback(), i.e. you could get the values you'd\nget from configset_iter() as we parse them.\n"},{"id":"474419","messageId":"xmqq7cuz9yhp.fsf@gitster.g","threadId":"59327","inReplyTo":"230329.86sfdnvlke.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 3/8] config.c: create config_reader and the_reader","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-03-29T18:57:38Z","receivedAt":"2023-03-29T18:57:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> But I think this paragraph still does a bad job of justifying this\n> direction with reference to existing code.\n\nI thought it read reasonably well, if not perfect, and do not think\nI am capable of rewriting it better, unfortunately.\n\nCare to suggest a better rewrite?\n\n> \t struct config_options {\n> \t        unsigned int respect_includes : 1;\n> \t+       unsigned int ignore_system : 1;\n> \t+       unsigned int ignore_global : 1;\n> \t        unsigned int ignore_repo : 1;\n> \t+       unsigned int ignore_local : 1;\n> \t        unsigned int ignore_worktree : 1;\n> \t        unsigned int ignore_cmdline : 1;\n> \t        unsigned int system_gently : 1;\n\nThat does look (I am not sure about _local bit, though) well\norganized, but I suspect that it can be left for a follow-on\nclean-up series, perhaps?\n\nThanks.\n"},{"id":"474422","messageId":"kl6lfs9ncomw.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"xmqq7cuz9yhp.fsf@gitster.g","subject":"Re: [PATCH v3 3/8] config.c: create config_reader and the_reader","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-29T20:02:15Z","receivedAt":"2023-03-29T20:02:28Z","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.e. we actually *do* have a repo there, we just haven't bridged the gap\n>>> of \"ignore most of its config\" so we can use config_with_options()\n>>> there.\n>> \t struct config_options {\n>> \t        unsigned int respect_includes : 1;\n>> \t+       unsigned int ignore_system : 1;\n>> \t+       unsigned int ignore_global : 1;\n>> \t        unsigned int ignore_repo : 1;\n>> \t+       unsigned int ignore_local : 1;\n>> \t        unsigned int ignore_worktree : 1;\n>> \t        unsigned int ignore_cmdline : 1;\n>> \t        unsigned int system_gently : 1;\n>\n> That does look (I am not sure about _local bit, though) well\n> organized, but I suspect that it can be left for a follow-on\n> clean-up series, perhaps?\n\nMakes sense, I did suggest something similar previously:\n\n  https://lore.kernel.org/git/kl6ly1oze7wb.fsf@chooglen-macbookpro.roam.corp.google.com\n\nBut I think that's a follow up series for sure.\n"},{"id":"474470","messageId":"kl6lcz4qcels.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"59327","inReplyTo":"230329.86sfdnvlke.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v3 3/8] config.c: create config_reader and the_reader","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-03-30T17:51:11Z","receivedAt":"2023-03-30T17:51:34Z","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 Tue, Mar 28 2023, Glen Choo via GitGitGadget wrote:\n>> A more typical approach would be to put this struct on \"the_repository\",\n>> but that's a worse fit for this use case since config reading is not\n>> scoped to a repository. E.g. we can read config before the repository is\n>> known (\"read_very_early_config()\"), blatantly ignore the repo\n>> (\"read_protected_config()\"), or read only from a file\n>> (\"git_config_from_file()\"). This is especially evident in t5318 and\n>> t9210, where test-tool and scalar parse config but don't fully\n>> initialize \"the_repository\".\n>\n> [...]\n>\n> But I think this paragraph still does a bad job of justifying this\n> direction with reference to existing code.\n>\n> Why? Because from reading it you get the impression that with\n> read_very_early_config() and read_protected_config() \"config reading is\n> not scoped to a repository\", but \"scoped to\" is doing a *lot* of work\n> here.\n>\n> [...]\n>\n> So, so far the reader might be genuinely confused, since we already have\n> a \"repo\" in scope why can't we use it for this cache? Even if just\n> reading the system config etc.\n>\n> For *those* cases I think what I *think* you're going for is that while\n> we have a \"struct repository\" already, we don't want to use it for our\n> \"cache\", and instead have a file-scoped one.\n\nI was probably unclear, bleh. I intended \"repository\" to mean 'the thing\nusers interact with', not \"struct repository\". At any rate, the major\nuse case I'm concerned with is 'reading config from a file', where the\nrepository really isn't relevant at all (more on that later).\n\n> Personally, I don't see how it's cleaner to always use a file-scope\n> rather than piggy-back on the global we almost always have (or provide a\n> fallback), but let's not get on that topic again :)\n\nPiggybacking is probably less intrusive, but I'm not sure it results in\na coherent interface. The _only_ use of git_config_source.repo is to\nread config from blobs in config_with_options() (which we need to read\n.gitmodules from commits, not the working copy). After that, we don't\nactually propagate the \"struct repository\" at all (because it's not\nneeded), and I think it makes sense to keep it that way.\n\n> Now, the case that *is* special on the other hand is\n> git_config_from_file(), there we really don't have a \"repository\" at\n> all, as it never gets the \"struct config_include_data inc\", or a\n> \"git_config_source\".\n>\n> But if we dig a bit into those cases there's 3x users of\n> git_config_from_file() outside of config.c itself:\n>\n>  * setup.c, to read only repo's \"config.worktree\"\n>  * setup.c, to read only repo \"config\"\n>  * sequencer.c, to read \"sequencer/opts\"\n\nWe should also include git_config_from_file_with_options() (which is\nbasically the same thing), which adds one more caller:\n\n* bundle-uri.c, to read bundle URI files\n\n> For the former two, I think the only thing that's needed is something\n> like this, along with a corresponding change to\n> do_git_config_sequence():\n>\n> \tdiff --git a/config.h b/config.h\n> \tindex 7606246531a..b8a3de4eb93 100644\n> \t--- a/config.h\n> \t+++ b/config.h\n> \t@@ -85,7 +85,10 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n> \t \n> \t struct config_options {\n> \t        unsigned int respect_includes : 1;\n> \t+       unsigned int ignore_system : 1;\n> \t+       unsigned int ignore_global : 1;\n> \t        unsigned int ignore_repo : 1;\n> \t+       unsigned int ignore_local : 1;\n> \t        unsigned int ignore_worktree : 1;\n> \t        unsigned int ignore_cmdline : 1;\n> \t        unsigned int system_gently : 1;\n>\n> I.e. we actually *do* have a repo there, we just haven't bridged the gap\n> of \"ignore most of its config\" so we can use config_with_options()\n> there.\n\nI'm ambivalent on this. On the one hand, you're not wrong to say that\nthere probably _is_ a repository that we just happen to not care about,\nand maybe it makes sense for config_with_options() to see a \"struct\nrepository\". On the other, I'm still quite convinced that the \"struct\nrepository\" that we already have just happens to be there by accident\n(because \"struct git_config_source\" is a union of unrelated things), and\nI don't think we should be piggybacking onto that.\n\n> The sequencer.c case is trickier, but presumably for such isolated\n> reading we could have a lower-level function which would return the\n> equivalent of a \"key_value_info\" on errors or whatever.\n\nbundle-uri.c falls into this case of 'read a file in config syntax' too.\nFor this reason, I see at least two layers to the config API:\n\n- Parsing a file in config syntax, i.e. the \"lower level\" API\n- Reading Git-specific config (understanding where config is located,\n  caching it, etc), i.e. the \"higher level\" API\n\nWe have in-tree callers for _both_ of these layers, and I think that's\nappropriate. IOW I don't think we necessarily need to hide the \"lower\nlevel\" API inside of config.c and expose only the \"higher level\" API\nin-tree [1], which was the impression I got from some of your RFC\npatches.\n\nSeparating the layers like this also makes it possible to expose the\n\"lower\" level to out-of-tree callers in a sensible way. To parse a\nfile in a given syntax, a caller shouldn't need to know about\nrepositories and whatnot. That's exactly what this series is trying to\nprepare for, and being principled about the 'config reading cache' is\nessential to get this sort of separation.\n\n> I.e. are we assuming no \"repo\", but per the above we really do have one,\n> but we just don't pass it because we don't have a \"read only the\n> worktree config part\", or whatever?\n\nThis was addressed above.\n\n> Ditto the line number relaying for builtin/config.c, which as my RFC\n> showed we have one or two API users that care, which we can just\n> convert...\n\nbuiltin/config.c is a weird case that I think needs some refactoring,\ne.g. there's\n\n- git config -l, which will list all of the git config (\"higher level\")\n- git config -l -f <file>, which lists config from just a file (\"lower\n  level\")\n\nbut it uses config_with_options() in both cases! It works because\nconfig_with_options() can switch between \"read a subset the git config\"\nand \"read just this file\", but it's pretty gross, and we sometimes get\nit wrong. (see https://lore.kernel.org/git/xmqqzg9kew1q.fsf@gitster.g/\nas an example of how --global is a bit broken).\n\nYour suggestion to convert that (also made upthread, but I can't find\nthe link for some reason...) to something that uses config_set sounds\npretty reasonable [1].\n\n> Anyway, I'm fine with this direction for now, but given the above & my\n> previous RFC\n> https://lore.kernel.org/git/RFC-cover-0.5-00000000000-20230317T042408Z-avarab@gmail.com/\n> I can't help but think we're taking two steps forward & one step\n> backwards for some of this.\n\nThanks. I appreciate the feedback, nevertheless; I think it's bringing\nus closer to a good conclusion.\n\nFWIW I'm working on a followup that will take _many_ steps forward by\nadjusting config_fn_t. I don't know how that will pan out, so I\nappreciate checking in this series, which is at least a marginal\nimprovement over the status quo.\n\n[1] \"struct config_set\" doesn't fall very neatly into the \"lower\" and\n\"higher\" level API discussion. It's useful to be able to read config\ninto some in-memory cache (in-tree and out-of-tree), though that isn't\nas \"low level\" as parsing config (without caching). That will probably\nbe a good follow up to my work to _just_ parse config.\n"}]}