{"thread":{"id":"60017","subject":"[PATCH 0/2] config-parse: create config parsing library","startedAt":"2023-07-20T22:18:15Z","lastAt":"2023-10-27T13:04:27Z","messageCount":49,"participants":["Glen Choo via GitGitGadget","Jonathan Tan","Junio C Hamano","Glen Choo","Josh Steadmon","Taylor Blau"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"479695","messageId":"pull.1551.git.git.1689891436.gitgitgadget@gmail.com","threadId":"60017","inReplyTo":null,"subject":"[PATCH 0/2] config-parse: create config parsing library","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-07-20T22:17:14Z","receivedAt":"2023-07-20T22:18:15Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Config parsing no longer uses global state as of gc/config-context, so the\nnatural next step for libification is to turn that into its own library.\nThis series starts that process by moving config parsing into\nconfig-parse.[c|h] so that other programs can include this functionality\nwithout pulling in all of config.[c|h].\n\nTo use config-parse.[c|h], an external caller has to obey our convention of\nusing \"#include git-compat-util.h\" at the start of the .c file. This is\ndoable by including the in-tree git-compat-util.h and linking against the\nlibgit.a Make target (which is admittedly cumbersome), and we've verified\nthis by compiling and linking to the library using the Google-internal\nversion of Bazel.\n\nThis series is not meant to distract from Calvin's git-std-lib series [1].\nIn fact, the two are complementary: git-std-lib will make it possible for\nexternal callers to compile a smaller subset of files in order to use a\nlibrary. Doing this for config-parse will make config-parse easier to use,\nwhile testing that git-std-lib does what we want it to.\n\nI considered calling the library config-ll (like we do in other parts of the\ncodebase) instead of config-parse, with the intention of adding more \"low\nlevel\" config code to it in the future. A benefit to that is that by having\nfewer modules, dependency management is easier to reason about. However, I\nstruggled to think of what other config code could be considered \"low level\"\nbut doesn't make sense as its own module. (E.g. struct config_set is a low\nlevel implementation detail, but I think it's well-scoped enough to be its\nown config-set module.) I'd appreciate suggestions on how the config\nlibraries could be organized.\n\n[1]\nhttps://lore.kernel.org/git/20230627195251.1973421-1-calvinwan@google.com/\n\nGlen Choo (2):\n  config: return positive from git_config_parse_key()\n  config-parse: split library out of config.[c|h]\n\n Makefile           |   1 +\n builtin/config.c   |   3 +-\n config-parse.c     | 611 +++++++++++++++++++++++++++++++++++++++++++\n config-parse.h     | 182 +++++++++++++\n config.c           | 636 +--------------------------------------------\n config.h           | 146 +----------\n submodule-config.c |   4 +-\n t/t1300-config.sh  |  16 ++\n 8 files changed, 816 insertions(+), 783 deletions(-)\n create mode 100644 config-parse.c\n create mode 100644 config-parse.h\n\n\nbase-commit: aa9166bcc0ba654fc21f198a30647ec087f733ed\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1551%2Fchooglen%2Fconfig%2Fparse-lib-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1551/chooglen/config/parse-lib-v1\nPull-Request: https://github.com/git/git/pull/1551\n-- \ngitgitgadget\n"},{"id":"479696","messageId":"99244816307b822bd8ffcbff8690ef449c797a23.1689891436.git.gitgitgadget@gmail.com","threadId":"60017","inReplyTo":"pull.1551.git.git.1689891436.gitgitgadget@gmail.com","subject":"[PATCH 1/2] config: return positive from git_config_parse_key()","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-07-20T22:17:15Z","receivedAt":"2023-07-20T22:18:17Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\ngit_config_parse_key() returns #define-d error codes, but negated. This\nnegation is merely a convenience to other parts of config.c that don't\nbother inspecting the return value before passing it along. But:\n\na) There's no good reason why those callers couldn't negate the value\n   themselves.\n\nb) In other callers, this value eventually gets fed to exit(3), and\n   those callers need to sanitize the negative value (and they sometimes\n   do so lossily, by overriding the return value with\n   CONFIG_INVALID_KEY).\n\nc) We want to move that into a separate library, and returning only\n   negative values no longer makes as much sense.\n\nChange git_config_parse_key() to return positive values instead, and\nadjust callers accordingly. Callers that sanitize the negative sign for\nexit(3) now pass the return value opaquely, fixing a bug where \"git\nconfig <key with no section or name>\" results in a different exit code\ndepending on whether we are setting or getting config. Callers that\nwanted to pass along a negative value now negate the return value\nthemselves.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/config.c   |  3 +--\n config.c           | 16 ++++++++--------\n config.h           |  2 +-\n submodule-config.c |  4 ++--\n t/t1300-config.sh  | 16 ++++++++++++++++\n 5 files changed, 28 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 1c75cbc43df..8a2840f0a8c 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -362,8 +362,7 @@ static int get_value(const char *key_, const char *regex_, unsigned flags)\n \t\t\tgoto free_strings;\n \t\t}\n \t} else {\n-\t\tif (git_config_parse_key(key_, &key, NULL)) {\n-\t\t\tret = CONFIG_INVALID_KEY;\n+\t\tif ((ret = git_config_parse_key(key_, &key, NULL))) {\n \t\t\tgoto free_strings;\n \t\t}\n \t}\ndiff --git a/config.c b/config.c\nindex 85c5f35132c..ca77ca17a47 100644\n--- a/config.c\n+++ b/config.c\n@@ -534,8 +534,9 @@ static inline int iskeychar(int c)\n  * Auxiliary function to sanity-check and split the key into the section\n  * identifier and variable name.\n  *\n- * Returns 0 on success, -1 when there is an invalid character in the key and\n- * -2 if there is no section name in the key.\n+ * Returns 0 on success, CONFIG_INVALID_KEY when there is an invalid character\n+ * in the key and CONFIG_NO_SECTION_OR_NAME if there is no section name in the\n+ * key.\n  *\n  * store_key - pointer to char* which will hold a copy of the key with\n  *             lowercase section and variable name\n@@ -555,12 +556,12 @@ int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)\n \n \tif (last_dot == NULL || last_dot == key) {\n \t\terror(_(\"key does not contain a section: %s\"), key);\n-\t\treturn -CONFIG_NO_SECTION_OR_NAME;\n+\t\treturn CONFIG_NO_SECTION_OR_NAME;\n \t}\n \n \tif (!last_dot[1]) {\n \t\terror(_(\"key does not contain variable name: %s\"), key);\n-\t\treturn -CONFIG_NO_SECTION_OR_NAME;\n+\t\treturn CONFIG_NO_SECTION_OR_NAME;\n \t}\n \n \tbaselen = last_dot - key;\n@@ -596,7 +597,7 @@ int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)\n \n out_free_ret_1:\n \tFREE_AND_NULL(*store_key);\n-\treturn -CONFIG_INVALID_KEY;\n+\treturn CONFIG_INVALID_KEY;\n }\n \n static int config_parse_pair(const char *key, const char *value,\n@@ -2346,7 +2347,7 @@ static int configset_find_element(struct config_set *set, const char *key,\n \t * `key` may come from the user, so normalize it before using it\n \t * for querying entries from the hashmap.\n \t */\n-\tret = git_config_parse_key(key, &normalized_key, NULL);\n+\tret = -git_config_parse_key(key, &normalized_key, NULL);\n \tif (ret)\n \t\treturn ret;\n \n@@ -3334,8 +3335,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \tsize_t contents_sz;\n \tstruct config_store_data store = CONFIG_STORE_INIT;\n \n-\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+\tret = git_config_parse_key(key, &store.key, &store.baselen);\n \tif (ret)\n \t\tgoto out_free;\n \ndiff --git a/config.h b/config.h\nindex 6332d749047..40966cb6828 100644\n--- a/config.h\n+++ b/config.h\n@@ -23,7 +23,7 @@\n \n struct object_id;\n \n-/* git_config_parse_key() returns these negated: */\n+/* git_config_parse_key() returns these: */\n #define CONFIG_INVALID_KEY 1\n #define CONFIG_NO_SECTION_OR_NAME 2\n /* git_config_set_gently(), git_config_set_multivar_gently() return the above or these: */\ndiff --git a/submodule-config.c b/submodule-config.c\nindex b6908e295f4..2aafc7f9cbe 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -824,8 +824,8 @@ int print_config_from_gitmodules(struct repository *repo, const char *key)\n \tchar *store_key;\n \n \tret = git_config_parse_key(key, &store_key, NULL);\n-\tif (ret < 0)\n-\t\treturn CONFIG_INVALID_KEY;\n+\tif (ret)\n+\t\treturn ret;\n \n \tconfig_from_gitmodules(config_print_callback, repo, store_key);\n \ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 387d336c91f..3202b0f8843 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2590,4 +2590,20 @@ test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such\n \tgrep \"fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url\" err\n '\n \n+# Exit codes\n+test_expect_success '--get with bad key' '\n+\t# Also exits with 1 if the value is not found\n+\ttest_expect_code 1 git config --get \"bad.name\\n\" 2>err &&\n+\tgrep \"error: invalid key\" err &&\n+\ttest_expect_code 2 git config --get \"bad.\" 2>err &&\n+\tgrep \"error: key does not contain variable name\" err\n+'\n+\n+test_expect_success 'set with bad key' '\n+\ttest_expect_code 1 git config \"bad.name\\n\" var 2>err &&\n+\tgrep \"error: invalid key\" err &&\n+\ttest_expect_code 2 git config \"bad.\" var 2>err &&\n+\tgrep \"error: key does not contain variable name\" err\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"479697","messageId":"4461d2163e8fbd0cc3fb7f3f7b101df529420604.1689891436.git.gitgitgadget@gmail.com","threadId":"60017","inReplyTo":"pull.1551.git.git.1689891436.gitgitgadget@gmail.com","subject":"[PATCH 2/2] config-parse: split library out of config.[c|h]","fromName":"Glen Choo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-07-20T22:17:16Z","receivedAt":"2023-07-20T22:18: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\nThe config parsing machinery (besides \"include\" directives) is usable by\nprograms other than Git - it works with works with any file written in\nGit config syntax (IOW it doesn't rely on 'core' Git features like a\nrepository), and as of the series ending at 6e8e7981eb (config: pass\nsource to config_parser_event_fn_t, 2023-06-28), it no longer relies on\nglobal state. Thus, we can and should start turning it into a library\nother programs can use.\n\nBegin this process by splitting the config parsing code out of\nconfig.[c|h] and into config-parse.[c|h]. Do not change interfaces or\nfunction bodies, but tweak visibility and includes where appropriate,\nnamely:\n\n- git_config_from_stdin() is now non-static so that it can be seen by\n  config.c.\n\n- \"struct config_source\" is now defined in the .h file so that it can be\n  seen by config.c. And as a result, config-lib.h needs to \"#include\n  strbuf.h\".\n\nIn theory, this makes it possible for in-tree files to decide whether\nthey only need all of the config functionality or only config parsing,\nand bring in the smallest bit of functionality needed. But for now,\nthere are no in-tree files that can swap \"#include config.h\" for\n\"#include config-parse.h\". E.g. Bundle URIs would only need config\nparsing to parse bundle lists, but bundle-uri.c uses other config.h\nfunctionality like key parsing and reading repo settings.\n\nThe resulting library is usable, though it is unergonomic to do so,\ne.g. the caller needs to \"#include git-compat-util.h\" and other\ndependencies, and we don't have an easy way of linking in the required\nobjects. This isn't the end state we want for our libraries, but at\nleast we have _some_ library whose usability we can improve in future\nseries.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n Makefile       |   1 +\n config-parse.c | 611 +++++++++++++++++++++++++++++++++++++++++++++++\n config-parse.h | 182 ++++++++++++++\n config.c       | 632 -------------------------------------------------\n config.h       | 146 +-----------\n 5 files changed, 795 insertions(+), 777 deletions(-)\n create mode 100644 config-parse.c\n create mode 100644 config-parse.h\n\ndiff --git a/Makefile b/Makefile\nindex fb541dedc9f..67e05bcee57 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -992,6 +992,7 @@ LIB_OBJS += compat/obstack.o\n LIB_OBJS += compat/terminal.o\n LIB_OBJS += compat/zlib-uncompress2.o\n LIB_OBJS += config.o\n+LIB_OBJS += config-parse.o\n LIB_OBJS += connect.o\n LIB_OBJS += connected.o\n LIB_OBJS += convert.o\ndiff --git a/config-parse.c b/config-parse.c\nnew file mode 100644\nindex 00000000000..9f26ca42079\n--- /dev/null\n+++ b/config-parse.c\n@@ -0,0 +1,611 @@\n+#include \"git-compat-util.h\"\n+#include \"strbuf.h\"\n+#include \"gettext.h\"\n+#include \"hashmap.h\"\n+#include \"utf8.h\"\n+#include \"config-parse.h\"\n+\n+static int config_file_fgetc(struct config_source *conf)\n+{\n+\treturn getc_unlocked(conf->u.file);\n+}\n+\n+static int config_file_ungetc(int c, struct config_source *conf)\n+{\n+\treturn ungetc(c, conf->u.file);\n+}\n+\n+static long config_file_ftell(struct config_source *conf)\n+{\n+\treturn ftell(conf->u.file);\n+}\n+\n+\n+static int config_buf_fgetc(struct config_source *conf)\n+{\n+\tif (conf->u.buf.pos < conf->u.buf.len)\n+\t\treturn conf->u.buf.buf[conf->u.buf.pos++];\n+\n+\treturn EOF;\n+}\n+\n+static int config_buf_ungetc(int c, struct config_source *conf)\n+{\n+\tif (conf->u.buf.pos > 0) {\n+\t\tconf->u.buf.pos--;\n+\t\tif (conf->u.buf.buf[conf->u.buf.pos] != c)\n+\t\t\tBUG(\"config_buf can only ungetc the same character\");\n+\t\treturn c;\n+\t}\n+\n+\treturn EOF;\n+}\n+\n+static long config_buf_ftell(struct config_source *conf)\n+{\n+\treturn conf->u.buf.pos;\n+}\n+\n+static inline int iskeychar(int c)\n+{\n+\treturn isalnum(c) || c == '-';\n+}\n+\n+/*\n+ * Auxiliary function to sanity-check and split the key into the section\n+ * identifier and variable name.\n+ *\n+ * Returns 0 on success, CONFIG_INVALID_KEY when there is an invalid character\n+ * in the key and CONFIG_NO_SECTION_OR_NAME if there is no section name in the\n+ * key.\n+ *\n+ * store_key - pointer to char* which will hold a copy of the key with\n+ *             lowercase section and variable name\n+ * baselen - pointer to size_t which will hold the length of the\n+ *           section + subsection part, can be NULL\n+ */\n+int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)\n+{\n+\tsize_t i, baselen;\n+\tint dot;\n+\tconst char *last_dot = strrchr(key, '.');\n+\n+\t/*\n+\t * Since \"key\" actually contains the section name and the real\n+\t * key name separated by a dot, we have to know where the dot is.\n+\t */\n+\n+\tif (last_dot == NULL || last_dot == key) {\n+\t\terror(_(\"key does not contain a section: %s\"), key);\n+\t\treturn CONFIG_NO_SECTION_OR_NAME;\n+\t}\n+\n+\tif (!last_dot[1]) {\n+\t\terror(_(\"key does not contain variable name: %s\"), key);\n+\t\treturn CONFIG_NO_SECTION_OR_NAME;\n+\t}\n+\n+\tbaselen = last_dot - key;\n+\tif (baselen_)\n+\t\t*baselen_ = baselen;\n+\n+\t/*\n+\t * Validate the key and while at it, lower case it for matching.\n+\t */\n+\t*store_key = xmallocz(strlen(key));\n+\n+\tdot = 0;\n+\tfor (i = 0; key[i]; i++) {\n+\t\tunsigned char c = key[i];\n+\t\tif (c == '.')\n+\t\t\tdot = 1;\n+\t\t/* Leave the extended basename untouched.. */\n+\t\tif (!dot || i > baselen) {\n+\t\t\tif (!iskeychar(c) ||\n+\t\t\t    (i == baselen + 1 && !isalpha(c))) {\n+\t\t\t\terror(_(\"invalid key: %s\"), key);\n+\t\t\t\tgoto out_free_ret_1;\n+\t\t\t}\n+\t\t\tc = tolower(c);\n+\t\t} else if (c == '\\n') {\n+\t\t\terror(_(\"invalid key (newline): %s\"), key);\n+\t\t\tgoto out_free_ret_1;\n+\t\t}\n+\t\t(*store_key)[i] = c;\n+\t}\n+\n+\treturn 0;\n+\n+out_free_ret_1:\n+\tFREE_AND_NULL(*store_key);\n+\treturn CONFIG_INVALID_KEY;\n+}\n+\n+static int get_next_char(struct config_source *cs)\n+{\n+\tint c = cs->do_fgetc(cs);\n+\n+\tif (c == '\\r') {\n+\t\t/* DOS like systems */\n+\t\tc = cs->do_fgetc(cs);\n+\t\tif (c != '\\n') {\n+\t\t\tif (c != EOF)\n+\t\t\t\tcs->do_ungetc(c, cs);\n+\t\t\tc = '\\r';\n+\t\t}\n+\t}\n+\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+\t\t * overflows. Note that we can't return an error specifically,\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\treturn 0;\n+\t}\n+\n+\tif (c == '\\n')\n+\t\tcs->linenr++;\n+\tif (c == EOF) {\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 *cs)\n+{\n+\tint quote = 0, comment = 0, space = 0;\n+\n+\tstrbuf_reset(&cs->value);\n+\tfor (;;) {\n+\t\tint c = get_next_char(cs);\n+\t\tif (c == '\\n') {\n+\t\t\tif (quote) {\n+\t\t\t\tcs->linenr--;\n+\t\t\t\treturn NULL;\n+\t\t\t}\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 (cs->value.len)\n+\t\t\t\tspace++;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!quote) {\n+\t\t\tif (c == ';' || c == '#') {\n+\t\t\t\tcomment = 1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\t\tfor (; space; space--)\n+\t\t\tstrbuf_addch(&cs->value, ' ');\n+\t\tif (c == '\\\\') {\n+\t\t\tc = get_next_char(cs);\n+\t\t\tswitch (c) {\n+\t\t\tcase '\\n':\n+\t\t\t\tcontinue;\n+\t\t\tcase 't':\n+\t\t\t\tc = '\\t';\n+\t\t\t\tbreak;\n+\t\t\tcase 'b':\n+\t\t\t\tc = '\\b';\n+\t\t\t\tbreak;\n+\t\t\tcase 'n':\n+\t\t\t\tc = '\\n';\n+\t\t\t\tbreak;\n+\t\t\t/* Some characters escape as themselves */\n+\t\t\tcase '\\\\': case '\"':\n+\t\t\t\tbreak;\n+\t\t\t/* Reject unknown escape sequences */\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\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}\n+}\n+\n+static int get_value(struct config_source *cs, struct key_value_info *kvi,\n+\t\t     config_fn_t fn, void *data, struct strbuf *name)\n+{\n+\tint c;\n+\tchar *value;\n+\tint ret;\n+\tstruct config_context ctx = {\n+\t\t.kvi = kvi,\n+\t};\n+\n+\t/* Get the full name */\n+\tfor (;;) {\n+\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+\t\tstrbuf_addch(name, tolower(c));\n+\t}\n+\n+\twhile (c == ' ' || c == '\\t')\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(cs);\n+\t\tif (!value)\n+\t\t\treturn -1;\n+\t}\n+\t/*\n+\t * We already consumed the \\n, but we need linenr to point to\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+\tkvi->linenr = cs->linenr;\n+\tret = fn(name->buf, value, &ctx, data);\n+\tif (ret >= 0)\n+\t\tcs->linenr++;\n+\treturn ret;\n+}\n+\n+static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n+\t\t\t\t int c)\n+{\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(cs);\n+\t} while (isspace(c));\n+\n+\t/* We require the format to be '[base \"extension\"]' */\n+\tif (c != '\"')\n+\t\treturn -1;\n+\tstrbuf_addch(name, '.');\n+\n+\tfor (;;) {\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(cs);\n+\t\t\tif (c == '\\n')\n+\t\t\t\tgoto error_incomplete_line;\n+\t\t}\n+\t\tstrbuf_addch(name, c);\n+\t}\n+\n+\t/* Final ']' */\n+\tif (get_next_char(cs) != ']')\n+\t\treturn -1;\n+\treturn 0;\n+error_incomplete_line:\n+\tcs->linenr--;\n+\treturn -1;\n+}\n+\n+static int get_base_var(struct config_source *cs, struct strbuf *name)\n+{\n+\tcs->subsection_case_sensitive = 1;\n+\tfor (;;) {\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(cs, name, c);\n+\t\tif (!iskeychar(c) && c != '.')\n+\t\t\treturn -1;\n+\t\tstrbuf_addch(name, tolower(c));\n+\t}\n+}\n+\n+struct parse_event_data {\n+\tenum config_event_t previous_type;\n+\tsize_t previous_offset;\n+\tconst struct config_options *opts;\n+};\n+\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+\tif (!data->opts || !data->opts->event_fn)\n+\t\treturn 0;\n+\n+\tif (type == CONFIG_EVENT_WHITESPACE &&\n+\t    data->previous_type == type)\n+\t\treturn 0;\n+\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+\t * we will already have advanced to the next event.\n+\t */\n+\tif (type != CONFIG_EVENT_EOF)\n+\t\toffset--;\n+\n+\tif (data->previous_type != CONFIG_EVENT_EOF &&\n+\t    data->opts->event_fn(data->previous_type, data->previous_offset,\n+\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n+\t\treturn -1;\n+\n+\tdata->previous_type = type;\n+\tdata->previous_offset = offset;\n+\n+\treturn 0;\n+}\n+\n+static void kvi_from_source(struct config_source *cs,\n+\t\t\t    enum config_scope scope,\n+\t\t\t    struct key_value_info *out)\n+{\n+\tout->filename = strintern(cs->name);\n+\tout->origin_type = cs->origin_type;\n+\tout->linenr = cs->linenr;\n+\tout->scope = scope;\n+\tout->path = cs->path;\n+}\n+\n+static int git_parse_source(struct config_source *cs, config_fn_t fn,\n+\t\t\t    struct key_value_info *kvi, void *data,\n+\t\t\t    const struct config_options *opts)\n+{\n+\tint comment = 0;\n+\tsize_t baselen = 0;\n+\tstruct strbuf *var = &cs->var;\n+\tint error_return = 0;\n+\tchar *error_msg = NULL;\n+\n+\t/* U+FEFF Byte Order Mark in UTF8 */\n+\tconst char *bomptr = utf8_bom;\n+\n+\t/* For the parser event callback */\n+\tstruct parse_event_data event_data = {\n+\t\tCONFIG_EVENT_EOF, 0, opts\n+\t};\n+\n+\tfor (;;) {\n+\t\tint c;\n+\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+\t\t\t * own, but e.g. Windows Notepad will do it happily. */\n+\t\t\tif (c == (*bomptr & 0377)) {\n+\t\t\t\tbomptr++;\n+\t\t\t\tcontinue;\n+\t\t\t} else {\n+\t\t\t\t/* Do not tolerate partial BOM. */\n+\t\t\t\tif (bomptr != utf8_bom)\n+\t\t\t\t\tbreak;\n+\t\t\t\t/* No BOM at file beginning. Cool. */\n+\t\t\t\tbomptr = NULL;\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\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\t\treturn -1;\n+\t\t\tcomment = 0;\n+\t\t\tcontinue;\n+\t\t}\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\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\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\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\t\tbreak;\n+\t\t\tstrbuf_addch(var, '.');\n+\t\t\tbaselen = var->len;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!isalpha(c))\n+\t\t\tbreak;\n+\n+\t\tif (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)\n+\t\t\treturn -1;\n+\n+\t\t/*\n+\t\t * Truncate the var name back to the section header\n+\t\t * stem prior to grabbing the suffix part of the name\n+\t\t * and the value.\n+\t\t */\n+\t\tstrbuf_setlen(var, baselen);\n+\t\tstrbuf_addch(var, tolower(c));\n+\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n+\t\t\tbreak;\n+\t}\n+\n+\tif (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)\n+\t\treturn -1;\n+\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      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      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      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       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       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      cs->linenr, cs->name);\n+\t}\n+\n+\tswitch (opts && opts->error_action ?\n+\t\topts->error_action :\n+\t\tcs->default_error_action) {\n+\tcase CONFIG_ERROR_DIE:\n+\t\tdie(\"%s\", error_msg);\n+\t\tbreak;\n+\tcase CONFIG_ERROR_ERROR:\n+\t\terror_return = error(\"%s\", error_msg);\n+\t\tbreak;\n+\tcase CONFIG_ERROR_SILENT:\n+\t\terror_return = -1;\n+\t\tbreak;\n+\tcase CONFIG_ERROR_UNSET:\n+\t\tBUG(\"config error action unset\");\n+\t}\n+\n+\tfree(error_msg);\n+\treturn error_return;\n+}\n+\n+/*\n+ * All source specific fields in the union, die_on_error, name and the callbacks\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,\n+\t\t\t  void *data, enum config_scope scope,\n+\t\t\t  const struct config_options *opts)\n+{\n+\tstruct key_value_info kvi = KVI_INIT;\n+\tint ret;\n+\n+\t/* push config-file parsing state stack */\n+\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+\tkvi_from_source(top, scope, &kvi);\n+\n+\tret = git_parse_source(top, fn, &kvi, data, opts);\n+\n+\tstrbuf_release(&top->value);\n+\tstrbuf_release(&top->var);\n+\n+\treturn ret;\n+}\n+\n+static int do_config_from_file(config_fn_t fn,\n+\t\t\t       const enum config_origin_type origin_type,\n+\t\t\t       const char *name, const char *path, FILE *f,\n+\t\t\t       void *data, enum config_scope scope,\n+\t\t\t       const struct config_options *opts)\n+{\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n+\tint ret;\n+\n+\ttop.u.file = f;\n+\ttop.origin_type = origin_type;\n+\ttop.name = name;\n+\ttop.path = path;\n+\ttop.default_error_action = CONFIG_ERROR_DIE;\n+\ttop.do_fgetc = config_file_fgetc;\n+\ttop.do_ungetc = config_file_ungetc;\n+\ttop.do_ftell = config_file_ftell;\n+\n+\tflockfile(f);\n+\tret = do_config_from(&top, fn, data, scope, opts);\n+\tfunlockfile(f);\n+\treturn ret;\n+}\n+\n+int git_config_from_stdin(config_fn_t fn, void *data,\n+\t\t\t  enum config_scope scope)\n+{\n+\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n+\t\t\t\t   data, scope, NULL);\n+}\n+\n+int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n+\t\t\t\t      void *data, enum config_scope scope,\n+\t\t\t\t      const struct config_options *opts)\n+{\n+\tint ret = -1;\n+\tFILE *f;\n+\n+\tif (!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, scope, opts);\n+\t\tfclose(f);\n+\t}\n+\treturn ret;\n+}\n+\n+int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n+{\n+\treturn git_config_from_file_with_options(fn, filename, data,\n+\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, NULL);\n+}\n+\n+int git_config_from_mem(config_fn_t fn,\n+\t\t\tconst enum config_origin_type origin_type,\n+\t\t\tconst char *name, const char *buf, size_t len,\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_options *opts)\n+{\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n+\n+\ttop.u.buf.buf = buf;\n+\ttop.u.buf.len = len;\n+\ttop.u.buf.pos = 0;\n+\ttop.origin_type = origin_type;\n+\ttop.name = name;\n+\ttop.path = NULL;\n+\ttop.default_error_action = CONFIG_ERROR_ERROR;\n+\ttop.do_fgetc = config_buf_fgetc;\n+\ttop.do_ungetc = config_buf_ungetc;\n+\ttop.do_ftell = config_buf_ftell;\n+\n+\treturn do_config_from(&top, fn, data, scope, opts);\n+}\ndiff --git a/config-parse.h b/config-parse.h\nnew file mode 100644\nindex 00000000000..d6ee88583d8\n--- /dev/null\n+++ b/config-parse.h\n@@ -0,0 +1,182 @@\n+/*\n+ * Low level config parsing.\n+ */\n+#ifndef CONFIG_LIB_H\n+#define CONFIG_LIB_H\n+\n+#include \"strbuf.h\"\n+\n+/* git_config_parse_key() returns these: */\n+#define CONFIG_INVALID_KEY 1\n+#define CONFIG_NO_SECTION_OR_NAME 2\n+\n+int git_config_parse_key(const char *, char **, size_t *);\n+\n+enum config_scope {\n+\tCONFIG_SCOPE_UNKNOWN = 0,\n+\tCONFIG_SCOPE_SYSTEM,\n+\tCONFIG_SCOPE_GLOBAL,\n+\tCONFIG_SCOPE_LOCAL,\n+\tCONFIG_SCOPE_WORKTREE,\n+\tCONFIG_SCOPE_COMMAND,\n+\tCONFIG_SCOPE_SUBMODULE,\n+};\n+const char *config_scope_name(enum config_scope scope);\n+\n+enum config_origin_type {\n+\tCONFIG_ORIGIN_UNKNOWN = 0,\n+\tCONFIG_ORIGIN_BLOB,\n+\tCONFIG_ORIGIN_FILE,\n+\tCONFIG_ORIGIN_STDIN,\n+\tCONFIG_ORIGIN_SUBMODULE_BLOB,\n+\tCONFIG_ORIGIN_CMDLINE\n+};\n+\n+enum config_event_t {\n+\tCONFIG_EVENT_SECTION,\n+\tCONFIG_EVENT_ENTRY,\n+\tCONFIG_EVENT_WHITESPACE,\n+\tCONFIG_EVENT_COMMENT,\n+\tCONFIG_EVENT_EOF,\n+\tCONFIG_EVENT_ERROR\n+};\n+\n+struct config_source;\n+/*\n+ * The parser event function (if not NULL) is called with the event type and\n+ * the begin/end offsets of the parsed elements.\n+ *\n+ * Note: for CONFIG_EVENT_ENTRY (i.e. config variables), the trailing newline\n+ * character is considered part of the element.\n+ */\n+typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n+\t\t\t\t\tsize_t begin_offset, size_t end_offset,\n+\t\t\t\t\tstruct config_source *cs,\n+\t\t\t\t\tvoid *event_fn_data);\n+\n+struct config_options {\n+\tunsigned int respect_includes : 1;\n+\tunsigned int ignore_repo : 1;\n+\tunsigned int ignore_worktree : 1;\n+\tunsigned int ignore_cmdline : 1;\n+\tunsigned int system_gently : 1;\n+\n+\t/*\n+\t * For internal use. Include all includeif.hasremoteurl paths without\n+\t * checking if the repo has that remote URL, and when doing so, verify\n+\t * that files included in this way do not configure any remote URLs\n+\t * themselves.\n+\t */\n+\tunsigned int unconditional_remote_url : 1;\n+\n+\tconst char *commondir;\n+\tconst char *git_dir;\n+\t/*\n+\t * event_fn and event_fn_data are for internal use only. Handles events\n+\t * emitted by the config parser.\n+\t */\n+\tconfig_parser_event_fn_t event_fn;\n+\tvoid *event_fn_data;\n+\tenum config_error_action {\n+\t\tCONFIG_ERROR_UNSET = 0, /* use source-specific default */\n+\t\tCONFIG_ERROR_DIE, /* die() on error */\n+\t\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n+\t\tCONFIG_ERROR_SILENT, /* return -1 */\n+\t} error_action;\n+};\n+\n+struct config_source {\n+\tstruct config_source *prev;\n+\tunion {\n+\t\tFILE *file;\n+\t\tstruct config_buf {\n+\t\t\tconst char *buf;\n+\t\t\tsize_t len;\n+\t\t\tsize_t pos;\n+\t\t} buf;\n+\t} u;\n+\tenum config_origin_type origin_type;\n+\tconst char *name;\n+\tconst char *path;\n+\tenum config_error_action default_error_action;\n+\tint linenr;\n+\tint eof;\n+\tsize_t total_len;\n+\tstruct strbuf value;\n+\tstruct strbuf var;\n+\tunsigned subsection_case_sensitive : 1;\n+\n+\tint (*do_fgetc)(struct config_source *c);\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+/* Config source metadata for a given config key-value pair */\n+struct key_value_info {\n+\tconst char *filename;\n+\tint linenr;\n+\tenum config_origin_type origin_type;\n+\tenum config_scope scope;\n+\tconst char *path;\n+};\n+#define KVI_INIT { \\\n+\t.filename = NULL, \\\n+\t.linenr = -1, \\\n+\t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n+\t.scope = CONFIG_SCOPE_UNKNOWN, \\\n+\t.path = NULL, \\\n+}\n+\n+/* Captures additional information that a config callback can use. */\n+struct config_context {\n+\t/* Config source metadata for key and value. */\n+\tconst struct key_value_info *kvi;\n+};\n+#define CONFIG_CONTEXT_INIT { 0 }\n+\n+/**\n+ * A config callback function takes four parameters:\n+ *\n+ * - the name of the parsed variable. This is in canonical \"flat\" form: the\n+ *   section, subsection, and variable segments will be separated by dots,\n+ *   and the section and variable segments will be all lowercase. E.g.,\n+ *   `core.ignorecase`, `diff.SomeType.textconv`.\n+ *\n+ * - the value of the found variable, as a string. If the variable had no\n+ *   value specified, the value will be NULL (typically this means it\n+ *   should be interpreted as boolean true).\n+ *\n+ * - the 'config context', that is, additional information about the config\n+ *   iteration operation provided by the config machinery. For example, this\n+ *   includes information about the config source being parsed (e.g. the\n+ *   filename).\n+ *\n+ * - a void pointer passed in by the caller of the config API; this can\n+ *   contain callback-specific data\n+ *\n+ * A config callback should return 0 for success, or -1 if the variable\n+ * could not be parsed properly.\n+ */\n+typedef int (*config_fn_t)(const char *, const char *,\n+\t\t\t   const struct config_context *, void *);\n+\n+/**\n+ * Read a specific file in git-config format.\n+ */\n+int git_config_from_file(config_fn_t fn, const char *, void *);\n+\n+int git_config_from_file_with_options(config_fn_t fn, const char *,\n+\t\t\t\t      void *, enum config_scope,\n+\t\t\t\t      const struct config_options *);\n+\n+int git_config_from_mem(config_fn_t fn,\n+\t\t\tconst enum config_origin_type,\n+\t\t\tconst char *name,\n+\t\t\tconst char *buf, size_t len,\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_options *opts);\n+\n+int git_config_from_stdin(config_fn_t fn, void *data, enum config_scope scope);\n+\n+#endif /* CONFIG_LIB_H */\ndiff --git a/config.c b/config.c\nindex ca77ca17a47..b575dfd7ba0 100644\n--- a/config.c\n+++ b/config.c\n@@ -42,33 +42,6 @@\n #include \"wrapper.h\"\n #include \"write-or-die.h\"\n \n-struct config_source {\n-\tstruct config_source *prev;\n-\tunion {\n-\t\tFILE *file;\n-\t\tstruct config_buf {\n-\t\t\tconst char *buf;\n-\t\t\tsize_t len;\n-\t\t\tsize_t pos;\n-\t\t} buf;\n-\t} u;\n-\tenum config_origin_type origin_type;\n-\tconst char *name;\n-\tconst char *path;\n-\tenum config_error_action default_error_action;\n-\tint linenr;\n-\tint eof;\n-\tsize_t total_len;\n-\tstruct strbuf value;\n-\tstruct strbuf var;\n-\tunsigned subsection_case_sensitive : 1;\n-\n-\tint (*do_fgetc)(struct config_source *c);\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 static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -83,47 +56,6 @@ static int zlib_compression_seen;\n  */\n static struct config_set protected_config;\n \n-static int config_file_fgetc(struct config_source *conf)\n-{\n-\treturn getc_unlocked(conf->u.file);\n-}\n-\n-static int config_file_ungetc(int c, struct config_source *conf)\n-{\n-\treturn ungetc(c, conf->u.file);\n-}\n-\n-static long config_file_ftell(struct config_source *conf)\n-{\n-\treturn ftell(conf->u.file);\n-}\n-\n-\n-static int config_buf_fgetc(struct config_source *conf)\n-{\n-\tif (conf->u.buf.pos < conf->u.buf.len)\n-\t\treturn conf->u.buf.buf[conf->u.buf.pos++];\n-\n-\treturn EOF;\n-}\n-\n-static int config_buf_ungetc(int c, struct config_source *conf)\n-{\n-\tif (conf->u.buf.pos > 0) {\n-\t\tconf->u.buf.pos--;\n-\t\tif (conf->u.buf.buf[conf->u.buf.pos] != c)\n-\t\t\tBUG(\"config_buf can only ungetc the same character\");\n-\t\treturn c;\n-\t}\n-\n-\treturn EOF;\n-}\n-\n-static long config_buf_ftell(struct config_source *conf)\n-{\n-\treturn conf->u.buf.pos;\n-}\n-\n struct config_include_data {\n \tint depth;\n \tconfig_fn_t fn;\n@@ -525,81 +457,6 @@ void git_config_push_env(const char *spec)\n \tfree(key);\n }\n \n-static inline int iskeychar(int c)\n-{\n-\treturn isalnum(c) || c == '-';\n-}\n-\n-/*\n- * Auxiliary function to sanity-check and split the key into the section\n- * identifier and variable name.\n- *\n- * Returns 0 on success, CONFIG_INVALID_KEY when there is an invalid character\n- * in the key and CONFIG_NO_SECTION_OR_NAME if there is no section name in the\n- * key.\n- *\n- * store_key - pointer to char* which will hold a copy of the key with\n- *             lowercase section and variable name\n- * baselen - pointer to size_t which will hold the length of the\n- *           section + subsection part, can be NULL\n- */\n-int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)\n-{\n-\tsize_t i, baselen;\n-\tint dot;\n-\tconst char *last_dot = strrchr(key, '.');\n-\n-\t/*\n-\t * Since \"key\" actually contains the section name and the real\n-\t * key name separated by a dot, we have to know where the dot is.\n-\t */\n-\n-\tif (last_dot == NULL || last_dot == key) {\n-\t\terror(_(\"key does not contain a section: %s\"), key);\n-\t\treturn CONFIG_NO_SECTION_OR_NAME;\n-\t}\n-\n-\tif (!last_dot[1]) {\n-\t\terror(_(\"key does not contain variable name: %s\"), key);\n-\t\treturn CONFIG_NO_SECTION_OR_NAME;\n-\t}\n-\n-\tbaselen = last_dot - key;\n-\tif (baselen_)\n-\t\t*baselen_ = baselen;\n-\n-\t/*\n-\t * Validate the key and while at it, lower case it for matching.\n-\t */\n-\t*store_key = xmallocz(strlen(key));\n-\n-\tdot = 0;\n-\tfor (i = 0; key[i]; i++) {\n-\t\tunsigned char c = key[i];\n-\t\tif (c == '.')\n-\t\t\tdot = 1;\n-\t\t/* Leave the extended basename untouched.. */\n-\t\tif (!dot || i > baselen) {\n-\t\t\tif (!iskeychar(c) ||\n-\t\t\t    (i == baselen + 1 && !isalpha(c))) {\n-\t\t\t\terror(_(\"invalid key: %s\"), key);\n-\t\t\t\tgoto out_free_ret_1;\n-\t\t\t}\n-\t\t\tc = tolower(c);\n-\t\t} else if (c == '\\n') {\n-\t\t\terror(_(\"invalid key (newline): %s\"), key);\n-\t\t\tgoto out_free_ret_1;\n-\t\t}\n-\t\t(*store_key)[i] = c;\n-\t}\n-\n-\treturn 0;\n-\n-out_free_ret_1:\n-\tFREE_AND_NULL(*store_key);\n-\treturn CONFIG_INVALID_KEY;\n-}\n-\n static int config_parse_pair(const char *key, const char *value,\n \t\t\t     struct key_value_info *kvi,\n \t\t\t     config_fn_t fn, void *data)\n@@ -784,390 +641,6 @@ out:\n \treturn ret;\n }\n \n-static int get_next_char(struct config_source *cs)\n-{\n-\tint c = cs->do_fgetc(cs);\n-\n-\tif (c == '\\r') {\n-\t\t/* DOS like systems */\n-\t\tc = cs->do_fgetc(cs);\n-\t\tif (c != '\\n') {\n-\t\t\tif (c != EOF)\n-\t\t\t\tcs->do_ungetc(c, cs);\n-\t\t\tc = '\\r';\n-\t\t}\n-\t}\n-\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-\t\t * overflows. Note that we can't return an error specifically,\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\treturn 0;\n-\t}\n-\n-\tif (c == '\\n')\n-\t\tcs->linenr++;\n-\tif (c == EOF) {\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 *cs)\n-{\n-\tint quote = 0, comment = 0, space = 0;\n-\n-\tstrbuf_reset(&cs->value);\n-\tfor (;;) {\n-\t\tint c = get_next_char(cs);\n-\t\tif (c == '\\n') {\n-\t\t\tif (quote) {\n-\t\t\t\tcs->linenr--;\n-\t\t\t\treturn NULL;\n-\t\t\t}\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 (cs->value.len)\n-\t\t\t\tspace++;\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (!quote) {\n-\t\t\tif (c == ';' || c == '#') {\n-\t\t\t\tcomment = 1;\n-\t\t\t\tcontinue;\n-\t\t\t}\n-\t\t}\n-\t\tfor (; space; space--)\n-\t\t\tstrbuf_addch(&cs->value, ' ');\n-\t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char(cs);\n-\t\t\tswitch (c) {\n-\t\t\tcase '\\n':\n-\t\t\t\tcontinue;\n-\t\t\tcase 't':\n-\t\t\t\tc = '\\t';\n-\t\t\t\tbreak;\n-\t\t\tcase 'b':\n-\t\t\t\tc = '\\b';\n-\t\t\t\tbreak;\n-\t\t\tcase 'n':\n-\t\t\t\tc = '\\n';\n-\t\t\t\tbreak;\n-\t\t\t/* Some characters escape as themselves */\n-\t\t\tcase '\\\\': case '\"':\n-\t\t\t\tbreak;\n-\t\t\t/* Reject unknown escape sequences */\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\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}\n-}\n-\n-static int get_value(struct config_source *cs, struct key_value_info *kvi,\n-\t\t     config_fn_t fn, void *data, struct strbuf *name)\n-{\n-\tint c;\n-\tchar *value;\n-\tint ret;\n-\tstruct config_context ctx = {\n-\t\t.kvi = kvi,\n-\t};\n-\n-\t/* Get the full name */\n-\tfor (;;) {\n-\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-\t\tstrbuf_addch(name, tolower(c));\n-\t}\n-\n-\twhile (c == ' ' || c == '\\t')\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(cs);\n-\t\tif (!value)\n-\t\t\treturn -1;\n-\t}\n-\t/*\n-\t * We already consumed the \\n, but we need linenr to point to\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-\tkvi->linenr = cs->linenr;\n-\tret = fn(name->buf, value, &ctx, data);\n-\tif (ret >= 0)\n-\t\tcs->linenr++;\n-\treturn ret;\n-}\n-\n-static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n-\t\t\t\t int c)\n-{\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(cs);\n-\t} while (isspace(c));\n-\n-\t/* We require the format to be '[base \"extension\"]' */\n-\tif (c != '\"')\n-\t\treturn -1;\n-\tstrbuf_addch(name, '.');\n-\n-\tfor (;;) {\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(cs);\n-\t\t\tif (c == '\\n')\n-\t\t\t\tgoto error_incomplete_line;\n-\t\t}\n-\t\tstrbuf_addch(name, c);\n-\t}\n-\n-\t/* Final ']' */\n-\tif (get_next_char(cs) != ']')\n-\t\treturn -1;\n-\treturn 0;\n-error_incomplete_line:\n-\tcs->linenr--;\n-\treturn -1;\n-}\n-\n-static int get_base_var(struct config_source *cs, struct strbuf *name)\n-{\n-\tcs->subsection_case_sensitive = 1;\n-\tfor (;;) {\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(cs, name, c);\n-\t\tif (!iskeychar(c) && c != '.')\n-\t\t\treturn -1;\n-\t\tstrbuf_addch(name, tolower(c));\n-\t}\n-}\n-\n-struct parse_event_data {\n-\tenum config_event_t previous_type;\n-\tsize_t previous_offset;\n-\tconst struct config_options *opts;\n-};\n-\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-\tif (!data->opts || !data->opts->event_fn)\n-\t\treturn 0;\n-\n-\tif (type == CONFIG_EVENT_WHITESPACE &&\n-\t    data->previous_type == type)\n-\t\treturn 0;\n-\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-\t * we will already have advanced to the next event.\n-\t */\n-\tif (type != CONFIG_EVENT_EOF)\n-\t\toffset--;\n-\n-\tif (data->previous_type != CONFIG_EVENT_EOF &&\n-\t    data->opts->event_fn(data->previous_type, data->previous_offset,\n-\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n-\t\treturn -1;\n-\n-\tdata->previous_type = type;\n-\tdata->previous_offset = offset;\n-\n-\treturn 0;\n-}\n-\n-static void kvi_from_source(struct config_source *cs,\n-\t\t\t    enum config_scope scope,\n-\t\t\t    struct key_value_info *out)\n-{\n-\tout->filename = strintern(cs->name);\n-\tout->origin_type = cs->origin_type;\n-\tout->linenr = cs->linenr;\n-\tout->scope = scope;\n-\tout->path = cs->path;\n-}\n-\n-static int git_parse_source(struct config_source *cs, config_fn_t fn,\n-\t\t\t    struct key_value_info *kvi, void *data,\n-\t\t\t    const struct config_options *opts)\n-{\n-\tint comment = 0;\n-\tsize_t baselen = 0;\n-\tstruct strbuf *var = &cs->var;\n-\tint error_return = 0;\n-\tchar *error_msg = NULL;\n-\n-\t/* U+FEFF Byte Order Mark in UTF8 */\n-\tconst char *bomptr = utf8_bom;\n-\n-\t/* For the parser event callback */\n-\tstruct parse_event_data event_data = {\n-\t\tCONFIG_EVENT_EOF, 0, opts\n-\t};\n-\n-\tfor (;;) {\n-\t\tint c;\n-\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-\t\t\t * own, but e.g. Windows Notepad will do it happily. */\n-\t\t\tif (c == (*bomptr & 0377)) {\n-\t\t\t\tbomptr++;\n-\t\t\t\tcontinue;\n-\t\t\t} else {\n-\t\t\t\t/* Do not tolerate partial BOM. */\n-\t\t\t\tif (bomptr != utf8_bom)\n-\t\t\t\t\tbreak;\n-\t\t\t\t/* No BOM at file beginning. Cool. */\n-\t\t\t\tbomptr = NULL;\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\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\t\treturn -1;\n-\t\t\tcomment = 0;\n-\t\t\tcontinue;\n-\t\t}\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\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\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\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\t\tbreak;\n-\t\t\tstrbuf_addch(var, '.');\n-\t\t\tbaselen = var->len;\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (!isalpha(c))\n-\t\t\tbreak;\n-\n-\t\tif (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)\n-\t\t\treturn -1;\n-\n-\t\t/*\n-\t\t * Truncate the var name back to the section header\n-\t\t * stem prior to grabbing the suffix part of the name\n-\t\t * and the value.\n-\t\t */\n-\t\tstrbuf_setlen(var, baselen);\n-\t\tstrbuf_addch(var, tolower(c));\n-\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n-\t\t\tbreak;\n-\t}\n-\n-\tif (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)\n-\t\treturn -1;\n-\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      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      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      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       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       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      cs->linenr, cs->name);\n-\t}\n-\n-\tswitch (opts && opts->error_action ?\n-\t\topts->error_action :\n-\t\tcs->default_error_action) {\n-\tcase CONFIG_ERROR_DIE:\n-\t\tdie(\"%s\", error_msg);\n-\t\tbreak;\n-\tcase CONFIG_ERROR_ERROR:\n-\t\terror_return = error(\"%s\", error_msg);\n-\t\tbreak;\n-\tcase CONFIG_ERROR_SILENT:\n-\t\terror_return = -1;\n-\t\tbreak;\n-\tcase CONFIG_ERROR_UNSET:\n-\t\tBUG(\"config error action unset\");\n-\t}\n-\n-\tfree(error_msg);\n-\treturn error_return;\n-}\n-\n static uintmax_t get_unit_factor(const char *end)\n {\n \tif (!*end)\n@@ -1961,111 +1434,6 @@ int git_default_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-/*\n- * All source specific fields in the union, die_on_error, name and the callbacks\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,\n-\t\t\t  void *data, enum config_scope scope,\n-\t\t\t  const struct config_options *opts)\n-{\n-\tstruct key_value_info kvi = KVI_INIT;\n-\tint ret;\n-\n-\t/* push config-file parsing state stack */\n-\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-\tkvi_from_source(top, scope, &kvi);\n-\n-\tret = git_parse_source(top, fn, &kvi, data, opts);\n-\n-\tstrbuf_release(&top->value);\n-\tstrbuf_release(&top->var);\n-\n-\treturn ret;\n-}\n-\n-static int do_config_from_file(config_fn_t fn,\n-\t\t\t       const enum config_origin_type origin_type,\n-\t\t\t       const char *name, const char *path, FILE *f,\n-\t\t\t       void *data, enum config_scope scope,\n-\t\t\t       const struct config_options *opts)\n-{\n-\tstruct config_source top = CONFIG_SOURCE_INIT;\n-\tint ret;\n-\n-\ttop.u.file = f;\n-\ttop.origin_type = origin_type;\n-\ttop.name = name;\n-\ttop.path = path;\n-\ttop.default_error_action = CONFIG_ERROR_DIE;\n-\ttop.do_fgetc = config_file_fgetc;\n-\ttop.do_ungetc = config_file_ungetc;\n-\ttop.do_ftell = config_file_ftell;\n-\n-\tflockfile(f);\n-\tret = do_config_from(&top, fn, data, scope, opts);\n-\tfunlockfile(f);\n-\treturn ret;\n-}\n-\n-static int git_config_from_stdin(config_fn_t fn, void *data,\n-\t\t\t\t enum config_scope scope)\n-{\n-\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n-\t\t\t\t   data, scope, NULL);\n-}\n-\n-int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n-\t\t\t\t      void *data, enum config_scope scope,\n-\t\t\t\t      const struct config_options *opts)\n-{\n-\tint ret = -1;\n-\tFILE *f;\n-\n-\tif (!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, scope, opts);\n-\t\tfclose(f);\n-\t}\n-\treturn ret;\n-}\n-\n-int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n-{\n-\treturn git_config_from_file_with_options(fn, filename, data,\n-\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, NULL);\n-}\n-\n-int git_config_from_mem(config_fn_t fn,\n-\t\t\tconst enum config_origin_type origin_type,\n-\t\t\tconst char *name, const char *buf, size_t len,\n-\t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_options *opts)\n-{\n-\tstruct config_source top = CONFIG_SOURCE_INIT;\n-\n-\ttop.u.buf.buf = buf;\n-\ttop.u.buf.len = len;\n-\ttop.u.buf.pos = 0;\n-\ttop.origin_type = origin_type;\n-\ttop.name = name;\n-\ttop.path = NULL;\n-\ttop.default_error_action = CONFIG_ERROR_ERROR;\n-\ttop.do_fgetc = config_buf_fgetc;\n-\ttop.do_ungetc = config_buf_ungetc;\n-\ttop.do_ftell = config_buf_ftell;\n-\n-\treturn do_config_from(&top, fn, data, scope, opts);\n-}\n-\n int git_config_from_blob_oid(config_fn_t fn,\n \t\t\t      const char *name,\n \t\t\t      struct repository *repo,\ndiff --git a/config.h b/config.h\nindex 40966cb6828..1a7dcc24208 100644\n--- a/config.h\n+++ b/config.h\n@@ -4,7 +4,7 @@\n #include \"hashmap.h\"\n #include \"string-list.h\"\n #include \"repository.h\"\n-\n+#include \"config-parse.h\"\n \n /**\n  * The config API gives callers a way to access Git configuration files\n@@ -23,9 +23,6 @@\n \n struct object_id;\n \n-/* git_config_parse_key() returns these: */\n-#define CONFIG_INVALID_KEY 1\n-#define CONFIG_NO_SECTION_OR_NAME 2\n /* git_config_set_gently(), git_config_set_multivar_gently() return the above or these: */\n #define CONFIG_NO_LOCK -1\n #define CONFIG_INVALID_FILE 3\n@@ -36,17 +33,6 @@ struct object_id;\n \n #define CONFIG_REGEX_NONE ((void *)1)\n \n-enum config_scope {\n-\tCONFIG_SCOPE_UNKNOWN = 0,\n-\tCONFIG_SCOPE_SYSTEM,\n-\tCONFIG_SCOPE_GLOBAL,\n-\tCONFIG_SCOPE_LOCAL,\n-\tCONFIG_SCOPE_WORKTREE,\n-\tCONFIG_SCOPE_COMMAND,\n-\tCONFIG_SCOPE_SUBMODULE,\n-};\n-const char *config_scope_name(enum config_scope scope);\n-\n struct git_config_source {\n \tunsigned int use_stdin:1;\n \tconst char *file;\n@@ -54,137 +40,9 @@ struct git_config_source {\n \tenum config_scope scope;\n };\n \n-enum config_origin_type {\n-\tCONFIG_ORIGIN_UNKNOWN = 0,\n-\tCONFIG_ORIGIN_BLOB,\n-\tCONFIG_ORIGIN_FILE,\n-\tCONFIG_ORIGIN_STDIN,\n-\tCONFIG_ORIGIN_SUBMODULE_BLOB,\n-\tCONFIG_ORIGIN_CMDLINE\n-};\n-\n-enum config_event_t {\n-\tCONFIG_EVENT_SECTION,\n-\tCONFIG_EVENT_ENTRY,\n-\tCONFIG_EVENT_WHITESPACE,\n-\tCONFIG_EVENT_COMMENT,\n-\tCONFIG_EVENT_EOF,\n-\tCONFIG_EVENT_ERROR\n-};\n-\n-struct config_source;\n-/*\n- * The parser event function (if not NULL) is called with the event type and\n- * the begin/end offsets of the parsed elements.\n- *\n- * Note: for CONFIG_EVENT_ENTRY (i.e. config variables), the trailing newline\n- * character is considered part of the element.\n- */\n-typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n-\t\t\t\t\tsize_t begin_offset, size_t end_offset,\n-\t\t\t\t\tstruct config_source *cs,\n-\t\t\t\t\tvoid *event_fn_data);\n-\n-struct config_options {\n-\tunsigned int respect_includes : 1;\n-\tunsigned int ignore_repo : 1;\n-\tunsigned int ignore_worktree : 1;\n-\tunsigned int ignore_cmdline : 1;\n-\tunsigned int system_gently : 1;\n-\n-\t/*\n-\t * For internal use. Include all includeif.hasremoteurl paths without\n-\t * checking if the repo has that remote URL, and when doing so, verify\n-\t * that files included in this way do not configure any remote URLs\n-\t * themselves.\n-\t */\n-\tunsigned int unconditional_remote_url : 1;\n-\n-\tconst char *commondir;\n-\tconst char *git_dir;\n-\t/*\n-\t * event_fn and event_fn_data are for internal use only. Handles events\n-\t * emitted by the config parser.\n-\t */\n-\tconfig_parser_event_fn_t event_fn;\n-\tvoid *event_fn_data;\n-\tenum config_error_action {\n-\t\tCONFIG_ERROR_UNSET = 0, /* use source-specific default */\n-\t\tCONFIG_ERROR_DIE, /* die() on error */\n-\t\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n-\t\tCONFIG_ERROR_SILENT, /* return -1 */\n-\t} error_action;\n-};\n-\n-/* Config source metadata for a given config key-value pair */\n-struct key_value_info {\n-\tconst char *filename;\n-\tint linenr;\n-\tenum config_origin_type origin_type;\n-\tenum config_scope scope;\n-\tconst char *path;\n-};\n-#define KVI_INIT { \\\n-\t.filename = NULL, \\\n-\t.linenr = -1, \\\n-\t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n-\t.scope = CONFIG_SCOPE_UNKNOWN, \\\n-\t.path = NULL, \\\n-}\n-\n-/* Captures additional information that a config callback can use. */\n-struct config_context {\n-\t/* Config source metadata for key and value. */\n-\tconst struct key_value_info *kvi;\n-};\n-#define CONFIG_CONTEXT_INIT { 0 }\n-\n-/**\n- * A config callback function takes four parameters:\n- *\n- * - the name of the parsed variable. This is in canonical \"flat\" form: the\n- *   section, subsection, and variable segments will be separated by dots,\n- *   and the section and variable segments will be all lowercase. E.g.,\n- *   `core.ignorecase`, `diff.SomeType.textconv`.\n- *\n- * - the value of the found variable, as a string. If the variable had no\n- *   value specified, the value will be NULL (typically this means it\n- *   should be interpreted as boolean true).\n- *\n- * - the 'config context', that is, additional information about the config\n- *   iteration operation provided by the config machinery. For example, this\n- *   includes information about the config source being parsed (e.g. the\n- *   filename).\n- *\n- * - a void pointer passed in by the caller of the config API; this can\n- *   contain callback-specific data\n- *\n- * A config callback should return 0 for success, or -1 if the variable\n- * could not be parsed properly.\n- */\n-typedef int (*config_fn_t)(const char *, const char *,\n-\t\t\t   const struct config_context *, void *);\n-\n int git_default_config(const char *, const char *,\n \t\t       const struct config_context *, void *);\n \n-/**\n- * Read a specific file in git-config format.\n- * This function takes the same callback and data parameters as `git_config`.\n- *\n- * Unlike git_config(), this function does not respect includes.\n- */\n-int git_config_from_file(config_fn_t fn, const char *, void *);\n-\n-int git_config_from_file_with_options(config_fn_t fn, const char *,\n-\t\t\t\t      void *, enum config_scope,\n-\t\t\t\t      const struct config_options *);\n-int git_config_from_mem(config_fn_t fn,\n-\t\t\tconst enum config_origin_type,\n-\t\t\tconst char *name,\n-\t\t\tconst char *buf, size_t len,\n-\t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_options *opts);\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     struct repository *repo,\n \t\t\t     const struct object_id *oid, void *data,\n@@ -322,8 +180,6 @@ int repo_config_set_worktree_gently(struct repository *, const char *, const cha\n  */\n void git_config_set(const char *, const char *);\n \n-int git_config_parse_key(const char *, char **, size_t *);\n-\n /*\n  * The following macros specify flag bits that alter the behavior\n  * of the git_config_set_multivar*() methods.\n-- \ngitgitgadget\n"},{"id":"479701","messageId":"20230720234450.3087841-1-jonathantanmy@google.com","threadId":"60017","inReplyTo":"99244816307b822bd8ffcbff8690ef449c797a23.1689891436.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/2] config: return positive from git_config_parse_key()","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-07-20T23:44:49Z","receivedAt":"2023-07-20T23:44:56Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> From: Glen Choo <chooglen@google.com>\n> \n> git_config_parse_key() returns #define-d error codes, but negated. This\n> negation is merely a convenience to other parts of config.c that don't\n> bother inspecting the return value before passing it along. But:\n> \n> a) There's no good reason why those callers couldn't negate the value\n>    themselves.\n> \n> b) In other callers, this value eventually gets fed to exit(3), and\n>    those callers need to sanitize the negative value (and they sometimes\n>    do so lossily, by overriding the return value with\n>    CONFIG_INVALID_KEY).\n> \n> c) We want to move that into a separate library, and returning only\n>    negative values no longer makes as much sense.\n\nI'm not sure if we ever concluded that functions returning errors should\nreturn positive integers, but in this case I think it makes sense. We\ncan document what's returned as being the same as what's documented in\nthe config manpage.\n\nThe negative return was as early as when the function was first\nintroduced in b09c53a3e3 (Sanity-check config variable names, 2011-01-\n30), but there's no indication there as to why the author chose negative\nvalues.\n\n> Change git_config_parse_key() to return positive values instead, and\n> adjust callers accordingly. Callers that sanitize the negative sign for\n> exit(3) now pass the return value opaquely, fixing a bug where \"git\n> config <key with no section or name>\" results in a different exit code\n> depending on whether we are setting or getting config.\n\nCan you be more precise as to which bug is being fixed? (I think\nsomewhere, a 1 is returned when it should be a 2.)\n\n> Callers that\n> wanted to pass along a negative value now negate the return value\n> themselves.\n\nOK.\n\n> diff --git a/builtin/config.c b/builtin/config.c\n> index 1c75cbc43df..8a2840f0a8c 100644\n> --- a/builtin/config.c\n> +++ b/builtin/config.c\n> @@ -362,8 +362,7 @@ static int get_value(const char *key_, const char *regex_, unsigned flags)\n>  \t\t\tgoto free_strings;\n>  \t\t}\n>  \t} else {\n> -\t\tif (git_config_parse_key(key_, &key, NULL)) {\n> -\t\t\tret = CONFIG_INVALID_KEY;\n> +\t\tif ((ret = git_config_parse_key(key_, &key, NULL))) {\n>  \t\t\tgoto free_strings;\n>  \t\t}\n>  \t}\n\nAh, here, the return value was sanitized in such a way that it lost\ninformation. The change makes sense.\n\nBesides the callers modified in this patch, there is another caller\nconfig_parse_pair() in config.c, but that just checks whether the return\nvalue is 0, so it remaining unmodified is fine.\n\n> diff --git a/config.h b/config.h\n> index 6332d749047..40966cb6828 100644\n> --- a/config.h\n> +++ b/config.h\n> @@ -23,7 +23,7 @@\n>  \n>  struct object_id;\n>  \n> -/* git_config_parse_key() returns these negated: */\n> +/* git_config_parse_key() returns these: */\n>  #define CONFIG_INVALID_KEY 1\n>  #define CONFIG_NO_SECTION_OR_NAME 2\n\nShould these be turned into an enum? Also, it might be worth adding that\nthese match the return values as documented in the manpage.\n\n> diff --git a/t/t1300-config.sh b/t/t1300-config.sh\n> index 387d336c91f..3202b0f8843 100755\n> --- a/t/t1300-config.sh\n> +++ b/t/t1300-config.sh\n> @@ -2590,4 +2590,20 @@ test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such\n>  \tgrep \"fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url\" err\n>  '\n>  \n> +# Exit codes\n> +test_expect_success '--get with bad key' '\n\nRather than put an \"exit codes\" title, maybe embed that in the test\ndescription.\n\n> +\t# Also exits with 1 if the value is not found\n\nI don't understand this comment - what's the difference between a bad\nkey and a value not being found? And if there's a difference, could we\ntest both?\n\n> +\ttest_expect_code 1 git config --get \"bad.name\\n\" 2>err &&\n> +\tgrep \"error: invalid key\" err &&\n> +\ttest_expect_code 2 git config --get \"bad.\" 2>err &&\n> +\tgrep \"error: key does not contain variable name\" err\n> +'\n> +\n> +test_expect_success 'set with bad key' '\n> +\ttest_expect_code 1 git config \"bad.name\\n\" var 2>err &&\n> +\tgrep \"error: invalid key\" err &&\n> +\ttest_expect_code 2 git config \"bad.\" var 2>err &&\n> +\tgrep \"error: key does not contain variable name\" err\n> +'\n\nMakes sense.\n\nFrom a libification perspective, I'm not sure that using positive values\nto indicate error is an advantage over negative values, but it makes\nsense in this particular context to have the return values match the\nmanpage exactly, since that is part of the benefit of this function. So\nI think this patch is worth getting in by itself.\n"},{"id":"479702","messageId":"20230721003107.3095493-1-jonathantanmy@google.com","threadId":"60017","inReplyTo":"4461d2163e8fbd0cc3fb7f3f7b101df529420604.1689891436.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/2] config-parse: split library out of config.[c|h]","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-07-21T00:31:07Z","receivedAt":"2023-07-21T00:31:13Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> Begin this process by splitting the config parsing code out of\n> config.[c|h] and into config-parse.[c|h].\n\nI think we need to be more careful in how we split. It would be easier\nif there is a concrete use case, but some preliminary questions:\n\n - \"respect_includes\" is in the library, but callers might want to opt\n   out of it or provide an alternative way to resolve includes.\n - There is a lot of error reporting capability with respect to the\n   source of config, and not all sources are applicable to library\n   users. How should we proceed? E.g. should we expect that all library\n   users conform to the list here (e.g. even if the source is something\n   like but not exactly STDIN, they should pick it), or allow users to\n   customize sources?\n\nIn the absence of more information, the split I would envision is\neither something that can only parse a buffer, its error messages being\nvery generic (the caller should turn them into something more specific\nbefore showing them to the user) (but one problem here is that we must\npostprocess includes, which might be a problem if the output of parsing\nis a flat hashtable, since we wouldn't know which keys are overridden\nby the includes and which are not); or something that can take in a\ncallback that is invoked whenever something is included and maybe also\na callback for access to the object database and that has full knowledge\nof all sources for error reporting (or allows the caller to customize\nthe sources).\n"},{"id":"479706","messageId":"xmqq4jlx28c6.fsf@gitster.g","threadId":"60017","inReplyTo":"99244816307b822bd8ffcbff8690ef449c797a23.1689891436.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/2] config: return positive from git_config_parse_key()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-07-21T04:32:09Z","receivedAt":"2023-07-21T04:32:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Glen Choo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Glen Choo <chooglen@google.com>\n>\n> git_config_parse_key() returns #define-d error codes, but negated. This\n> negation is merely a convenience to other parts of config.c that don't\n> bother inspecting the return value before passing it along. But:\n>\n> a) There's no good reason why those callers couldn't negate the value\n>    themselves.\n\nThat is not a good reason to break from a widely adopted convention\nin UNIXy library functions to signal success with 0 and failure with\nnegative values.  The callers if they want to have a positive values\ncan flip the polarity themselves, by the way.\n\n>\n> b) In other callers, this value eventually gets fed to exit(3), and\n>    those callers need to sanitize the negative value (and they sometimes\n>    do so lossily, by overriding the return value with\n>    CONFIG_INVALID_KEY).\n\nThere is no reason to think that each and every minute difference\nthe direct callers of a library function may want to notice by\ndifferent error return values needs to be propagated to the calling\nprocess via its exit value.  It is perfectly fine and expected for\nthe status values of the entire process is more coarse grained than\nindividual library calls, the latter may convey not just \"I failed\"\nbut \"I failed why\" to their callers, while the former may not want\nto say \"I made a call to some library functions and got this error\ncode\", let alone \"I called library function X and got error code Y\".\n\nIn other words, if your program does\n\n\terr = library_call_about_some_filesystem_operation();\n\tif (err)\n\t\texit(err); /* or exit(-err) */\n\terr = library_call_about_some_database_operation();\n\tif (err)\n\t\texit(err); /* or exit(-err) */\n\terr = library_call_about_some_parsing();\n\tif (err)\n\t\texit(err); /* or exit(-err) */\n\nthere is something wrong.  The error codes from these different\nlibrary functions share the same \"integer\" namespace without being\nsegregated, and expecting the calling process to be able to tell\nwhat error we discovered by relaying the literal translation of low\nlevel library error code would not work.  The exit() codes would\nneed to be wider (i.e. not limited only to the possible errors from\na single library function) and would be coarser (i.e. a filesystem\noperation may say \"open failed due to permission error\" or \"open\nfailed because there was no such file\"; at the end-user level, it\nmay be more appropriate to say \"configuration file could not be\nread\", regardless of the reason why the filesystem operation\nfailed).\n\n\terr = library_call_about_some_filesystem_operation();\n\tif (err) {\n\t\terror(\"cannot open the filesystem entity\");\n\t\texit(ERR_FILESYSTEM);\n\terr = library_call_about_some_database_operation();\n\tif (err)\n\t\texit(ERR_DATABASE);\n\terr = library_call_about_some_parsing();\n\tif (err)\n\t\texit(ERR_PARSING);\n\nSo, this is not a good reason, either.\n\n> c) We want to move that into a separate library, and returning only\n>    negative values no longer makes as much sense.\n\nQuite the contrary, if it were a purely internal convention, we may\nnot care too much, as we have only ourselves to confuse by picking\nan unusual convention, but if we are making more parts of our code\navailable in a library-ish interface, I would expect they follow\nsome convention, and that convention would be \"0 for success,\nnegative for failure\".\n\nThanks.\n"},{"id":"479742","messageId":"kl6lwmytz2cf.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"60017","inReplyTo":"20230721003107.3095493-1-jonathantanmy@google.com","subject":"Re: [PATCH 2/2] config-parse: split library out of config.[c|h]","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-07-21T15:55:12Z","receivedAt":"2023-07-21T15:55:38Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n>> Begin this process by splitting the config parsing code out of\n>> config.[c|h] and into config-parse.[c|h].\n>\n> I think we need to be more careful in how we split. It would be easier\n> if there is a concrete use case, but some preliminary questions:\n>\n>  - \"respect_includes\" is in the library, but callers might want to opt\n>    out of it or provide an alternative way to resolve includes.\n\nMakes sense. Or alternatively, we could choose not to support\n\"respect_includes\" initially, and exclude it to avoid confusion.\n\n>  - There is a lot of error reporting capability with respect to the\n>    source of config, and not all sources are applicable to library\n>    users. How should we proceed? E.g. should we expect that all library\n>    users conform to the list here (e.g. even if the source is something\n>    like but not exactly STDIN, they should pick it), or allow users to\n>    customize sources?\n\nGood point. I would also prefer to have the list of sources constrained\nto the list of sources available via the library. Some possibilities I\ncan see are:\n\n1. Move the Git-program-specific error message reporting to a level above\n   the library (i.e. config.c).\n\n2. Proceed as-is (with the additional sources in the library) and leave a\n   FIXME to address this when we find a Git library-idiomatic way to\n   handle errors. This won't be the last time we'll have to untangle\n   Git-program-specific error reporting from the library - it might be\n   useful to try to figure out all of that in one fell swoop.\n\n3. Figure out library-idiomatic error handling mentioned in 2. right\n   now.\n\nI think 1. is the best option, but if that fails, 2. is also reasonable.\n3. is too difficult to do with a sample size of 1.\n\n> In the absence of more information, the split I would envision is\n> either something that can only parse a buffer, its error messages being\n> very generic (the caller should turn them into something more specific\n> before showing them to the user) (but one problem here is that we must\n> postprocess includes, which might be a problem if the output of parsing\n> is a flat hashtable, since we wouldn't know which keys are overridden\n> by the includes and which are not);\n\nHm, how does the include mechanism here this differ from what's in this\npatch? This also only parses a single file and ignores includes. I'm not\nsure why this requires us to postprocess includes - in config.c,\nincludes are handled by 'pausing' parsing of the current source,\nevaluating the included files, then 'resuming' parsing.\n\n> or something that can take in a\n> callback that is invoked whenever something is included and maybe also\n> a callback for access to the object database and that has full knowledge\n> of all sources for error reporting (or allows the caller to customize\n> the sources).\n\nAh, I like this callback idea quite a lot. This lets config-parse.c\neasily support unconditional includes and provides entry points for\nprogram-specific behavior (like checking the odb). I will try this.\n"},{"id":"479744","messageId":"kl6ltttxz1jj.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"60017","inReplyTo":"xmqq4jlx28c6.fsf@gitster.g","subject":"Re: [PATCH 1/2] config: return positive from git_config_parse_key()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-07-21T16:12:32Z","receivedAt":"2023-07-21T16:13:22Z","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>> From: Glen Choo <chooglen@google.com>\n>>\n>> git_config_parse_key() returns #define-d error codes, but negated. This\n>> negation is merely a convenience to other parts of config.c that don't\n>> bother inspecting the return value before passing it along. But:\n>>\n>> a) There's no good reason why those callers couldn't negate the value\n>>    themselves.\n>\n> That is not a good reason to break from a widely adopted convention\n> in UNIXy library functions to signal success with 0 and failure with\n> negative values.  The callers if they want to have a positive values\n> can flip the polarity themselves, by the way.\n\nOh, interesting. I was trying to follow the conventions of the\nsurrounding config.c code and many other parts of the codebase, which\nreturns positive values. Why do we choose to return postive values\nthroughout the codebase, by the way? Is it because they were really\nintended for exit(3), and not to be used as a library.\n\n>>\n>> b) In other callers, this value eventually gets fed to exit(3), and\n>>    those callers need to sanitize the negative value (and they sometimes\n>>    do so lossily, by overriding the return value with\n>>    CONFIG_INVALID_KEY).\n>\n> There is no reason to think that each and every minute difference\n> the direct callers of a library function may want to notice by\n> different error return values needs to be propagated to the calling\n> process via its exit value.\n\nYes, I fully agree. I didn't intend to be a statement on how things\nshould be, but rather how they already are. The oddities in this case\nare:\n\n- No callers actually care about the sign of git_config_parse_key()\n  since it can only return values of one sign. Only\n  configset_find_element() benefits from the sign being negative (since\n  it returns negative on config key errors), but instead of putting the\n  burden on the function it depends on, it could just return the\n  negative value itself. And this \"burden\" is real, in that other\n  callers have to worry about the negative value.\n\n- For better or worse, we've already wired git_config_parse_key()\n  directly to the exit values, e.g. if one peeks into\n  builtin/config.c:cmd_config(), we'll see \"return\n  git_config_set_multivar_in_file_gently()\", which in turn may return\n  the value from git_config_parse_key(). (And as a result, we also try\n  hard to separate the error values returnable by git_config_parse_key()\n  vs the others returnable by git_config_set_multivar_in_file_gently().)\n\n  I would strongly prefer if builtin/config.c took more responsibility\n  for noticing config.c errors and sanitizing them accordingly, but it\n  seemed like too much churn for this series. If you think it is the\n  right time for it, though (which I think it might be), I could try to\n  make that change.\n\n>> c) We want to move that into a separate library, and returning only\n>>    negative values no longer makes as much sense.\n>\n> Quite the contrary, if it were a purely internal convention, we may\n> not care too much, as we have only ourselves to confuse by picking\n> an unusual convention, but if we are making more parts of our code\n> available in a library-ish interface, I would expect they follow\n> some convention, and that convention would be \"0 for success,\n> negative for failure\".\n\nRight. I do not care what the convention is, only that we pick one. I\nchose the one that I saw in the surrounding code (positive), but I'm\nhappy to go with UNIXy (negative) if others think it is worth the churn.\n"},{"id":"479746","messageId":"xmqqa5vpw7b7.fsf@gitster.g","threadId":"60017","inReplyTo":"kl6ltttxz1jj.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH 1/2] config: return positive from git_config_parse_key()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-07-21T16:36:12Z","receivedAt":"2023-07-21T16:38:55Z","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> Oh, interesting. I was trying to follow the conventions of the\n> surrounding config.c code and many other parts of the codebase, which\n> returns positive values. Why do we choose to return postive values\n> throughout the codebase, by the way? Is it because they were really\n> intended for exit(3), and not to be used as a library.\n\nIf config.c does that, I'd say that was poorly designed oddball.\nLooking at read-cache.c (which is older parts of the codebase\nwritten back when the developer base was smaller) may give you a\nbetter examples to follow.  After all, error() returns negative\nexactly because we want to follow the usual \"negative is an error\"\nconvention.\n"},{"id":"480027","messageId":"20230731234910.94149-1-chooglen@google.com","threadId":"60017","inReplyTo":"pull.1551.git.git.1689891436.gitgitgadget@gmail.com","subject":"[RFC PATCH v1.5 0/5] config-parse: create config parsing library","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-07-31T23:46:37Z","receivedAt":"2023-07-31T23:49:47Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"I'll be leaving Google soon, so I won't be pushing this series through to\ncompletion :/ As such, here's an rfc-quality 'reroll' to demonstrate how I\nintended to respond to the comments on v1 2/2 that discussed where the library\nboundary should be [1]. In particular:\n\n> - \"respect_includes\" is in the library, but callers might want to opt\n>   out of it or provide an alternative way to resolve includes.\n\nI've punted on this altogether by removing \"respect_includes\" from the options\naccepted by the library functions. As 2/5 describes, config_options is quite\nbloated, so this split already makes sense even without config-parse.\n\nInitially, I considered turning the \"includes\" machinery into a first-class\ncitizen in config-parse instead of having it implemented as a separate config\ncallback, but it turned out to be much more complicated than it first appears.\nThe biggest challenge is that as \"hasconfig:remote.*url\" performs the additional\npass over the config files, it performs different actions - from the starting\nfile, it collects remote urls, then in files that could be imported via\n\"hasconfig:remote.*url\", it forbid remote urls. This 'action switching' didn't\ntranslate well into the lower level machinery, so I opted to leave this for\nlater.\n\n> - There is a lot of error reporting capability with respect to the\n>   source of config, and not all sources are applicable to library\n>   users. How should we proceed? E.g. should we expect that all library\n>   users conform to the list here (e.g. even if the source is something\n>   like but not exactly STDIN, they should pick it), or allow users to\n>   customize sources?\n\nI've opted to move the error reporting to a callback function (3/5), so in-tree\ncallers will use the existing logic, but other callers can provide their own\nerror handling. I've reused the config 'event listeners' for this purpose since\nthey seemed pretty well-suited to the task - they already respond to errors, and\nthe in-tree error handling doesn't do anything that an event listener can't\nexpress.\n\nAs a result, the library now excludes functions that use the in-tree error\nhandling; we either teach the function to accept event listeners or exclude it\naltogether.\n\n[1] https://lore.kernel.org/git/20230721003107.3095493-1-jonathantanmy@google.com\n\nGlen Choo (5):\n  config: return positive from git_config_parse_key()\n  config: split out config_parse_options\n  config: report config parse errors using cb\n  config.c: accept config_parse_options in git_config_from_stdin\n  config-parse: split library out of config.[c|h]\n\n Makefile           |   1 +\n builtin/config.c   |   7 +-\n bundle-uri.c       |   4 +-\n config-parse.c     | 561 ++++++++++++++++++++++++++++++++++++++\n config-parse.h     | 155 +++++++++++\n config.c           | 660 ++++-----------------------------------------\n config.h           | 134 +--------\n fsck.c             |   4 +-\n submodule-config.c |  13 +-\n t/t1300-config.sh  |  16 ++\n 10 files changed, 816 insertions(+), 739 deletions(-)\n create mode 100644 config-parse.c\n create mode 100644 config-parse.h\n\nRange-diff:\n1:  9924481630 = 1:  9924481630 config: return positive from git_config_parse_key()\n-:  ---------- > 2:  8807be8319 config: split out config_parse_options\n-:  ---------- > 3:  3d4a2357f3 config: report config parse errors using cb\n-:  ---------- > 4:  ff03ee1de7 config.c: accept config_parse_options in git_config_from_stdin\n2:  4461d2163e ! 5:  377acbfbb5 config-parse: split library out of config.[c|h]\n    @@ config-parse.c (new)\n     +struct parse_event_data {\n     +\tenum config_event_t previous_type;\n     +\tsize_t previous_offset;\n    -+\tconst struct config_options *opts;\n    ++\tconst struct config_parse_options *opts;\n     +};\n     +\n     +static int do_event(struct config_source *cs, enum config_event_t type,\n    @@ config-parse.c (new)\n     +\n     +static int git_parse_source(struct config_source *cs, config_fn_t fn,\n     +\t\t\t    struct key_value_info *kvi, void *data,\n    -+\t\t\t    const struct config_options *opts)\n    ++\t\t\t    const struct config_parse_options *opts)\n     +{\n     +\tint comment = 0;\n     +\tsize_t baselen = 0;\n     +\tstruct strbuf *var = &cs->var;\n    -+\tint error_return = 0;\n    -+\tchar *error_msg = NULL;\n     +\n     +\t/* U+FEFF Byte Order Mark in UTF8 */\n     +\tconst char *bomptr = utf8_bom;\n    @@ config-parse.c (new)\n     +\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n     +\t\t\tbreak;\n     +\t}\n    -+\n    -+\tif (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)\n    -+\t\treturn -1;\n    -+\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      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      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      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       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       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      cs->linenr, cs->name);\n    -+\t}\n    -+\n    -+\tswitch (opts && opts->error_action ?\n    -+\t\topts->error_action :\n    -+\t\tcs->default_error_action) {\n    -+\tcase CONFIG_ERROR_DIE:\n    -+\t\tdie(\"%s\", error_msg);\n    -+\t\tbreak;\n    -+\tcase CONFIG_ERROR_ERROR:\n    -+\t\terror_return = error(\"%s\", error_msg);\n    -+\t\tbreak;\n    -+\tcase CONFIG_ERROR_SILENT:\n    -+\t\terror_return = -1;\n    -+\t\tbreak;\n    -+\tcase CONFIG_ERROR_UNSET:\n    -+\t\tBUG(\"config error action unset\");\n    -+\t}\n    -+\n    -+\tfree(error_msg);\n    -+\treturn error_return;\n    ++\t/*\n    ++\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n    ++\t * in order for our callback to receive the error event, we have to call\n    ++\t * do_event twice\n    ++\t */\n    ++\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n    ++\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n    ++\treturn -1;\n     +}\n     +\n     +/*\n    @@ config-parse.c (new)\n     + */\n     +static int do_config_from(struct config_source *top, config_fn_t fn,\n     +\t\t\t  void *data, enum config_scope scope,\n    -+\t\t\t  const struct config_options *opts)\n    ++\t\t\t  const struct config_parse_options *opts)\n     +{\n     +\tstruct key_value_info kvi = KVI_INIT;\n     +\tint ret;\n    @@ config-parse.c (new)\n     +\t\t\t       const enum config_origin_type origin_type,\n     +\t\t\t       const char *name, const char *path, FILE *f,\n     +\t\t\t       void *data, enum config_scope scope,\n    -+\t\t\t       const struct config_options *opts)\n    ++\t\t\t       const struct config_parse_options *opts)\n     +{\n     +\tstruct config_source top = CONFIG_SOURCE_INIT;\n     +\tint ret;\n    @@ config-parse.c (new)\n     +\ttop.origin_type = origin_type;\n     +\ttop.name = name;\n     +\ttop.path = path;\n    -+\ttop.default_error_action = CONFIG_ERROR_DIE;\n     +\ttop.do_fgetc = config_file_fgetc;\n     +\ttop.do_ungetc = config_file_ungetc;\n     +\ttop.do_ftell = config_file_ftell;\n    @@ config-parse.c (new)\n     +\treturn ret;\n     +}\n     +\n    -+int git_config_from_stdin(config_fn_t fn, void *data,\n    -+\t\t\t  enum config_scope scope)\n    ++int git_config_from_stdin(config_fn_t fn, void *data, enum config_scope scope,\n    ++\t\t\t  const struct config_parse_options *config_opts)\n     +{\n     +\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n    -+\t\t\t\t   data, scope, NULL);\n    ++\t\t\t\t   data, scope, config_opts);\n     +}\n     +\n     +int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n     +\t\t\t\t      void *data, enum config_scope scope,\n    -+\t\t\t\t      const struct config_options *opts)\n    ++\t\t\t\t      const struct config_parse_options *opts)\n     +{\n     +\tint ret = -1;\n     +\tFILE *f;\n    @@ config-parse.c (new)\n     +\treturn ret;\n     +}\n     +\n    -+int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n    -+{\n    -+\treturn git_config_from_file_with_options(fn, filename, data,\n    -+\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, NULL);\n    -+}\n    -+\n     +int git_config_from_mem(config_fn_t fn,\n     +\t\t\tconst enum config_origin_type origin_type,\n     +\t\t\tconst char *name, const char *buf, size_t len,\n     +\t\t\tvoid *data, enum config_scope scope,\n    -+\t\t\tconst struct config_options *opts)\n    ++\t\t\tconst struct config_parse_options *opts)\n     +{\n     +\tstruct config_source top = CONFIG_SOURCE_INIT;\n     +\n    @@ config-parse.c (new)\n     +\ttop.origin_type = origin_type;\n     +\ttop.name = name;\n     +\ttop.path = NULL;\n    -+\ttop.default_error_action = CONFIG_ERROR_ERROR;\n     +\ttop.do_fgetc = config_buf_fgetc;\n     +\ttop.do_ungetc = config_buf_ungetc;\n     +\ttop.do_ftell = config_buf_ftell;\n    @@ config-parse.h (new)\n     +/*\n     + * Low level config parsing.\n     + */\n    -+#ifndef CONFIG_LIB_H\n    -+#define CONFIG_LIB_H\n    ++#ifndef CONFIG_PARSE_H\n    ++#define CONFIG_PARSE_H\n     +\n     +#include \"strbuf.h\"\n     +\n    @@ config-parse.h (new)\n     +\t\t\t\t\tstruct config_source *cs,\n     +\t\t\t\t\tvoid *event_fn_data);\n     +\n    -+struct config_options {\n    -+\tunsigned int respect_includes : 1;\n    -+\tunsigned int ignore_repo : 1;\n    -+\tunsigned int ignore_worktree : 1;\n    -+\tunsigned int ignore_cmdline : 1;\n    -+\tunsigned int system_gently : 1;\n    -+\n    -+\t/*\n    -+\t * For internal use. Include all includeif.hasremoteurl paths without\n    -+\t * checking if the repo has that remote URL, and when doing so, verify\n    -+\t * that files included in this way do not configure any remote URLs\n    -+\t * themselves.\n    -+\t */\n    -+\tunsigned int unconditional_remote_url : 1;\n    -+\n    -+\tconst char *commondir;\n    -+\tconst char *git_dir;\n    ++struct config_parse_options {\n     +\t/*\n     +\t * event_fn and event_fn_data are for internal use only. Handles events\n     +\t * emitted by the config parser.\n     +\t */\n     +\tconfig_parser_event_fn_t event_fn;\n     +\tvoid *event_fn_data;\n    -+\tenum config_error_action {\n    -+\t\tCONFIG_ERROR_UNSET = 0, /* use source-specific default */\n    -+\t\tCONFIG_ERROR_DIE, /* die() on error */\n    -+\t\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n    -+\t\tCONFIG_ERROR_SILENT, /* return -1 */\n    -+\t} error_action;\n     +};\n     +\n     +struct config_source {\n    @@ config-parse.h (new)\n     +\tenum config_origin_type origin_type;\n     +\tconst char *name;\n     +\tconst char *path;\n    -+\tenum config_error_action default_error_action;\n     +\tint linenr;\n     +\tint eof;\n     +\tsize_t total_len;\n    @@ config-parse.h (new)\n     +typedef int (*config_fn_t)(const char *, const char *,\n     +\t\t\t   const struct config_context *, void *);\n     +\n    -+/**\n    -+ * Read a specific file in git-config format.\n    -+ */\n    -+int git_config_from_file(config_fn_t fn, const char *, void *);\n    -+\n     +int git_config_from_file_with_options(config_fn_t fn, const char *,\n     +\t\t\t\t      void *, enum config_scope,\n    -+\t\t\t\t      const struct config_options *);\n    ++\t\t\t\t      const struct config_parse_options *);\n     +\n     +int git_config_from_mem(config_fn_t fn,\n     +\t\t\tconst enum config_origin_type,\n     +\t\t\tconst char *name,\n     +\t\t\tconst char *buf, size_t len,\n     +\t\t\tvoid *data, enum config_scope scope,\n    -+\t\t\tconst struct config_options *opts);\n    ++\t\t\tconst struct config_parse_options *opts);\n     +\n    -+int git_config_from_stdin(config_fn_t fn, void *data, enum config_scope scope);\n    ++int git_config_from_stdin(config_fn_t fn, void *data, enum config_scope scope,\n    ++\t\t\t  const struct config_parse_options *config_opts);\n     +\n    -+#endif /* CONFIG_LIB_H */\n    ++#endif /* CONFIG_PARSE_H */\n     \n      ## config.c ##\n     @@\n    @@ config.c\n     -\tenum config_origin_type origin_type;\n     -\tconst char *name;\n     -\tconst char *path;\n    --\tenum config_error_action default_error_action;\n     -\tint linenr;\n     -\tint eof;\n     -\tsize_t total_len;\n    @@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)\n     -struct parse_event_data {\n     -\tenum config_event_t previous_type;\n     -\tsize_t previous_offset;\n    --\tconst struct config_options *opts;\n    +-\tconst struct config_parse_options *opts;\n     -};\n     -\n     -static int do_event(struct config_source *cs, enum config_event_t type,\n    @@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)\n     -\tout->path = cs->path;\n     -}\n     -\n    + int git_config_err_fn(enum config_event_t type, size_t begin_offset UNUSED,\n    + \t\t      size_t end_offset UNUSED, struct config_source *cs,\n    + \t\t      void *data)\n    +@@ config.c: int git_config_err_fn(enum config_event_t type, size_t begin_offset UNUSED,\n    + \treturn error_return;\n    + }\n    + \n     -static int git_parse_source(struct config_source *cs, config_fn_t fn,\n     -\t\t\t    struct key_value_info *kvi, void *data,\n    --\t\t\t    const struct config_options *opts)\n    +-\t\t\t    const struct config_parse_options *opts)\n     -{\n     -\tint comment = 0;\n     -\tsize_t baselen = 0;\n     -\tstruct strbuf *var = &cs->var;\n    --\tint error_return = 0;\n    --\tchar *error_msg = NULL;\n     -\n     -\t/* U+FEFF Byte Order Mark in UTF8 */\n     -\tconst char *bomptr = utf8_bom;\n    @@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)\n     -\t\t\tbreak;\n     -\t}\n     -\n    --\tif (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)\n    --\t\treturn -1;\n    --\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      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      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      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       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       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      cs->linenr, cs->name);\n    --\t}\n    --\n    --\tswitch (opts && opts->error_action ?\n    --\t\topts->error_action :\n    --\t\tcs->default_error_action) {\n    --\tcase CONFIG_ERROR_DIE:\n    --\t\tdie(\"%s\", error_msg);\n    --\t\tbreak;\n    --\tcase CONFIG_ERROR_ERROR:\n    --\t\terror_return = error(\"%s\", error_msg);\n    --\t\tbreak;\n    --\tcase CONFIG_ERROR_SILENT:\n    --\t\terror_return = -1;\n    --\t\tbreak;\n    --\tcase CONFIG_ERROR_UNSET:\n    --\t\tBUG(\"config error action unset\");\n    --\t}\n    --\n    --\tfree(error_msg);\n    --\treturn error_return;\n    +-\t/*\n    +-\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n    +-\t * in order for our callback to receive the error event, we have to call\n    +-\t * do_event twice\n    +-\t */\n    +-\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n    +-\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n    +-\treturn -1;\n     -}\n     -\n      static uintmax_t get_unit_factor(const char *end)\n    @@ config.c: int git_default_config(const char *var, const char *value,\n     - */\n     -static int do_config_from(struct config_source *top, config_fn_t fn,\n     -\t\t\t  void *data, enum config_scope scope,\n    --\t\t\t  const struct config_options *opts)\n    +-\t\t\t  const struct config_parse_options *opts)\n     -{\n     -\tstruct key_value_info kvi = KVI_INIT;\n     -\tint ret;\n    @@ config.c: int git_default_config(const char *var, const char *value,\n     -\t\t\t       const enum config_origin_type origin_type,\n     -\t\t\t       const char *name, const char *path, FILE *f,\n     -\t\t\t       void *data, enum config_scope scope,\n    --\t\t\t       const struct config_options *opts)\n    +-\t\t\t       const struct config_parse_options *opts)\n     -{\n     -\tstruct config_source top = CONFIG_SOURCE_INIT;\n     -\tint ret;\n    @@ config.c: int git_default_config(const char *var, const char *value,\n     -\ttop.origin_type = origin_type;\n     -\ttop.name = name;\n     -\ttop.path = path;\n    --\ttop.default_error_action = CONFIG_ERROR_DIE;\n     -\ttop.do_fgetc = config_file_fgetc;\n     -\ttop.do_ungetc = config_file_ungetc;\n     -\ttop.do_ftell = config_file_ftell;\n    @@ config.c: int git_default_config(const char *var, const char *value,\n     -}\n     -\n     -static int git_config_from_stdin(config_fn_t fn, void *data,\n    --\t\t\t\t enum config_scope scope)\n    +-\t\t\t\t enum config_scope scope,\n    +-\t\t\t\t const struct config_parse_options *config_opts)\n     -{\n     -\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n    --\t\t\t\t   data, scope, NULL);\n    +-\t\t\t\t   data, scope, config_opts);\n     -}\n     -\n     -int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n     -\t\t\t\t      void *data, enum config_scope scope,\n    --\t\t\t\t      const struct config_options *opts)\n    +-\t\t\t\t      const struct config_parse_options *opts)\n     -{\n     -\tint ret = -1;\n     -\tFILE *f;\n    @@ config.c: int git_default_config(const char *var, const char *value,\n     -\treturn ret;\n     -}\n     -\n    --int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n    --{\n    --\treturn git_config_from_file_with_options(fn, filename, data,\n    --\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, NULL);\n    --}\n    --\n    + int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n    + {\n    + \tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n    +@@ config.c: int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n    + \t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, &config_opts);\n    + }\n    + \n     -int git_config_from_mem(config_fn_t fn,\n     -\t\t\tconst enum config_origin_type origin_type,\n     -\t\t\tconst char *name, const char *buf, size_t len,\n     -\t\t\tvoid *data, enum config_scope scope,\n    --\t\t\tconst struct config_options *opts)\n    +-\t\t\tconst struct config_parse_options *opts)\n     -{\n     -\tstruct config_source top = CONFIG_SOURCE_INIT;\n     -\n    @@ config.c: int git_default_config(const char *var, const char *value,\n     -\ttop.origin_type = origin_type;\n     -\ttop.name = name;\n     -\ttop.path = NULL;\n    --\ttop.default_error_action = CONFIG_ERROR_ERROR;\n     -\ttop.do_fgetc = config_buf_fgetc;\n     -\ttop.do_ungetc = config_buf_ungetc;\n     -\ttop.do_ftell = config_buf_ftell;\n    @@ config.h: struct git_config_source {\n     -\t\t\t\t\tstruct config_source *cs,\n     -\t\t\t\t\tvoid *event_fn_data);\n     -\n    --struct config_options {\n    --\tunsigned int respect_includes : 1;\n    --\tunsigned int ignore_repo : 1;\n    --\tunsigned int ignore_worktree : 1;\n    --\tunsigned int ignore_cmdline : 1;\n    --\tunsigned int system_gently : 1;\n    --\n    --\t/*\n    --\t * For internal use. Include all includeif.hasremoteurl paths without\n    --\t * checking if the repo has that remote URL, and when doing so, verify\n    --\t * that files included in this way do not configure any remote URLs\n    --\t * themselves.\n    --\t */\n    --\tunsigned int unconditional_remote_url : 1;\n    --\n    --\tconst char *commondir;\n    --\tconst char *git_dir;\n    +-struct config_parse_options {\n     -\t/*\n     -\t * event_fn and event_fn_data are for internal use only. Handles events\n     -\t * emitted by the config parser.\n     -\t */\n     -\tconfig_parser_event_fn_t event_fn;\n     -\tvoid *event_fn_data;\n    --\tenum config_error_action {\n    --\t\tCONFIG_ERROR_UNSET = 0, /* use source-specific default */\n    --\t\tCONFIG_ERROR_DIE, /* die() on error */\n    --\t\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n    --\t\tCONFIG_ERROR_SILENT, /* return -1 */\n    --\t} error_action;\n     -};\n    +-\n    + #define CP_OPTS_INIT(error_action) { \\\n    + \t.event_fn = git_config_err_fn, \\\n    + \t.event_fn_data = (enum config_error_action []){(error_action)}, \\\n    +@@ config.h: enum config_error_action {\n    + int git_config_err_fn(enum config_event_t type, size_t begin_offset,\n    + \t\t      size_t end_offset, struct config_source *cs,\n    + \t\t      void *event_fn_data);\n     -\n     -/* Config source metadata for a given config key-value pair */\n     -struct key_value_info {\n    @@ config.h: struct git_config_source {\n     -\n      int git_default_config(const char *, const char *,\n      \t\t       const struct config_context *, void *);\n    - \n    --/**\n    -- * Read a specific file in git-config format.\n    -- * This function takes the same callback and data parameters as `git_config`.\n    -- *\n    -- * Unlike git_config(), this function does not respect includes.\n    -- */\n    --int git_config_from_file(config_fn_t fn, const char *, void *);\n    +-\n    + /**\n    +  * Read a specific file in git-config format.\n    +  * This function takes the same callback and data parameters as `git_config`.\n    +@@ config.h: int git_default_config(const char *, const char *,\n    +  * Unlike git_config(), this function does not respect includes.\n    +  */\n    + int git_config_from_file(config_fn_t fn, const char *, void *);\n     -\n     -int git_config_from_file_with_options(config_fn_t fn, const char *,\n     -\t\t\t\t      void *, enum config_scope,\n    --\t\t\t\t      const struct config_options *);\n    +-\t\t\t\t      const struct config_parse_options *);\n     -int git_config_from_mem(config_fn_t fn,\n     -\t\t\tconst enum config_origin_type,\n     -\t\t\tconst char *name,\n     -\t\t\tconst char *buf, size_t len,\n     -\t\t\tvoid *data, enum config_scope scope,\n    --\t\t\tconst struct config_options *opts);\n    +-\t\t\tconst struct config_parse_options *opts);\n      int git_config_from_blob_oid(config_fn_t fn, const char *name,\n      \t\t\t     struct repository *repo,\n      \t\t\t     const struct object_id *oid, void *data,\n\nbase-commit: aa9166bcc0ba654fc21f198a30647ec087f733ed\n-- \n2.41.0.585.gd2178a4bd4-goog\n\n"},{"id":"480028","messageId":"20230731234910.94149-2-chooglen@google.com","threadId":"60017","inReplyTo":"20230731234910.94149-1-chooglen@google.com","subject":"[RFC PATCH v1.5 1/5] config: return positive from git_config_parse_key()","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-07-31T23:46:38Z","receivedAt":"2023-07-31T23:50:08Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"git_config_parse_key() returns #define-d error codes, but negated. This\nnegation is merely a convenience to other parts of config.c that don't\nbother inspecting the return value before passing it along. But:\n\na) There's no good reason why those callers couldn't negate the value\n   themselves.\n\nb) In other callers, this value eventually gets fed to exit(3), and\n   those callers need to sanitize the negative value (and they sometimes\n   do so lossily, by overriding the return value with\n   CONFIG_INVALID_KEY).\n\nc) We want to move that into a separate library, and returning only\n   negative values no longer makes as much sense.\n\nChange git_config_parse_key() to return positive values instead, and\nadjust callers accordingly. Callers that sanitize the negative sign for\nexit(3) now pass the return value opaquely, fixing a bug where \"git\nconfig <key with no section or name>\" results in a different exit code\ndepending on whether we are setting or getting config. Callers that\nwanted to pass along a negative value now negate the return value\nthemselves.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/config.c   |  3 +--\n config.c           | 16 ++++++++--------\n config.h           |  2 +-\n submodule-config.c |  4 ++--\n t/t1300-config.sh  | 16 ++++++++++++++++\n 5 files changed, 28 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 1c75cbc43d..8a2840f0a8 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -362,8 +362,7 @@ static int get_value(const char *key_, const char *regex_, unsigned flags)\n \t\t\tgoto free_strings;\n \t\t}\n \t} else {\n-\t\tif (git_config_parse_key(key_, &key, NULL)) {\n-\t\t\tret = CONFIG_INVALID_KEY;\n+\t\tif ((ret = git_config_parse_key(key_, &key, NULL))) {\n \t\t\tgoto free_strings;\n \t\t}\n \t}\ndiff --git a/config.c b/config.c\nindex 85c5f35132..ca77ca17a4 100644\n--- a/config.c\n+++ b/config.c\n@@ -534,8 +534,9 @@ static inline int iskeychar(int c)\n  * Auxiliary function to sanity-check and split the key into the section\n  * identifier and variable name.\n  *\n- * Returns 0 on success, -1 when there is an invalid character in the key and\n- * -2 if there is no section name in the key.\n+ * Returns 0 on success, CONFIG_INVALID_KEY when there is an invalid character\n+ * in the key and CONFIG_NO_SECTION_OR_NAME if there is no section name in the\n+ * key.\n  *\n  * store_key - pointer to char* which will hold a copy of the key with\n  *             lowercase section and variable name\n@@ -555,12 +556,12 @@ int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)\n \n \tif (last_dot == NULL || last_dot == key) {\n \t\terror(_(\"key does not contain a section: %s\"), key);\n-\t\treturn -CONFIG_NO_SECTION_OR_NAME;\n+\t\treturn CONFIG_NO_SECTION_OR_NAME;\n \t}\n \n \tif (!last_dot[1]) {\n \t\terror(_(\"key does not contain variable name: %s\"), key);\n-\t\treturn -CONFIG_NO_SECTION_OR_NAME;\n+\t\treturn CONFIG_NO_SECTION_OR_NAME;\n \t}\n \n \tbaselen = last_dot - key;\n@@ -596,7 +597,7 @@ int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)\n \n out_free_ret_1:\n \tFREE_AND_NULL(*store_key);\n-\treturn -CONFIG_INVALID_KEY;\n+\treturn CONFIG_INVALID_KEY;\n }\n \n static int config_parse_pair(const char *key, const char *value,\n@@ -2346,7 +2347,7 @@ static int configset_find_element(struct config_set *set, const char *key,\n \t * `key` may come from the user, so normalize it before using it\n \t * for querying entries from the hashmap.\n \t */\n-\tret = git_config_parse_key(key, &normalized_key, NULL);\n+\tret = -git_config_parse_key(key, &normalized_key, NULL);\n \tif (ret)\n \t\treturn ret;\n \n@@ -3334,8 +3335,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \tsize_t contents_sz;\n \tstruct config_store_data store = CONFIG_STORE_INIT;\n \n-\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+\tret = git_config_parse_key(key, &store.key, &store.baselen);\n \tif (ret)\n \t\tgoto out_free;\n \ndiff --git a/config.h b/config.h\nindex 6332d74904..40966cb682 100644\n--- a/config.h\n+++ b/config.h\n@@ -23,7 +23,7 @@\n \n struct object_id;\n \n-/* git_config_parse_key() returns these negated: */\n+/* git_config_parse_key() returns these: */\n #define CONFIG_INVALID_KEY 1\n #define CONFIG_NO_SECTION_OR_NAME 2\n /* git_config_set_gently(), git_config_set_multivar_gently() return the above or these: */\ndiff --git a/submodule-config.c b/submodule-config.c\nindex b6908e295f..2aafc7f9cb 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -824,8 +824,8 @@ int print_config_from_gitmodules(struct repository *repo, const char *key)\n \tchar *store_key;\n \n \tret = git_config_parse_key(key, &store_key, NULL);\n-\tif (ret < 0)\n-\t\treturn CONFIG_INVALID_KEY;\n+\tif (ret)\n+\t\treturn ret;\n \n \tconfig_from_gitmodules(config_print_callback, repo, store_key);\n \ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 387d336c91..3202b0f884 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -2590,4 +2590,20 @@ test_expect_success 'includeIf.hasconfig:remote.*.url forbids remote url in such\n \tgrep \"fatal: remote URLs cannot be configured in file directly or indirectly included by includeIf.hasconfig:remote.*.url\" err\n '\n \n+# Exit codes\n+test_expect_success '--get with bad key' '\n+\t# Also exits with 1 if the value is not found\n+\ttest_expect_code 1 git config --get \"bad.name\\n\" 2>err &&\n+\tgrep \"error: invalid key\" err &&\n+\ttest_expect_code 2 git config --get \"bad.\" 2>err &&\n+\tgrep \"error: key does not contain variable name\" err\n+'\n+\n+test_expect_success 'set with bad key' '\n+\ttest_expect_code 1 git config \"bad.name\\n\" var 2>err &&\n+\tgrep \"error: invalid key\" err &&\n+\ttest_expect_code 2 git config \"bad.\" var 2>err &&\n+\tgrep \"error: key does not contain variable name\" err\n+'\n+\n test_done\n-- \n2.41.0.585.gd2178a4bd4-goog\n\n"},{"id":"480029","messageId":"20230731234910.94149-4-chooglen@google.com","threadId":"60017","inReplyTo":"20230731234910.94149-1-chooglen@google.com","subject":"[RFC PATCH v1.5 3/5] config: report config parse errors using cb","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-07-31T23:46:40Z","receivedAt":"2023-07-31T23:50:13Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"In a subsequent commit, config parsing will become its own library, and\nit's likely that the caller will want flexibility in handling errors\n(instead of being limited to the error handling we have in-tree).\n\nMove the Git-specific error handling into a config_parser_event_fn_t\nthat responds to config errors, and make git_parse_source() always\nreturn -1 (careful inspection shows that it was always returning -1\nalready). This makes CONFIG_ERROR_SILENT obsolete since that is\nequivalent to not specifying an error event listener. Also, remove\nCONFIG_ERROR_UNSET and the config_source 'default', since all callers\nare now expected to specify the error handling they want.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n builtin/config.c   |   4 +-\n bundle-uri.c       |   4 +-\n config.c           | 175 ++++++++++++++++++++++++++-------------------\n config.h           |  20 ++++--\n fsck.c             |   4 +-\n submodule-config.c |   9 ++-\n 6 files changed, 129 insertions(+), 87 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 8a2840f0a8..de1878b947 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -42,7 +42,9 @@ static int actions, type;\n static char *default_value;\n static int end_nul;\n static int respect_includes_opt = -1;\n-static struct config_options config_options;\n+static struct config_options config_options = {\n+\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE)\n+};\n static int show_origin;\n static int show_scope;\n static int fixed_value;\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex f93ca6a486..856bffdcad 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -237,9 +237,7 @@ int bundle_uri_parse_config_format(const char *uri,\n \t\t\t\t   struct bundle_list *list)\n {\n \tint result;\n-\tstruct config_parse_options opts = {\n-\t\t.error_action = CONFIG_ERROR_ERROR,\n-\t};\n+\tstruct config_parse_options opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);\n \n \tif (!list->baseURI) {\n \t\tstruct strbuf baseURI = STRBUF_INIT;\ndiff --git a/config.c b/config.c\nindex dc6cda03aa..b6d0e16240 100644\n--- a/config.c\n+++ b/config.c\n@@ -55,7 +55,6 @@ struct config_source {\n \tenum config_origin_type origin_type;\n \tconst char *name;\n \tconst char *path;\n-\tenum config_error_action default_error_action;\n \tint linenr;\n \tint eof;\n \tsize_t total_len;\n@@ -185,13 +184,15 @@ static int handle_path_include(const struct key_value_info *kvi,\n \t}\n \n \tif (!access_or_die(path, R_OK, 0)) {\n+\t\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n+\n \t\tif (++inc->depth > MAX_INCLUDE_DEPTH)\n \t\t\tdie(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,\n \t\t\t    !kvi ? \"<unknown>\" :\n \t\t\t    kvi->filename ? kvi->filename :\n \t\t\t    \"the command line\");\n \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n-\t\t\t\t\t\t\tkvi->scope, NULL);\n+\t\t\t\t\t\t\tkvi->scope, &config_opts);\n \t\tinc->depth--;\n \t}\n cleanup:\n@@ -339,7 +340,9 @@ static int add_remote_url(const char *var, const char *value,\n \n static void populate_remote_urls(struct config_include_data *inc)\n {\n-\tstruct config_options opts;\n+\tstruct config_options opts = {\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t};\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n@@ -1029,6 +1032,56 @@ static void kvi_from_source(struct config_source *cs,\n \tout->path = cs->path;\n }\n \n+int git_config_err_fn(enum config_event_t type, size_t begin_offset UNUSED,\n+\t\t      size_t end_offset UNUSED, struct config_source *cs,\n+\t\t      void *data)\n+{\n+\tchar *error_msg = NULL;\n+\tint error_return = 0;\n+\tenum config_error_action *action = data;\n+\n+\tif (type != CONFIG_EVENT_ERROR)\n+\t\treturn 0;\n+\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      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      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      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       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       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      cs->linenr, cs->name);\n+\t}\n+\n+\tswitch (*action) {\n+\tcase CONFIG_ERROR_DIE:\n+\t\tdie(\"%s\", error_msg);\n+\t\tbreak;\n+\tcase CONFIG_ERROR_ERROR:\n+\t\terror_return = error(\"%s\", error_msg);\n+\t\tbreak;\n+\t}\n+\n+\tfree(error_msg);\n+\treturn error_return;\n+}\n+\n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t\t    struct key_value_info *kvi, void *data,\n \t\t\t    const struct config_parse_options *opts)\n@@ -1036,8 +1089,6 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \tint comment = 0;\n \tsize_t baselen = 0;\n \tstruct strbuf *var = &cs->var;\n-\tint error_return = 0;\n-\tchar *error_msg = NULL;\n \n \t/* U+FEFF Byte Order Mark in UTF8 */\n \tconst char *bomptr = utf8_bom;\n@@ -1119,53 +1170,14 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t\tbreak;\n \t}\n \n-\tif (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)\n-\t\treturn -1;\n-\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      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      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      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       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       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      cs->linenr, cs->name);\n-\t}\n-\n-\tswitch (opts && opts->error_action ?\n-\t\topts->error_action :\n-\t\tcs->default_error_action) {\n-\tcase CONFIG_ERROR_DIE:\n-\t\tdie(\"%s\", error_msg);\n-\t\tbreak;\n-\tcase CONFIG_ERROR_ERROR:\n-\t\terror_return = error(\"%s\", error_msg);\n-\t\tbreak;\n-\tcase CONFIG_ERROR_SILENT:\n-\t\terror_return = -1;\n-\t\tbreak;\n-\tcase CONFIG_ERROR_UNSET:\n-\t\tBUG(\"config error action unset\");\n-\t}\n-\n-\tfree(error_msg);\n-\treturn error_return;\n+\t/*\n+\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n+\t * in order for our callback to receive the error event, we have to call\n+\t * do_event twice\n+\t */\n+\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n+\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n+\treturn -1;\n }\n \n static uintmax_t get_unit_factor(const char *end)\n@@ -2002,7 +2014,6 @@ static int do_config_from_file(config_fn_t fn,\n \ttop.origin_type = origin_type;\n \ttop.name = name;\n \ttop.path = path;\n-\ttop.default_error_action = CONFIG_ERROR_DIE;\n \ttop.do_fgetc = config_file_fgetc;\n \ttop.do_ungetc = config_file_ungetc;\n \ttop.do_ftell = config_file_ftell;\n@@ -2016,8 +2027,10 @@ static int do_config_from_file(config_fn_t fn,\n static int git_config_from_stdin(config_fn_t fn, void *data,\n \t\t\t\t enum config_scope scope)\n {\n+\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n+\n \treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n-\t\t\t\t   data, scope, NULL);\n+\t\t\t\t   data, scope, &config_opts);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2040,8 +2053,10 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \n int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n {\n+\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n+\n \treturn git_config_from_file_with_options(fn, filename, data,\n-\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, NULL);\n+\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, &config_opts);\n }\n \n int git_config_from_mem(config_fn_t fn,\n@@ -2058,7 +2073,6 @@ int git_config_from_mem(config_fn_t fn,\n \ttop.origin_type = origin_type;\n \ttop.name = name;\n \ttop.path = NULL;\n-\ttop.default_error_action = CONFIG_ERROR_ERROR;\n \ttop.do_fgetc = config_buf_fgetc;\n \ttop.do_ungetc = config_buf_ungetc;\n \ttop.do_ftell = config_buf_ftell;\n@@ -2077,6 +2091,7 @@ int git_config_from_blob_oid(config_fn_t fn,\n \tchar *buf;\n \tunsigned long size;\n \tint ret;\n+\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);\n \n \tbuf = repo_read_object_file(repo, oid, &type, &size);\n \tif (!buf)\n@@ -2087,7 +2102,7 @@ int git_config_from_blob_oid(config_fn_t fn,\n \t}\n \n \tret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size,\n-\t\t\t\t  data, scope, NULL);\n+\t\t\t\t  data, scope, &config_opts);\n \tfree(buf);\n \n \treturn ret;\n@@ -2188,29 +2203,32 @@ static int do_git_config_sequence(const struct config_options *opts,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n \t\tret += git_config_from_file_with_options(fn, system_config,\n \t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n-\t\t\t\t\t\t\t NULL);\n+\t\t\t\t\t\t\t &opts->parse_options);\n \n \tgit_global_config(&user_config, &xdg_config);\n \n \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n \t\tret += git_config_from_file_with_options(fn, xdg_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL,\n+\t\t\t\t\t\t\t &opts->parse_options);\n \n \tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n \t\tret += git_config_from_file_with_options(fn, user_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL,\n+\t\t\t\t\t\t\t &opts->parse_options);\n \n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n \t\tret += git_config_from_file_with_options(fn, repo_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n+\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL,\n+\t\t\t\t\t\t\t &opts->parse_options);\n \n \tif (!opts->ignore_worktree && worktree_config &&\n \t    repo && repo->repository_format_worktree_config &&\n \t    !access_or_die(worktree_config, R_OK, 0)) {\n \t\t\tret += git_config_from_file_with_options(fn, worktree_config, data,\n \t\t\t\t\t\t\t\t CONFIG_SCOPE_WORKTREE,\n-\t\t\t\t\t\t\t\t NULL);\n+\t\t\t\t\t\t\t\t &opts->parse_options);\n \t}\n \n \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n@@ -2251,7 +2269,7 @@ int config_with_options(config_fn_t fn, void *data,\n \t} else if (config_source && config_source->file) {\n \t\tret = git_config_from_file_with_options(fn, config_source->file,\n \t\t\t\t\t\t\tdata, config_source->scope,\n-\t\t\t\t\t\t\tNULL);\n+\t\t\t\t\t\t\t&opts->parse_options);\n \t} else if (config_source && config_source->blob) {\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n \t\t\t\t\t       data, config_source->scope);\n@@ -2289,9 +2307,11 @@ static void configset_iter(struct config_set *set, config_fn_t fn, void *data)\n \n void read_early_config(config_fn_t cb, void *data)\n {\n-\tstruct config_options opts = {0};\n \tstruct strbuf commondir = STRBUF_INIT;\n \tstruct strbuf gitdir = STRBUF_INIT;\n+\tstruct config_options opts = {\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t};\n \n \topts.respect_includes = 1;\n \n@@ -2323,7 +2343,9 @@ void read_early_config(config_fn_t cb, void *data)\n  */\n void read_very_early_config(config_fn_t cb, void *data)\n {\n-\tstruct config_options opts = { 0 };\n+\tstruct config_options opts = {\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t};\n \n \topts.respect_includes = 1;\n \topts.ignore_repo = 1;\n@@ -2614,7 +2636,9 @@ int git_configset_get_pathname(struct config_set *set, const char *key, const ch\n /* Functions use to read configuration from a repository */\n static void repo_read_config(struct repository *repo)\n {\n-\tstruct config_options opts = { 0 };\n+\tstruct config_options opts = {\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t};\n \n \topts.respect_includes = 1;\n \topts.commondir = repo->commondir;\n@@ -2761,12 +2785,14 @@ int repo_config_get_pathname(struct repository *repo,\n static void read_protected_config(void)\n {\n \tstruct config_options opts = {\n-\t\t.respect_includes = 1,\n-\t\t.ignore_repo = 1,\n-\t\t.ignore_worktree = 1,\n-\t\t.system_gently = 1,\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n \t};\n \n+\topts.respect_includes = 1;\n+\topts.ignore_repo = 1;\n+\topts.ignore_worktree = 1;\n+\topts.system_gently = 1;\n+\n \tgit_configset_init(&protected_config);\n \tconfig_with_options(config_set_callback, &protected_config, NULL,\n \t\t\t    NULL, &opts);\n@@ -2977,6 +3003,7 @@ struct config_store_data {\n \t\tenum config_event_t type;\n \t\tint is_keys_section;\n \t} *parsed;\n+\tenum config_error_action error_action;\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@@ -3044,6 +3071,10 @@ static int store_aux_event(enum config_event_t type, size_t begin, size_t end,\n \t\t\tstore->seen[store->seen_nr] = store->parsed_nr;\n \t\t}\n \t}\n+\tif (type == CONFIG_EVENT_ERROR) {\n+\t\treturn git_config_err_fn(type, begin, end, cs,\n+\t\t\t\t\t &store->error_action);\n+\t}\n \n \tstore->parsed_nr++;\n \n@@ -3380,7 +3411,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \t\tstruct stat st;\n \t\tsize_t copy_begin, copy_end;\n \t\tint i, new_line = 0;\n-\t\tstruct config_parse_options opts;\n+\t\tstruct config_parse_options opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n \n \t\tif (!value_pattern)\n \t\t\tstore.value_pattern = NULL;\n@@ -3407,8 +3438,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \n \t\tALLOC_GROW(store.parsed, 1, store.parsed_alloc);\n \t\tstore.parsed[0].end = 0;\n+\t\tstore.error_action = CONFIG_ERROR_DIE;\n \n-\t\tmemset(&opts, 0, sizeof(opts));\n \t\topts.event_fn = store_aux_event;\n \t\topts.event_fn_data = &store;\n \ndiff --git a/config.h b/config.h\nindex b13586307d..1aed02cd5d 100644\n--- a/config.h\n+++ b/config.h\n@@ -86,12 +86,6 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n \t\t\t\t\tvoid *event_fn_data);\n \n struct config_parse_options {\n-\tenum config_error_action {\n-\t\tCONFIG_ERROR_UNSET = 0, /* use source-specific default */\n-\t\tCONFIG_ERROR_DIE, /* die() on error */\n-\t\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n-\t\tCONFIG_ERROR_SILENT, /* return -1 */\n-\t} error_action;\n \t/*\n \t * event_fn and event_fn_data are for internal use only. Handles events\n \t * emitted by the config parser.\n@@ -100,6 +94,11 @@ struct config_parse_options {\n \tvoid *event_fn_data;\n };\n \n+#define CP_OPTS_INIT(error_action) { \\\n+\t.event_fn = git_config_err_fn, \\\n+\t.event_fn_data = (enum config_error_action []){(error_action)}, \\\n+}\n+\n struct config_options {\n \tunsigned int respect_includes : 1;\n \tunsigned int ignore_repo : 1;\n@@ -119,6 +118,15 @@ struct config_options {\n \tunsigned int unconditional_remote_url : 1;\n };\n \n+enum config_error_action {\n+\tCONFIG_ERROR_DIE, /* die() on error */\n+\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n+};\n+\n+int git_config_err_fn(enum config_event_t type, size_t begin_offset,\n+\t\t      size_t end_offset, struct config_source *cs,\n+\t\t      void *event_fn_data);\n+\n /* Config source metadata for a given config key-value pair */\n struct key_value_info {\n \tconst char *filename;\ndiff --git a/fsck.c b/fsck.c\nindex 522ee1c18a..bc0ca11421 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1219,7 +1219,6 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\treturn 0;\n \n \tif (oidset_contains(&options->gitmodules_found, oid)) {\n-\t\tstruct config_parse_options config_opts = { 0 };\n \t\tstruct fsck_gitmodules_data data;\n \n \t\toidset_insert(&options->gitmodules_done, oid);\n@@ -1238,10 +1237,9 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\tdata.oid = oid;\n \t\tdata.options = options;\n \t\tdata.ret = 0;\n-\t\tconfig_opts.error_action = CONFIG_ERROR_SILENT;\n \t\tif (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB,\n \t\t\t\t\t\".gitmodules\", buf, size, &data,\n-\t\t\t\t\tCONFIG_SCOPE_UNKNOWN, &config_opts))\n+\t\t\t\t\tCONFIG_SCOPE_UNKNOWN, NULL))\n \t\t\tdata.ret |= report(options, oid, OBJ_BLOB,\n \t\t\t\t\tFSCK_MSG_GITMODULES_PARSE,\n \t\t\t\t\t\"could not parse gitmodules blob\");\ndiff --git a/submodule-config.c b/submodule-config.c\nindex 2aafc7f9cb..bcdd6feefa 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -565,6 +565,8 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \tenum object_type type;\n \tconst struct submodule *submodule = NULL;\n \tstruct parse_config_parameter parameter;\n+\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);\n+\n \n \t/*\n \t * If any parameter except the cache is a NULL pointer just\n@@ -608,7 +610,8 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \tparameter.gitmodules_oid = &oid;\n \tparameter.overwrite = 0;\n \tgit_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf,\n-\t\t\t    config, config_size, &parameter, CONFIG_SCOPE_UNKNOWN, NULL);\n+\t\t\t    config, config_size, &parameter,\n+\t\t\t    CONFIG_SCOPE_UNKNOWN, &config_opts);\n \tstrbuf_release(&rev);\n \tfree(config);\n \n@@ -652,7 +655,9 @@ static void config_from_gitmodules(config_fn_t fn, struct repository *repo, void\n \t\tstruct git_config_source config_source = {\n \t\t\t0, .scope = CONFIG_SCOPE_SUBMODULE\n \t\t};\n-\t\tconst struct config_options opts = { 0 };\n+\t\tstruct config_options opts = {\n+\t\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t\t};\n \t\tstruct object_id oid;\n \t\tchar *file;\n \t\tchar *oidstr = NULL;\n-- \n2.41.0.585.gd2178a4bd4-goog\n\n"},{"id":"480030","messageId":"20230731234910.94149-3-chooglen@google.com","threadId":"60017","inReplyTo":"20230731234910.94149-1-chooglen@google.com","subject":"[RFC PATCH v1.5 2/5] config: split out config_parse_options","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-07-31T23:46:39Z","receivedAt":"2023-07-31T23:50:14Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"\"struct config_options\" is a disjoint set of options options used by the\nconfig parser (e.g. event listners) and options used by\nconfig_with_options() (e.g. to handle includes, choose which config\nfiles to parse). Split parser-only options into config_parse_options.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n bundle-uri.c |  2 +-\n config.c     | 14 +++++++-------\n config.h     | 37 ++++++++++++++++++++-----------------\n fsck.c       |  2 +-\n 4 files changed, 29 insertions(+), 26 deletions(-)\n\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex 4b5c49b93d..f93ca6a486 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -237,7 +237,7 @@ int bundle_uri_parse_config_format(const char *uri,\n \t\t\t\t   struct bundle_list *list)\n {\n \tint result;\n-\tstruct config_options opts = {\n+\tstruct config_parse_options opts = {\n \t\t.error_action = CONFIG_ERROR_ERROR,\n \t};\n \ndiff --git a/config.c b/config.c\nindex ca77ca17a4..dc6cda03aa 100644\n--- a/config.c\n+++ b/config.c\n@@ -983,7 +983,7 @@ static int get_base_var(struct config_source *cs, struct strbuf *name)\n struct parse_event_data {\n \tenum config_event_t previous_type;\n \tsize_t previous_offset;\n-\tconst struct config_options *opts;\n+\tconst struct config_parse_options *opts;\n };\n \n static int do_event(struct config_source *cs, enum config_event_t type,\n@@ -1031,7 +1031,7 @@ static void kvi_from_source(struct config_source *cs,\n \n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t\t    struct key_value_info *kvi, void *data,\n-\t\t\t    const struct config_options *opts)\n+\t\t\t    const struct config_parse_options *opts)\n {\n \tint comment = 0;\n \tsize_t baselen = 0;\n@@ -1968,7 +1968,7 @@ int git_default_config(const char *var, const char *value,\n  */\n static int do_config_from(struct config_source *top, config_fn_t fn,\n \t\t\t  void *data, enum config_scope scope,\n-\t\t\t  const struct config_options *opts)\n+\t\t\t  const struct config_parse_options *opts)\n {\n \tstruct key_value_info kvi = KVI_INIT;\n \tint ret;\n@@ -1993,7 +1993,7 @@ static int do_config_from_file(config_fn_t fn,\n \t\t\t       const enum config_origin_type origin_type,\n \t\t\t       const char *name, const char *path, FILE *f,\n \t\t\t       void *data, enum config_scope scope,\n-\t\t\t       const struct config_options *opts)\n+\t\t\t       const struct config_parse_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n@@ -2022,7 +2022,7 @@ static int git_config_from_stdin(config_fn_t fn, void *data,\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \t\t\t\t      void *data, enum config_scope scope,\n-\t\t\t\t      const struct config_options *opts)\n+\t\t\t\t      const struct config_parse_options *opts)\n {\n \tint ret = -1;\n \tFILE *f;\n@@ -2048,7 +2048,7 @@ int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type origin_type,\n \t\t\tconst char *name, const char *buf, size_t len,\n \t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_options *opts)\n+\t\t\tconst struct config_parse_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \n@@ -3380,7 +3380,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \t\tstruct stat st;\n \t\tsize_t copy_begin, copy_end;\n \t\tint i, new_line = 0;\n-\t\tstruct config_options opts;\n+\t\tstruct config_parse_options opts;\n \n \t\tif (!value_pattern)\n \t\t\tstore.value_pattern = NULL;\ndiff --git a/config.h b/config.h\nindex 40966cb682..b13586307d 100644\n--- a/config.h\n+++ b/config.h\n@@ -85,6 +85,21 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n \t\t\t\t\tstruct config_source *cs,\n \t\t\t\t\tvoid *event_fn_data);\n \n+struct config_parse_options {\n+\tenum config_error_action {\n+\t\tCONFIG_ERROR_UNSET = 0, /* use source-specific default */\n+\t\tCONFIG_ERROR_DIE, /* die() on error */\n+\t\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n+\t\tCONFIG_ERROR_SILENT, /* return -1 */\n+\t} error_action;\n+\t/*\n+\t * event_fn and event_fn_data are for internal use only. Handles events\n+\t * emitted by the config parser.\n+\t */\n+\tconfig_parser_event_fn_t event_fn;\n+\tvoid *event_fn_data;\n+};\n+\n struct config_options {\n \tunsigned int respect_includes : 1;\n \tunsigned int ignore_repo : 1;\n@@ -92,6 +107,9 @@ struct config_options {\n \tunsigned int ignore_cmdline : 1;\n \tunsigned int system_gently : 1;\n \n+\tconst char *commondir;\n+\tconst char *git_dir;\n+\tstruct config_parse_options parse_options;\n \t/*\n \t * For internal use. Include all includeif.hasremoteurl paths without\n \t * checking if the repo has that remote URL, and when doing so, verify\n@@ -99,21 +117,6 @@ struct config_options {\n \t * themselves.\n \t */\n \tunsigned int unconditional_remote_url : 1;\n-\n-\tconst char *commondir;\n-\tconst char *git_dir;\n-\t/*\n-\t * event_fn and event_fn_data are for internal use only. Handles events\n-\t * emitted by the config parser.\n-\t */\n-\tconfig_parser_event_fn_t event_fn;\n-\tvoid *event_fn_data;\n-\tenum config_error_action {\n-\t\tCONFIG_ERROR_UNSET = 0, /* use source-specific default */\n-\t\tCONFIG_ERROR_DIE, /* die() on error */\n-\t\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n-\t\tCONFIG_ERROR_SILENT, /* return -1 */\n-\t} error_action;\n };\n \n /* Config source metadata for a given config key-value pair */\n@@ -178,13 +181,13 @@ int git_config_from_file(config_fn_t fn, const char *, void *);\n \n int git_config_from_file_with_options(config_fn_t fn, const char *,\n \t\t\t\t      void *, enum config_scope,\n-\t\t\t\t      const struct config_options *);\n+\t\t\t\t      const struct config_parse_options *);\n int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type,\n \t\t\tconst char *name,\n \t\t\tconst char *buf, size_t len,\n \t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_options *opts);\n+\t\t\tconst struct config_parse_options *opts);\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     struct repository *repo,\n \t\t\t     const struct object_id *oid, void *data,\ndiff --git a/fsck.c b/fsck.c\nindex 3be86616c5..522ee1c18a 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1219,7 +1219,7 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\treturn 0;\n \n \tif (oidset_contains(&options->gitmodules_found, oid)) {\n-\t\tstruct config_options config_opts = { 0 };\n+\t\tstruct config_parse_options config_opts = { 0 };\n \t\tstruct fsck_gitmodules_data data;\n \n \t\toidset_insert(&options->gitmodules_done, oid);\n-- \n2.41.0.585.gd2178a4bd4-goog\n\n"},{"id":"480031","messageId":"20230731234910.94149-5-chooglen@google.com","threadId":"60017","inReplyTo":"20230731234910.94149-1-chooglen@google.com","subject":"[RFC PATCH v1.5 4/5] config.c: accept config_parse_options in git_config_from_stdin","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-07-31T23:46:41Z","receivedAt":"2023-07-31T23:50:17Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"A later commit will move git_config_from_stdin() to a library, so it\nwill need to accept event listeners.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n config.c | 10 +++++-----\n 1 file changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex b6d0e16240..cf0785a258 100644\n--- a/config.c\n+++ b/config.c\n@@ -2025,12 +2025,11 @@ static int do_config_from_file(config_fn_t fn,\n }\n \n static int git_config_from_stdin(config_fn_t fn, void *data,\n-\t\t\t\t enum config_scope scope)\n+\t\t\t\t enum config_scope scope,\n+\t\t\t\t const struct config_parse_options *config_opts)\n {\n-\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n-\n \treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n-\t\t\t\t   data, scope, &config_opts);\n+\t\t\t\t   data, scope, config_opts);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2265,7 +2264,8 @@ int config_with_options(config_fn_t fn, void *data,\n \t * regular lookup sequence.\n \t */\n \tif (config_source && config_source->use_stdin) {\n-\t\tret = git_config_from_stdin(fn, data, config_source->scope);\n+\t\tret = git_config_from_stdin(fn, data, config_source->scope,\n+\t\t\t\t\t    &opts->parse_options);\n \t} else if (config_source && config_source->file) {\n \t\tret = git_config_from_file_with_options(fn, config_source->file,\n \t\t\t\t\t\t\tdata, config_source->scope,\n-- \n2.41.0.585.gd2178a4bd4-goog\n\n"},{"id":"480032","messageId":"20230731234910.94149-6-chooglen@google.com","threadId":"60017","inReplyTo":"20230731234910.94149-1-chooglen@google.com","subject":"[RFC PATCH v1.5 5/5] config-parse: split library out of config.[c|h]","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2023-07-31T23:46:42Z","receivedAt":"2023-07-31T23:50:46Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"The config parsing machinery (besides \"include\" directives) is usable by\nprograms other than Git - it works with works with any file written in\nGit config syntax (IOW it doesn't rely on 'core' Git features like a\nrepository), and as of the series ending at 6e8e7981eb (config: pass\nsource to config_parser_event_fn_t, 2023-06-28), it no longer relies on\nglobal state. Thus, we can and should start turning it into a library\nother programs can use.\n\nBegin this process by splitting the config parsing code out of\nconfig.[c|h] and into config-parse.[c|h]. Do not change interfaces or\nfunction bodies, but tweak visibility and includes where appropriate,\nnamely:\n\n- git_config_from_stdin() is now non-static so that it can be seen by\n  config.c.\n\n- \"struct config_source\" is now defined in the .h file so that it can be\n  seen by config.c. And as a result, config-lib.h needs to \"#include\n  strbuf.h\".\n\nIn theory, this makes it possible for in-tree files to decide whether\nthey only need all of the config functionality or only config parsing,\nand bring in the smallest bit of functionality needed. But for now,\nthere are no in-tree files that can swap \"#include config.h\" for\n\"#include config-parse.h\". E.g. Bundle URIs would only need config\nparsing to parse bundle lists, but bundle-uri.c uses other config.h\nfunctionality like key parsing and reading repo settings.\n\nThe resulting library is usable, though it is unergonomic to do so,\ne.g. the caller needs to \"#include git-compat-util.h\" and other\ndependencies, and we don't have an easy way of linking in the required\nobjects. This isn't the end state we want for our libraries, but at\nleast we have _some_ library whose usability we can improve in future\nseries.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\n---\n Makefile       |   1 +\n config-parse.c | 561 +++++++++++++++++++++++++++++++++++++++++++++++\n config-parse.h | 155 +++++++++++++\n config.c       | 583 -------------------------------------------------\n config.h       | 119 +---------\n 5 files changed, 718 insertions(+), 701 deletions(-)\n create mode 100644 config-parse.c\n create mode 100644 config-parse.h\n\ndiff --git a/Makefile b/Makefile\nindex fb541dedc9..67e05bcee5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -992,6 +992,7 @@ LIB_OBJS += compat/obstack.o\n LIB_OBJS += compat/terminal.o\n LIB_OBJS += compat/zlib-uncompress2.o\n LIB_OBJS += config.o\n+LIB_OBJS += config-parse.o\n LIB_OBJS += connect.o\n LIB_OBJS += connected.o\n LIB_OBJS += convert.o\ndiff --git a/config-parse.c b/config-parse.c\nnew file mode 100644\nindex 0000000000..ff6abc7bd3\n--- /dev/null\n+++ b/config-parse.c\n@@ -0,0 +1,561 @@\n+#include \"git-compat-util.h\"\n+#include \"strbuf.h\"\n+#include \"gettext.h\"\n+#include \"hashmap.h\"\n+#include \"utf8.h\"\n+#include \"config-parse.h\"\n+\n+static int config_file_fgetc(struct config_source *conf)\n+{\n+\treturn getc_unlocked(conf->u.file);\n+}\n+\n+static int config_file_ungetc(int c, struct config_source *conf)\n+{\n+\treturn ungetc(c, conf->u.file);\n+}\n+\n+static long config_file_ftell(struct config_source *conf)\n+{\n+\treturn ftell(conf->u.file);\n+}\n+\n+\n+static int config_buf_fgetc(struct config_source *conf)\n+{\n+\tif (conf->u.buf.pos < conf->u.buf.len)\n+\t\treturn conf->u.buf.buf[conf->u.buf.pos++];\n+\n+\treturn EOF;\n+}\n+\n+static int config_buf_ungetc(int c, struct config_source *conf)\n+{\n+\tif (conf->u.buf.pos > 0) {\n+\t\tconf->u.buf.pos--;\n+\t\tif (conf->u.buf.buf[conf->u.buf.pos] != c)\n+\t\t\tBUG(\"config_buf can only ungetc the same character\");\n+\t\treturn c;\n+\t}\n+\n+\treturn EOF;\n+}\n+\n+static long config_buf_ftell(struct config_source *conf)\n+{\n+\treturn conf->u.buf.pos;\n+}\n+\n+static inline int iskeychar(int c)\n+{\n+\treturn isalnum(c) || c == '-';\n+}\n+\n+/*\n+ * Auxiliary function to sanity-check and split the key into the section\n+ * identifier and variable name.\n+ *\n+ * Returns 0 on success, CONFIG_INVALID_KEY when there is an invalid character\n+ * in the key and CONFIG_NO_SECTION_OR_NAME if there is no section name in the\n+ * key.\n+ *\n+ * store_key - pointer to char* which will hold a copy of the key with\n+ *             lowercase section and variable name\n+ * baselen - pointer to size_t which will hold the length of the\n+ *           section + subsection part, can be NULL\n+ */\n+int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)\n+{\n+\tsize_t i, baselen;\n+\tint dot;\n+\tconst char *last_dot = strrchr(key, '.');\n+\n+\t/*\n+\t * Since \"key\" actually contains the section name and the real\n+\t * key name separated by a dot, we have to know where the dot is.\n+\t */\n+\n+\tif (last_dot == NULL || last_dot == key) {\n+\t\terror(_(\"key does not contain a section: %s\"), key);\n+\t\treturn CONFIG_NO_SECTION_OR_NAME;\n+\t}\n+\n+\tif (!last_dot[1]) {\n+\t\terror(_(\"key does not contain variable name: %s\"), key);\n+\t\treturn CONFIG_NO_SECTION_OR_NAME;\n+\t}\n+\n+\tbaselen = last_dot - key;\n+\tif (baselen_)\n+\t\t*baselen_ = baselen;\n+\n+\t/*\n+\t * Validate the key and while at it, lower case it for matching.\n+\t */\n+\t*store_key = xmallocz(strlen(key));\n+\n+\tdot = 0;\n+\tfor (i = 0; key[i]; i++) {\n+\t\tunsigned char c = key[i];\n+\t\tif (c == '.')\n+\t\t\tdot = 1;\n+\t\t/* Leave the extended basename untouched.. */\n+\t\tif (!dot || i > baselen) {\n+\t\t\tif (!iskeychar(c) ||\n+\t\t\t    (i == baselen + 1 && !isalpha(c))) {\n+\t\t\t\terror(_(\"invalid key: %s\"), key);\n+\t\t\t\tgoto out_free_ret_1;\n+\t\t\t}\n+\t\t\tc = tolower(c);\n+\t\t} else if (c == '\\n') {\n+\t\t\terror(_(\"invalid key (newline): %s\"), key);\n+\t\t\tgoto out_free_ret_1;\n+\t\t}\n+\t\t(*store_key)[i] = c;\n+\t}\n+\n+\treturn 0;\n+\n+out_free_ret_1:\n+\tFREE_AND_NULL(*store_key);\n+\treturn CONFIG_INVALID_KEY;\n+}\n+\n+static int get_next_char(struct config_source *cs)\n+{\n+\tint c = cs->do_fgetc(cs);\n+\n+\tif (c == '\\r') {\n+\t\t/* DOS like systems */\n+\t\tc = cs->do_fgetc(cs);\n+\t\tif (c != '\\n') {\n+\t\t\tif (c != EOF)\n+\t\t\t\tcs->do_ungetc(c, cs);\n+\t\t\tc = '\\r';\n+\t\t}\n+\t}\n+\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+\t\t * overflows. Note that we can't return an error specifically,\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\treturn 0;\n+\t}\n+\n+\tif (c == '\\n')\n+\t\tcs->linenr++;\n+\tif (c == EOF) {\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 *cs)\n+{\n+\tint quote = 0, comment = 0, space = 0;\n+\n+\tstrbuf_reset(&cs->value);\n+\tfor (;;) {\n+\t\tint c = get_next_char(cs);\n+\t\tif (c == '\\n') {\n+\t\t\tif (quote) {\n+\t\t\t\tcs->linenr--;\n+\t\t\t\treturn NULL;\n+\t\t\t}\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 (cs->value.len)\n+\t\t\t\tspace++;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!quote) {\n+\t\t\tif (c == ';' || c == '#') {\n+\t\t\t\tcomment = 1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\t\tfor (; space; space--)\n+\t\t\tstrbuf_addch(&cs->value, ' ');\n+\t\tif (c == '\\\\') {\n+\t\t\tc = get_next_char(cs);\n+\t\t\tswitch (c) {\n+\t\t\tcase '\\n':\n+\t\t\t\tcontinue;\n+\t\t\tcase 't':\n+\t\t\t\tc = '\\t';\n+\t\t\t\tbreak;\n+\t\t\tcase 'b':\n+\t\t\t\tc = '\\b';\n+\t\t\t\tbreak;\n+\t\t\tcase 'n':\n+\t\t\t\tc = '\\n';\n+\t\t\t\tbreak;\n+\t\t\t/* Some characters escape as themselves */\n+\t\t\tcase '\\\\': case '\"':\n+\t\t\t\tbreak;\n+\t\t\t/* Reject unknown escape sequences */\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\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}\n+}\n+\n+static int get_value(struct config_source *cs, struct key_value_info *kvi,\n+\t\t     config_fn_t fn, void *data, struct strbuf *name)\n+{\n+\tint c;\n+\tchar *value;\n+\tint ret;\n+\tstruct config_context ctx = {\n+\t\t.kvi = kvi,\n+\t};\n+\n+\t/* Get the full name */\n+\tfor (;;) {\n+\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+\t\tstrbuf_addch(name, tolower(c));\n+\t}\n+\n+\twhile (c == ' ' || c == '\\t')\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(cs);\n+\t\tif (!value)\n+\t\t\treturn -1;\n+\t}\n+\t/*\n+\t * We already consumed the \\n, but we need linenr to point to\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+\tkvi->linenr = cs->linenr;\n+\tret = fn(name->buf, value, &ctx, data);\n+\tif (ret >= 0)\n+\t\tcs->linenr++;\n+\treturn ret;\n+}\n+\n+static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n+\t\t\t\t int c)\n+{\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(cs);\n+\t} while (isspace(c));\n+\n+\t/* We require the format to be '[base \"extension\"]' */\n+\tif (c != '\"')\n+\t\treturn -1;\n+\tstrbuf_addch(name, '.');\n+\n+\tfor (;;) {\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(cs);\n+\t\t\tif (c == '\\n')\n+\t\t\t\tgoto error_incomplete_line;\n+\t\t}\n+\t\tstrbuf_addch(name, c);\n+\t}\n+\n+\t/* Final ']' */\n+\tif (get_next_char(cs) != ']')\n+\t\treturn -1;\n+\treturn 0;\n+error_incomplete_line:\n+\tcs->linenr--;\n+\treturn -1;\n+}\n+\n+static int get_base_var(struct config_source *cs, struct strbuf *name)\n+{\n+\tcs->subsection_case_sensitive = 1;\n+\tfor (;;) {\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(cs, name, c);\n+\t\tif (!iskeychar(c) && c != '.')\n+\t\t\treturn -1;\n+\t\tstrbuf_addch(name, tolower(c));\n+\t}\n+}\n+\n+struct parse_event_data {\n+\tenum config_event_t previous_type;\n+\tsize_t previous_offset;\n+\tconst struct config_parse_options *opts;\n+};\n+\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+\tif (!data->opts || !data->opts->event_fn)\n+\t\treturn 0;\n+\n+\tif (type == CONFIG_EVENT_WHITESPACE &&\n+\t    data->previous_type == type)\n+\t\treturn 0;\n+\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+\t * we will already have advanced to the next event.\n+\t */\n+\tif (type != CONFIG_EVENT_EOF)\n+\t\toffset--;\n+\n+\tif (data->previous_type != CONFIG_EVENT_EOF &&\n+\t    data->opts->event_fn(data->previous_type, data->previous_offset,\n+\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n+\t\treturn -1;\n+\n+\tdata->previous_type = type;\n+\tdata->previous_offset = offset;\n+\n+\treturn 0;\n+}\n+\n+static void kvi_from_source(struct config_source *cs,\n+\t\t\t    enum config_scope scope,\n+\t\t\t    struct key_value_info *out)\n+{\n+\tout->filename = strintern(cs->name);\n+\tout->origin_type = cs->origin_type;\n+\tout->linenr = cs->linenr;\n+\tout->scope = scope;\n+\tout->path = cs->path;\n+}\n+\n+static int git_parse_source(struct config_source *cs, config_fn_t fn,\n+\t\t\t    struct key_value_info *kvi, void *data,\n+\t\t\t    const struct config_parse_options *opts)\n+{\n+\tint comment = 0;\n+\tsize_t baselen = 0;\n+\tstruct strbuf *var = &cs->var;\n+\n+\t/* U+FEFF Byte Order Mark in UTF8 */\n+\tconst char *bomptr = utf8_bom;\n+\n+\t/* For the parser event callback */\n+\tstruct parse_event_data event_data = {\n+\t\tCONFIG_EVENT_EOF, 0, opts\n+\t};\n+\n+\tfor (;;) {\n+\t\tint c;\n+\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+\t\t\t * own, but e.g. Windows Notepad will do it happily. */\n+\t\t\tif (c == (*bomptr & 0377)) {\n+\t\t\t\tbomptr++;\n+\t\t\t\tcontinue;\n+\t\t\t} else {\n+\t\t\t\t/* Do not tolerate partial BOM. */\n+\t\t\t\tif (bomptr != utf8_bom)\n+\t\t\t\t\tbreak;\n+\t\t\t\t/* No BOM at file beginning. Cool. */\n+\t\t\t\tbomptr = NULL;\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\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\t\treturn -1;\n+\t\t\tcomment = 0;\n+\t\t\tcontinue;\n+\t\t}\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\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\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\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\t\tbreak;\n+\t\t\tstrbuf_addch(var, '.');\n+\t\t\tbaselen = var->len;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!isalpha(c))\n+\t\t\tbreak;\n+\n+\t\tif (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)\n+\t\t\treturn -1;\n+\n+\t\t/*\n+\t\t * Truncate the var name back to the section header\n+\t\t * stem prior to grabbing the suffix part of the name\n+\t\t * and the value.\n+\t\t */\n+\t\tstrbuf_setlen(var, baselen);\n+\t\tstrbuf_addch(var, tolower(c));\n+\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n+\t\t\tbreak;\n+\t}\n+\t/*\n+\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n+\t * in order for our callback to receive the error event, we have to call\n+\t * do_event twice\n+\t */\n+\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n+\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n+\treturn -1;\n+}\n+\n+/*\n+ * All source specific fields in the union, die_on_error, name and the callbacks\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,\n+\t\t\t  void *data, enum config_scope scope,\n+\t\t\t  const struct config_parse_options *opts)\n+{\n+\tstruct key_value_info kvi = KVI_INIT;\n+\tint ret;\n+\n+\t/* push config-file parsing state stack */\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+\tkvi_from_source(top, scope, &kvi);\n+\n+\tret = git_parse_source(top, fn, &kvi, data, opts);\n+\n+\tstrbuf_release(&top->value);\n+\tstrbuf_release(&top->var);\n+\n+\treturn ret;\n+}\n+\n+static int do_config_from_file(config_fn_t fn,\n+\t\t\t       const enum config_origin_type origin_type,\n+\t\t\t       const char *name, const char *path, FILE *f,\n+\t\t\t       void *data, enum config_scope scope,\n+\t\t\t       const struct config_parse_options *opts)\n+{\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n+\tint ret;\n+\n+\ttop.u.file = f;\n+\ttop.origin_type = origin_type;\n+\ttop.name = name;\n+\ttop.path = path;\n+\ttop.do_fgetc = config_file_fgetc;\n+\ttop.do_ungetc = config_file_ungetc;\n+\ttop.do_ftell = config_file_ftell;\n+\n+\tflockfile(f);\n+\tret = do_config_from(&top, fn, data, scope, opts);\n+\tfunlockfile(f);\n+\treturn ret;\n+}\n+\n+int git_config_from_stdin(config_fn_t fn, void *data, enum config_scope scope,\n+\t\t\t  const struct config_parse_options *config_opts)\n+{\n+\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n+\t\t\t\t   data, scope, config_opts);\n+}\n+\n+int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n+\t\t\t\t      void *data, enum config_scope scope,\n+\t\t\t\t      const struct config_parse_options *opts)\n+{\n+\tint ret = -1;\n+\tFILE *f;\n+\n+\tif (!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, scope, opts);\n+\t\tfclose(f);\n+\t}\n+\treturn ret;\n+}\n+\n+int git_config_from_mem(config_fn_t fn,\n+\t\t\tconst enum config_origin_type origin_type,\n+\t\t\tconst char *name, const char *buf, size_t len,\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_parse_options *opts)\n+{\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n+\n+\ttop.u.buf.buf = buf;\n+\ttop.u.buf.len = len;\n+\ttop.u.buf.pos = 0;\n+\ttop.origin_type = origin_type;\n+\ttop.name = name;\n+\ttop.path = NULL;\n+\ttop.do_fgetc = config_buf_fgetc;\n+\ttop.do_ungetc = config_buf_ungetc;\n+\ttop.do_ftell = config_buf_ftell;\n+\n+\treturn do_config_from(&top, fn, data, scope, opts);\n+}\ndiff --git a/config-parse.h b/config-parse.h\nnew file mode 100644\nindex 0000000000..d5623906ef\n--- /dev/null\n+++ b/config-parse.h\n@@ -0,0 +1,155 @@\n+/*\n+ * Low level config parsing.\n+ */\n+#ifndef CONFIG_PARSE_H\n+#define CONFIG_PARSE_H\n+\n+#include \"strbuf.h\"\n+\n+/* git_config_parse_key() returns these: */\n+#define CONFIG_INVALID_KEY 1\n+#define CONFIG_NO_SECTION_OR_NAME 2\n+\n+int git_config_parse_key(const char *, char **, size_t *);\n+\n+enum config_scope {\n+\tCONFIG_SCOPE_UNKNOWN = 0,\n+\tCONFIG_SCOPE_SYSTEM,\n+\tCONFIG_SCOPE_GLOBAL,\n+\tCONFIG_SCOPE_LOCAL,\n+\tCONFIG_SCOPE_WORKTREE,\n+\tCONFIG_SCOPE_COMMAND,\n+\tCONFIG_SCOPE_SUBMODULE,\n+};\n+const char *config_scope_name(enum config_scope scope);\n+\n+enum config_origin_type {\n+\tCONFIG_ORIGIN_UNKNOWN = 0,\n+\tCONFIG_ORIGIN_BLOB,\n+\tCONFIG_ORIGIN_FILE,\n+\tCONFIG_ORIGIN_STDIN,\n+\tCONFIG_ORIGIN_SUBMODULE_BLOB,\n+\tCONFIG_ORIGIN_CMDLINE\n+};\n+\n+enum config_event_t {\n+\tCONFIG_EVENT_SECTION,\n+\tCONFIG_EVENT_ENTRY,\n+\tCONFIG_EVENT_WHITESPACE,\n+\tCONFIG_EVENT_COMMENT,\n+\tCONFIG_EVENT_EOF,\n+\tCONFIG_EVENT_ERROR\n+};\n+\n+struct config_source;\n+/*\n+ * The parser event function (if not NULL) is called with the event type and\n+ * the begin/end offsets of the parsed elements.\n+ *\n+ * Note: for CONFIG_EVENT_ENTRY (i.e. config variables), the trailing newline\n+ * character is considered part of the element.\n+ */\n+typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n+\t\t\t\t\tsize_t begin_offset, size_t end_offset,\n+\t\t\t\t\tstruct config_source *cs,\n+\t\t\t\t\tvoid *event_fn_data);\n+\n+struct config_parse_options {\n+\t/*\n+\t * event_fn and event_fn_data are for internal use only. Handles events\n+\t * emitted by the config parser.\n+\t */\n+\tconfig_parser_event_fn_t event_fn;\n+\tvoid *event_fn_data;\n+};\n+\n+struct config_source {\n+\tstruct config_source *prev;\n+\tunion {\n+\t\tFILE *file;\n+\t\tstruct config_buf {\n+\t\t\tconst char *buf;\n+\t\t\tsize_t len;\n+\t\t\tsize_t pos;\n+\t\t} buf;\n+\t} u;\n+\tenum config_origin_type origin_type;\n+\tconst char *name;\n+\tconst char *path;\n+\tint linenr;\n+\tint eof;\n+\tsize_t total_len;\n+\tstruct strbuf value;\n+\tstruct strbuf var;\n+\tunsigned subsection_case_sensitive : 1;\n+\n+\tint (*do_fgetc)(struct config_source *c);\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+/* Config source metadata for a given config key-value pair */\n+struct key_value_info {\n+\tconst char *filename;\n+\tint linenr;\n+\tenum config_origin_type origin_type;\n+\tenum config_scope scope;\n+\tconst char *path;\n+};\n+#define KVI_INIT { \\\n+\t.filename = NULL, \\\n+\t.linenr = -1, \\\n+\t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n+\t.scope = CONFIG_SCOPE_UNKNOWN, \\\n+\t.path = NULL, \\\n+}\n+\n+/* Captures additional information that a config callback can use. */\n+struct config_context {\n+\t/* Config source metadata for key and value. */\n+\tconst struct key_value_info *kvi;\n+};\n+#define CONFIG_CONTEXT_INIT { 0 }\n+\n+/**\n+ * A config callback function takes four parameters:\n+ *\n+ * - the name of the parsed variable. This is in canonical \"flat\" form: the\n+ *   section, subsection, and variable segments will be separated by dots,\n+ *   and the section and variable segments will be all lowercase. E.g.,\n+ *   `core.ignorecase`, `diff.SomeType.textconv`.\n+ *\n+ * - the value of the found variable, as a string. If the variable had no\n+ *   value specified, the value will be NULL (typically this means it\n+ *   should be interpreted as boolean true).\n+ *\n+ * - the 'config context', that is, additional information about the config\n+ *   iteration operation provided by the config machinery. For example, this\n+ *   includes information about the config source being parsed (e.g. the\n+ *   filename).\n+ *\n+ * - a void pointer passed in by the caller of the config API; this can\n+ *   contain callback-specific data\n+ *\n+ * A config callback should return 0 for success, or -1 if the variable\n+ * could not be parsed properly.\n+ */\n+typedef int (*config_fn_t)(const char *, const char *,\n+\t\t\t   const struct config_context *, void *);\n+\n+int git_config_from_file_with_options(config_fn_t fn, const char *,\n+\t\t\t\t      void *, enum config_scope,\n+\t\t\t\t      const struct config_parse_options *);\n+\n+int git_config_from_mem(config_fn_t fn,\n+\t\t\tconst enum config_origin_type,\n+\t\t\tconst char *name,\n+\t\t\tconst char *buf, size_t len,\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_parse_options *opts);\n+\n+int git_config_from_stdin(config_fn_t fn, void *data, enum config_scope scope,\n+\t\t\t  const struct config_parse_options *config_opts);\n+\n+#endif /* CONFIG_PARSE_H */\ndiff --git a/config.c b/config.c\nindex cf0785a258..9688e5eb8f 100644\n--- a/config.c\n+++ b/config.c\n@@ -42,32 +42,6 @@\n #include \"wrapper.h\"\n #include \"write-or-die.h\"\n \n-struct config_source {\n-\tstruct config_source *prev;\n-\tunion {\n-\t\tFILE *file;\n-\t\tstruct config_buf {\n-\t\t\tconst char *buf;\n-\t\t\tsize_t len;\n-\t\t\tsize_t pos;\n-\t\t} buf;\n-\t} u;\n-\tenum config_origin_type origin_type;\n-\tconst char *name;\n-\tconst char *path;\n-\tint linenr;\n-\tint eof;\n-\tsize_t total_len;\n-\tstruct strbuf value;\n-\tstruct strbuf var;\n-\tunsigned subsection_case_sensitive : 1;\n-\n-\tint (*do_fgetc)(struct config_source *c);\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 static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -82,47 +56,6 @@ static int zlib_compression_seen;\n  */\n static struct config_set protected_config;\n \n-static int config_file_fgetc(struct config_source *conf)\n-{\n-\treturn getc_unlocked(conf->u.file);\n-}\n-\n-static int config_file_ungetc(int c, struct config_source *conf)\n-{\n-\treturn ungetc(c, conf->u.file);\n-}\n-\n-static long config_file_ftell(struct config_source *conf)\n-{\n-\treturn ftell(conf->u.file);\n-}\n-\n-\n-static int config_buf_fgetc(struct config_source *conf)\n-{\n-\tif (conf->u.buf.pos < conf->u.buf.len)\n-\t\treturn conf->u.buf.buf[conf->u.buf.pos++];\n-\n-\treturn EOF;\n-}\n-\n-static int config_buf_ungetc(int c, struct config_source *conf)\n-{\n-\tif (conf->u.buf.pos > 0) {\n-\t\tconf->u.buf.pos--;\n-\t\tif (conf->u.buf.buf[conf->u.buf.pos] != c)\n-\t\t\tBUG(\"config_buf can only ungetc the same character\");\n-\t\treturn c;\n-\t}\n-\n-\treturn EOF;\n-}\n-\n-static long config_buf_ftell(struct config_source *conf)\n-{\n-\treturn conf->u.buf.pos;\n-}\n-\n struct config_include_data {\n \tint depth;\n \tconfig_fn_t fn;\n@@ -528,81 +461,6 @@ void git_config_push_env(const char *spec)\n \tfree(key);\n }\n \n-static inline int iskeychar(int c)\n-{\n-\treturn isalnum(c) || c == '-';\n-}\n-\n-/*\n- * Auxiliary function to sanity-check and split the key into the section\n- * identifier and variable name.\n- *\n- * Returns 0 on success, CONFIG_INVALID_KEY when there is an invalid character\n- * in the key and CONFIG_NO_SECTION_OR_NAME if there is no section name in the\n- * key.\n- *\n- * store_key - pointer to char* which will hold a copy of the key with\n- *             lowercase section and variable name\n- * baselen - pointer to size_t which will hold the length of the\n- *           section + subsection part, can be NULL\n- */\n-int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)\n-{\n-\tsize_t i, baselen;\n-\tint dot;\n-\tconst char *last_dot = strrchr(key, '.');\n-\n-\t/*\n-\t * Since \"key\" actually contains the section name and the real\n-\t * key name separated by a dot, we have to know where the dot is.\n-\t */\n-\n-\tif (last_dot == NULL || last_dot == key) {\n-\t\terror(_(\"key does not contain a section: %s\"), key);\n-\t\treturn CONFIG_NO_SECTION_OR_NAME;\n-\t}\n-\n-\tif (!last_dot[1]) {\n-\t\terror(_(\"key does not contain variable name: %s\"), key);\n-\t\treturn CONFIG_NO_SECTION_OR_NAME;\n-\t}\n-\n-\tbaselen = last_dot - key;\n-\tif (baselen_)\n-\t\t*baselen_ = baselen;\n-\n-\t/*\n-\t * Validate the key and while at it, lower case it for matching.\n-\t */\n-\t*store_key = xmallocz(strlen(key));\n-\n-\tdot = 0;\n-\tfor (i = 0; key[i]; i++) {\n-\t\tunsigned char c = key[i];\n-\t\tif (c == '.')\n-\t\t\tdot = 1;\n-\t\t/* Leave the extended basename untouched.. */\n-\t\tif (!dot || i > baselen) {\n-\t\t\tif (!iskeychar(c) ||\n-\t\t\t    (i == baselen + 1 && !isalpha(c))) {\n-\t\t\t\terror(_(\"invalid key: %s\"), key);\n-\t\t\t\tgoto out_free_ret_1;\n-\t\t\t}\n-\t\t\tc = tolower(c);\n-\t\t} else if (c == '\\n') {\n-\t\t\terror(_(\"invalid key (newline): %s\"), key);\n-\t\t\tgoto out_free_ret_1;\n-\t\t}\n-\t\t(*store_key)[i] = c;\n-\t}\n-\n-\treturn 0;\n-\n-out_free_ret_1:\n-\tFREE_AND_NULL(*store_key);\n-\treturn CONFIG_INVALID_KEY;\n-}\n-\n static int config_parse_pair(const char *key, const char *value,\n \t\t\t     struct key_value_info *kvi,\n \t\t\t     config_fn_t fn, void *data)\n@@ -787,251 +645,6 @@ 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-{\n-\tint c = cs->do_fgetc(cs);\n-\n-\tif (c == '\\r') {\n-\t\t/* DOS like systems */\n-\t\tc = cs->do_fgetc(cs);\n-\t\tif (c != '\\n') {\n-\t\t\tif (c != EOF)\n-\t\t\t\tcs->do_ungetc(c, cs);\n-\t\t\tc = '\\r';\n-\t\t}\n-\t}\n-\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-\t\t * overflows. Note that we can't return an error specifically,\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\treturn 0;\n-\t}\n-\n-\tif (c == '\\n')\n-\t\tcs->linenr++;\n-\tif (c == EOF) {\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 *cs)\n-{\n-\tint quote = 0, comment = 0, space = 0;\n-\n-\tstrbuf_reset(&cs->value);\n-\tfor (;;) {\n-\t\tint c = get_next_char(cs);\n-\t\tif (c == '\\n') {\n-\t\t\tif (quote) {\n-\t\t\t\tcs->linenr--;\n-\t\t\t\treturn NULL;\n-\t\t\t}\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 (cs->value.len)\n-\t\t\t\tspace++;\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (!quote) {\n-\t\t\tif (c == ';' || c == '#') {\n-\t\t\t\tcomment = 1;\n-\t\t\t\tcontinue;\n-\t\t\t}\n-\t\t}\n-\t\tfor (; space; space--)\n-\t\t\tstrbuf_addch(&cs->value, ' ');\n-\t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char(cs);\n-\t\t\tswitch (c) {\n-\t\t\tcase '\\n':\n-\t\t\t\tcontinue;\n-\t\t\tcase 't':\n-\t\t\t\tc = '\\t';\n-\t\t\t\tbreak;\n-\t\t\tcase 'b':\n-\t\t\t\tc = '\\b';\n-\t\t\t\tbreak;\n-\t\t\tcase 'n':\n-\t\t\t\tc = '\\n';\n-\t\t\t\tbreak;\n-\t\t\t/* Some characters escape as themselves */\n-\t\t\tcase '\\\\': case '\"':\n-\t\t\t\tbreak;\n-\t\t\t/* Reject unknown escape sequences */\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\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}\n-}\n-\n-static int get_value(struct config_source *cs, struct key_value_info *kvi,\n-\t\t     config_fn_t fn, void *data, struct strbuf *name)\n-{\n-\tint c;\n-\tchar *value;\n-\tint ret;\n-\tstruct config_context ctx = {\n-\t\t.kvi = kvi,\n-\t};\n-\n-\t/* Get the full name */\n-\tfor (;;) {\n-\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-\t\tstrbuf_addch(name, tolower(c));\n-\t}\n-\n-\twhile (c == ' ' || c == '\\t')\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(cs);\n-\t\tif (!value)\n-\t\t\treturn -1;\n-\t}\n-\t/*\n-\t * We already consumed the \\n, but we need linenr to point to\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-\tkvi->linenr = cs->linenr;\n-\tret = fn(name->buf, value, &ctx, data);\n-\tif (ret >= 0)\n-\t\tcs->linenr++;\n-\treturn ret;\n-}\n-\n-static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n-\t\t\t\t int c)\n-{\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(cs);\n-\t} while (isspace(c));\n-\n-\t/* We require the format to be '[base \"extension\"]' */\n-\tif (c != '\"')\n-\t\treturn -1;\n-\tstrbuf_addch(name, '.');\n-\n-\tfor (;;) {\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(cs);\n-\t\t\tif (c == '\\n')\n-\t\t\t\tgoto error_incomplete_line;\n-\t\t}\n-\t\tstrbuf_addch(name, c);\n-\t}\n-\n-\t/* Final ']' */\n-\tif (get_next_char(cs) != ']')\n-\t\treturn -1;\n-\treturn 0;\n-error_incomplete_line:\n-\tcs->linenr--;\n-\treturn -1;\n-}\n-\n-static int get_base_var(struct config_source *cs, struct strbuf *name)\n-{\n-\tcs->subsection_case_sensitive = 1;\n-\tfor (;;) {\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(cs, name, c);\n-\t\tif (!iskeychar(c) && c != '.')\n-\t\t\treturn -1;\n-\t\tstrbuf_addch(name, tolower(c));\n-\t}\n-}\n-\n-struct parse_event_data {\n-\tenum config_event_t previous_type;\n-\tsize_t previous_offset;\n-\tconst struct config_parse_options *opts;\n-};\n-\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-\tif (!data->opts || !data->opts->event_fn)\n-\t\treturn 0;\n-\n-\tif (type == CONFIG_EVENT_WHITESPACE &&\n-\t    data->previous_type == type)\n-\t\treturn 0;\n-\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-\t * we will already have advanced to the next event.\n-\t */\n-\tif (type != CONFIG_EVENT_EOF)\n-\t\toffset--;\n-\n-\tif (data->previous_type != CONFIG_EVENT_EOF &&\n-\t    data->opts->event_fn(data->previous_type, data->previous_offset,\n-\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n-\t\treturn -1;\n-\n-\tdata->previous_type = type;\n-\tdata->previous_offset = offset;\n-\n-\treturn 0;\n-}\n-\n-static void kvi_from_source(struct config_source *cs,\n-\t\t\t    enum config_scope scope,\n-\t\t\t    struct key_value_info *out)\n-{\n-\tout->filename = strintern(cs->name);\n-\tout->origin_type = cs->origin_type;\n-\tout->linenr = cs->linenr;\n-\tout->scope = scope;\n-\tout->path = cs->path;\n-}\n-\n int git_config_err_fn(enum config_event_t type, size_t begin_offset UNUSED,\n \t\t      size_t end_offset UNUSED, struct config_source *cs,\n \t\t      void *data)\n@@ -1082,104 +695,6 @@ int git_config_err_fn(enum config_event_t type, size_t begin_offset UNUSED,\n \treturn error_return;\n }\n \n-static int git_parse_source(struct config_source *cs, config_fn_t fn,\n-\t\t\t    struct key_value_info *kvi, void *data,\n-\t\t\t    const struct config_parse_options *opts)\n-{\n-\tint comment = 0;\n-\tsize_t baselen = 0;\n-\tstruct strbuf *var = &cs->var;\n-\n-\t/* U+FEFF Byte Order Mark in UTF8 */\n-\tconst char *bomptr = utf8_bom;\n-\n-\t/* For the parser event callback */\n-\tstruct parse_event_data event_data = {\n-\t\tCONFIG_EVENT_EOF, 0, opts\n-\t};\n-\n-\tfor (;;) {\n-\t\tint c;\n-\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-\t\t\t * own, but e.g. Windows Notepad will do it happily. */\n-\t\t\tif (c == (*bomptr & 0377)) {\n-\t\t\t\tbomptr++;\n-\t\t\t\tcontinue;\n-\t\t\t} else {\n-\t\t\t\t/* Do not tolerate partial BOM. */\n-\t\t\t\tif (bomptr != utf8_bom)\n-\t\t\t\t\tbreak;\n-\t\t\t\t/* No BOM at file beginning. Cool. */\n-\t\t\t\tbomptr = NULL;\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\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\t\treturn -1;\n-\t\t\tcomment = 0;\n-\t\t\tcontinue;\n-\t\t}\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\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\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\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\t\tbreak;\n-\t\t\tstrbuf_addch(var, '.');\n-\t\t\tbaselen = var->len;\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (!isalpha(c))\n-\t\t\tbreak;\n-\n-\t\tif (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)\n-\t\t\treturn -1;\n-\n-\t\t/*\n-\t\t * Truncate the var name back to the section header\n-\t\t * stem prior to grabbing the suffix part of the name\n-\t\t * and the value.\n-\t\t */\n-\t\tstrbuf_setlen(var, baselen);\n-\t\tstrbuf_addch(var, tolower(c));\n-\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n-\t\t\tbreak;\n-\t}\n-\n-\t/*\n-\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n-\t * in order for our callback to receive the error event, we have to call\n-\t * do_event twice\n-\t */\n-\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n-\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n-\treturn -1;\n-}\n-\n static uintmax_t get_unit_factor(const char *end)\n {\n \tif (!*end)\n@@ -1973,83 +1488,6 @@ int git_default_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-/*\n- * All source specific fields in the union, die_on_error, name and the callbacks\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,\n-\t\t\t  void *data, enum config_scope scope,\n-\t\t\t  const struct config_parse_options *opts)\n-{\n-\tstruct key_value_info kvi = KVI_INIT;\n-\tint ret;\n-\n-\t/* push config-file parsing state stack */\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-\tkvi_from_source(top, scope, &kvi);\n-\n-\tret = git_parse_source(top, fn, &kvi, data, opts);\n-\n-\tstrbuf_release(&top->value);\n-\tstrbuf_release(&top->var);\n-\n-\treturn ret;\n-}\n-\n-static int do_config_from_file(config_fn_t fn,\n-\t\t\t       const enum config_origin_type origin_type,\n-\t\t\t       const char *name, const char *path, FILE *f,\n-\t\t\t       void *data, enum config_scope scope,\n-\t\t\t       const struct config_parse_options *opts)\n-{\n-\tstruct config_source top = CONFIG_SOURCE_INIT;\n-\tint ret;\n-\n-\ttop.u.file = f;\n-\ttop.origin_type = origin_type;\n-\ttop.name = name;\n-\ttop.path = path;\n-\ttop.do_fgetc = config_file_fgetc;\n-\ttop.do_ungetc = config_file_ungetc;\n-\ttop.do_ftell = config_file_ftell;\n-\n-\tflockfile(f);\n-\tret = do_config_from(&top, fn, data, scope, opts);\n-\tfunlockfile(f);\n-\treturn ret;\n-}\n-\n-static int git_config_from_stdin(config_fn_t fn, void *data,\n-\t\t\t\t enum config_scope scope,\n-\t\t\t\t const struct config_parse_options *config_opts)\n-{\n-\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n-\t\t\t\t   data, scope, config_opts);\n-}\n-\n-int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n-\t\t\t\t      void *data, enum config_scope scope,\n-\t\t\t\t      const struct config_parse_options *opts)\n-{\n-\tint ret = -1;\n-\tFILE *f;\n-\n-\tif (!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, scope, opts);\n-\t\tfclose(f);\n-\t}\n-\treturn ret;\n-}\n-\n int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n {\n \tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n@@ -2058,27 +1496,6 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n \t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, &config_opts);\n }\n \n-int git_config_from_mem(config_fn_t fn,\n-\t\t\tconst enum config_origin_type origin_type,\n-\t\t\tconst char *name, const char *buf, size_t len,\n-\t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_parse_options *opts)\n-{\n-\tstruct config_source top = CONFIG_SOURCE_INIT;\n-\n-\ttop.u.buf.buf = buf;\n-\ttop.u.buf.len = len;\n-\ttop.u.buf.pos = 0;\n-\ttop.origin_type = origin_type;\n-\ttop.name = name;\n-\ttop.path = NULL;\n-\ttop.do_fgetc = config_buf_fgetc;\n-\ttop.do_ungetc = config_buf_ungetc;\n-\ttop.do_ftell = config_buf_ftell;\n-\n-\treturn do_config_from(&top, fn, data, scope, opts);\n-}\n-\n int git_config_from_blob_oid(config_fn_t fn,\n \t\t\t      const char *name,\n \t\t\t      struct repository *repo,\ndiff --git a/config.h b/config.h\nindex 1aed02cd5d..3bad5e1c32 100644\n--- a/config.h\n+++ b/config.h\n@@ -4,7 +4,7 @@\n #include \"hashmap.h\"\n #include \"string-list.h\"\n #include \"repository.h\"\n-\n+#include \"config-parse.h\"\n \n /**\n  * The config API gives callers a way to access Git configuration files\n@@ -23,9 +23,6 @@\n \n struct object_id;\n \n-/* git_config_parse_key() returns these: */\n-#define CONFIG_INVALID_KEY 1\n-#define CONFIG_NO_SECTION_OR_NAME 2\n /* git_config_set_gently(), git_config_set_multivar_gently() return the above or these: */\n #define CONFIG_NO_LOCK -1\n #define CONFIG_INVALID_FILE 3\n@@ -36,17 +33,6 @@ struct object_id;\n \n #define CONFIG_REGEX_NONE ((void *)1)\n \n-enum config_scope {\n-\tCONFIG_SCOPE_UNKNOWN = 0,\n-\tCONFIG_SCOPE_SYSTEM,\n-\tCONFIG_SCOPE_GLOBAL,\n-\tCONFIG_SCOPE_LOCAL,\n-\tCONFIG_SCOPE_WORKTREE,\n-\tCONFIG_SCOPE_COMMAND,\n-\tCONFIG_SCOPE_SUBMODULE,\n-};\n-const char *config_scope_name(enum config_scope scope);\n-\n struct git_config_source {\n \tunsigned int use_stdin:1;\n \tconst char *file;\n@@ -54,46 +40,6 @@ struct git_config_source {\n \tenum config_scope scope;\n };\n \n-enum config_origin_type {\n-\tCONFIG_ORIGIN_UNKNOWN = 0,\n-\tCONFIG_ORIGIN_BLOB,\n-\tCONFIG_ORIGIN_FILE,\n-\tCONFIG_ORIGIN_STDIN,\n-\tCONFIG_ORIGIN_SUBMODULE_BLOB,\n-\tCONFIG_ORIGIN_CMDLINE\n-};\n-\n-enum config_event_t {\n-\tCONFIG_EVENT_SECTION,\n-\tCONFIG_EVENT_ENTRY,\n-\tCONFIG_EVENT_WHITESPACE,\n-\tCONFIG_EVENT_COMMENT,\n-\tCONFIG_EVENT_EOF,\n-\tCONFIG_EVENT_ERROR\n-};\n-\n-struct config_source;\n-/*\n- * The parser event function (if not NULL) is called with the event type and\n- * the begin/end offsets of the parsed elements.\n- *\n- * Note: for CONFIG_EVENT_ENTRY (i.e. config variables), the trailing newline\n- * character is considered part of the element.\n- */\n-typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n-\t\t\t\t\tsize_t begin_offset, size_t end_offset,\n-\t\t\t\t\tstruct config_source *cs,\n-\t\t\t\t\tvoid *event_fn_data);\n-\n-struct config_parse_options {\n-\t/*\n-\t * event_fn and event_fn_data are for internal use only. Handles events\n-\t * emitted by the config parser.\n-\t */\n-\tconfig_parser_event_fn_t event_fn;\n-\tvoid *event_fn_data;\n-};\n-\n #define CP_OPTS_INIT(error_action) { \\\n \t.event_fn = git_config_err_fn, \\\n \t.event_fn_data = (enum config_error_action []){(error_action)}, \\\n@@ -126,59 +72,8 @@ enum config_error_action {\n int git_config_err_fn(enum config_event_t type, size_t begin_offset,\n \t\t      size_t end_offset, struct config_source *cs,\n \t\t      void *event_fn_data);\n-\n-/* Config source metadata for a given config key-value pair */\n-struct key_value_info {\n-\tconst char *filename;\n-\tint linenr;\n-\tenum config_origin_type origin_type;\n-\tenum config_scope scope;\n-\tconst char *path;\n-};\n-#define KVI_INIT { \\\n-\t.filename = NULL, \\\n-\t.linenr = -1, \\\n-\t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n-\t.scope = CONFIG_SCOPE_UNKNOWN, \\\n-\t.path = NULL, \\\n-}\n-\n-/* Captures additional information that a config callback can use. */\n-struct config_context {\n-\t/* Config source metadata for key and value. */\n-\tconst struct key_value_info *kvi;\n-};\n-#define CONFIG_CONTEXT_INIT { 0 }\n-\n-/**\n- * A config callback function takes four parameters:\n- *\n- * - the name of the parsed variable. This is in canonical \"flat\" form: the\n- *   section, subsection, and variable segments will be separated by dots,\n- *   and the section and variable segments will be all lowercase. E.g.,\n- *   `core.ignorecase`, `diff.SomeType.textconv`.\n- *\n- * - the value of the found variable, as a string. If the variable had no\n- *   value specified, the value will be NULL (typically this means it\n- *   should be interpreted as boolean true).\n- *\n- * - the 'config context', that is, additional information about the config\n- *   iteration operation provided by the config machinery. For example, this\n- *   includes information about the config source being parsed (e.g. the\n- *   filename).\n- *\n- * - a void pointer passed in by the caller of the config API; this can\n- *   contain callback-specific data\n- *\n- * A config callback should return 0 for success, or -1 if the variable\n- * could not be parsed properly.\n- */\n-typedef int (*config_fn_t)(const char *, const char *,\n-\t\t\t   const struct config_context *, void *);\n-\n int git_default_config(const char *, const char *,\n \t\t       const struct config_context *, void *);\n-\n /**\n  * Read a specific file in git-config format.\n  * This function takes the same callback and data parameters as `git_config`.\n@@ -186,16 +81,6 @@ int git_default_config(const char *, const char *,\n  * Unlike git_config(), this function does not respect includes.\n  */\n int git_config_from_file(config_fn_t fn, const char *, void *);\n-\n-int git_config_from_file_with_options(config_fn_t fn, const char *,\n-\t\t\t\t      void *, enum config_scope,\n-\t\t\t\t      const struct config_parse_options *);\n-int git_config_from_mem(config_fn_t fn,\n-\t\t\tconst enum config_origin_type,\n-\t\t\tconst char *name,\n-\t\t\tconst char *buf, size_t len,\n-\t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_parse_options *opts);\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     struct repository *repo,\n \t\t\t     const struct object_id *oid, void *data,\n@@ -333,8 +218,6 @@ int repo_config_set_worktree_gently(struct repository *, const char *, const cha\n  */\n void git_config_set(const char *, const char *);\n \n-int git_config_parse_key(const char *, char **, size_t *);\n-\n /*\n  * The following macros specify flag bits that alter the behavior\n  * of the git_config_set_multivar*() methods.\n-- \n2.41.0.585.gd2178a4bd4-goog\n\n"},{"id":"480170","messageId":"20230804213457.1174493-1-jonathantanmy@google.com","threadId":"60017","inReplyTo":"20230731234910.94149-4-chooglen@google.com","subject":"Re: [RFC PATCH v1.5 3/5] config: report config parse errors using cb","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-08-04T21:34:57Z","receivedAt":"2023-08-04T21:35:03Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Glen Choo <chooglen@google.com> writes:\n> +\t/*\n> +\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n> +\t * in order for our callback to receive the error event, we have to call\n> +\t * do_event twice\n> +\t */\n> +\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n> +\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n> +\treturn -1;\n>  }\n\nI think this is because do_event() uses the current position in the\nconfig source as the end_offset and emits the previous event if there is\none (that is, if do_event() has been called before). It can't emit the\ncurrent event because it still does not know what the current event's\nend_offset is.\n\nI also noticed some more difficult-to-understand things, like _EOF and\n_ERROR are never emitted as of current master (i.e. without this patch\nset) because they are always the last events to be passed to do_event().\nA refactoring to have separate \"do\" and \"flush\" would make things\nclearer.\n \n"},{"id":"480978","messageId":"cover.1692827403.git.steadmon@google.com","threadId":"60017","inReplyTo":"pull.1551.git.git.1689891436.gitgitgadget@gmail.com","subject":"[PATCH v2 0/4] config-parse: create config parsing library","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-08-23T21:53:47Z","receivedAt":"2023-08-23T21:54:58Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"I'll be taking over this series from Glen (thank you for the work so\nfar).\n\nHere's what I see as the current open questions:\n- Do we need a cleanup of return values in config.c? If so, does it need\n  to be part of this series?\n- Are we OK leaving the include machinery in config.c?\n- If we expect non-Git-CLI projects to use config-parse as a library,\n  should we provide alternative error-handling callbacks for (assumed)\n  use cases, even if they won't be directly used in the Git project?\n  (I lean to \"No\", but I want to make sure the option is considered.)\n- Does this series need to include the config-parse.c:do_event()\n  refactor Jonathan Tan mentioned in [1]?\n\n[1]: https://lore.kernel.org/git/20230804213457.1174493-1-jonathantanmy@google.com/\n\nChanges since v1.5:\n- Dropped patch 1/5: config: return positive from git_config_parse_key()\n\n\nGlen Choo (4):\n  config: split out config_parse_options\n  config: report config parse errors using cb\n  config.c: accept config_parse_options in git_config_from_stdin\n  config-parse: split library out of config.[c|h]\n\n Makefile           |   1 +\n builtin/config.c   |   4 +-\n bundle-uri.c       |   4 +-\n config-parse.c     | 561 ++++++++++++++++++++++++++++++++++++++\n config-parse.h     | 155 +++++++++++\n config.c           | 655 ++++-----------------------------------------\n config.h           | 134 +---------\n fsck.c             |   4 +-\n submodule-config.c |   9 +-\n 9 files changed, 795 insertions(+), 732 deletions(-)\n create mode 100644 config-parse.c\n create mode 100644 config-parse.h\n\nRange-diff against v1:\n1:  9924481630 < -:  ---------- config: return positive from git_config_parse_key()\n-:  ---------- > 1:  5c676fbac3 config: split out config_parse_options\n-:  ---------- > 2:  cb92a1f2e3 config: report config parse errors using cb\n-:  ---------- > 3:  e034d0780c config.c: accept config_parse_options in git_config_from_stdin\n-:  ---------- > 4:  74c5dcd5a2 config-parse: split library out of config.[c|h]\n\nbase-commit: aa9166bcc0ba654fc21f198a30647ec087f733ed\n-- \n2.42.0.rc1.204.g551eb34607-goog\n\n"},{"id":"480979","messageId":"5c676fbac370a2ba9d499ae2ef003e75b1d70081.1692827403.git.steadmon@google.com","threadId":"60017","inReplyTo":"cover.1692827403.git.steadmon@google.com","subject":"[PATCH v2 1/4] config: split out config_parse_options","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-08-23T21:53:48Z","receivedAt":"2023-08-23T21:54:58Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\n\"struct config_options\" is a disjoint set of options options used by the\nconfig parser (e.g. event listners) and options used by\nconfig_with_options() (e.g. to handle includes, choose which config\nfiles to parse). Split parser-only options into config_parse_options.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n bundle-uri.c |  2 +-\n config.c     | 14 +++++++-------\n config.h     | 37 ++++++++++++++++++++-----------------\n fsck.c       |  2 +-\n 4 files changed, 29 insertions(+), 26 deletions(-)\n\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex 4b5c49b93d..f93ca6a486 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -237,7 +237,7 @@ int bundle_uri_parse_config_format(const char *uri,\n \t\t\t\t   struct bundle_list *list)\n {\n \tint result;\n-\tstruct config_options opts = {\n+\tstruct config_parse_options opts = {\n \t\t.error_action = CONFIG_ERROR_ERROR,\n \t};\n \ndiff --git a/config.c b/config.c\nindex 85c5f35132..1518f70fc2 100644\n--- a/config.c\n+++ b/config.c\n@@ -982,7 +982,7 @@ static int get_base_var(struct config_source *cs, struct strbuf *name)\n struct parse_event_data {\n \tenum config_event_t previous_type;\n \tsize_t previous_offset;\n-\tconst struct config_options *opts;\n+\tconst struct config_parse_options *opts;\n };\n \n static int do_event(struct config_source *cs, enum config_event_t type,\n@@ -1030,7 +1030,7 @@ static void kvi_from_source(struct config_source *cs,\n \n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t\t    struct key_value_info *kvi, void *data,\n-\t\t\t    const struct config_options *opts)\n+\t\t\t    const struct config_parse_options *opts)\n {\n \tint comment = 0;\n \tsize_t baselen = 0;\n@@ -1967,7 +1967,7 @@ int git_default_config(const char *var, const char *value,\n  */\n static int do_config_from(struct config_source *top, config_fn_t fn,\n \t\t\t  void *data, enum config_scope scope,\n-\t\t\t  const struct config_options *opts)\n+\t\t\t  const struct config_parse_options *opts)\n {\n \tstruct key_value_info kvi = KVI_INIT;\n \tint ret;\n@@ -1992,7 +1992,7 @@ static int do_config_from_file(config_fn_t fn,\n \t\t\t       const enum config_origin_type origin_type,\n \t\t\t       const char *name, const char *path, FILE *f,\n \t\t\t       void *data, enum config_scope scope,\n-\t\t\t       const struct config_options *opts)\n+\t\t\t       const struct config_parse_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n@@ -2021,7 +2021,7 @@ static int git_config_from_stdin(config_fn_t fn, void *data,\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \t\t\t\t      void *data, enum config_scope scope,\n-\t\t\t\t      const struct config_options *opts)\n+\t\t\t\t      const struct config_parse_options *opts)\n {\n \tint ret = -1;\n \tFILE *f;\n@@ -2047,7 +2047,7 @@ int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type origin_type,\n \t\t\tconst char *name, const char *buf, size_t len,\n \t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_options *opts)\n+\t\t\tconst struct config_parse_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \n@@ -3380,7 +3380,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \t\tstruct stat st;\n \t\tsize_t copy_begin, copy_end;\n \t\tint i, new_line = 0;\n-\t\tstruct config_options opts;\n+\t\tstruct config_parse_options opts;\n \n \t\tif (!value_pattern)\n \t\t\tstore.value_pattern = NULL;\ndiff --git a/config.h b/config.h\nindex 6332d74904..2537516446 100644\n--- a/config.h\n+++ b/config.h\n@@ -85,6 +85,21 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n \t\t\t\t\tstruct config_source *cs,\n \t\t\t\t\tvoid *event_fn_data);\n \n+struct config_parse_options {\n+\tenum config_error_action {\n+\t\tCONFIG_ERROR_UNSET = 0, /* use source-specific default */\n+\t\tCONFIG_ERROR_DIE, /* die() on error */\n+\t\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n+\t\tCONFIG_ERROR_SILENT, /* return -1 */\n+\t} error_action;\n+\t/*\n+\t * event_fn and event_fn_data are for internal use only. Handles events\n+\t * emitted by the config parser.\n+\t */\n+\tconfig_parser_event_fn_t event_fn;\n+\tvoid *event_fn_data;\n+};\n+\n struct config_options {\n \tunsigned int respect_includes : 1;\n \tunsigned int ignore_repo : 1;\n@@ -92,6 +107,9 @@ struct config_options {\n \tunsigned int ignore_cmdline : 1;\n \tunsigned int system_gently : 1;\n \n+\tconst char *commondir;\n+\tconst char *git_dir;\n+\tstruct config_parse_options parse_options;\n \t/*\n \t * For internal use. Include all includeif.hasremoteurl paths without\n \t * checking if the repo has that remote URL, and when doing so, verify\n@@ -99,21 +117,6 @@ struct config_options {\n \t * themselves.\n \t */\n \tunsigned int unconditional_remote_url : 1;\n-\n-\tconst char *commondir;\n-\tconst char *git_dir;\n-\t/*\n-\t * event_fn and event_fn_data are for internal use only. Handles events\n-\t * emitted by the config parser.\n-\t */\n-\tconfig_parser_event_fn_t event_fn;\n-\tvoid *event_fn_data;\n-\tenum config_error_action {\n-\t\tCONFIG_ERROR_UNSET = 0, /* use source-specific default */\n-\t\tCONFIG_ERROR_DIE, /* die() on error */\n-\t\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n-\t\tCONFIG_ERROR_SILENT, /* return -1 */\n-\t} error_action;\n };\n \n /* Config source metadata for a given config key-value pair */\n@@ -178,13 +181,13 @@ int git_config_from_file(config_fn_t fn, const char *, void *);\n \n int git_config_from_file_with_options(config_fn_t fn, const char *,\n \t\t\t\t      void *, enum config_scope,\n-\t\t\t\t      const struct config_options *);\n+\t\t\t\t      const struct config_parse_options *);\n int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type,\n \t\t\tconst char *name,\n \t\t\tconst char *buf, size_t len,\n \t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_options *opts);\n+\t\t\tconst struct config_parse_options *opts);\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     struct repository *repo,\n \t\t\t     const struct object_id *oid, void *data,\ndiff --git a/fsck.c b/fsck.c\nindex 3be86616c5..522ee1c18a 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1219,7 +1219,7 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\treturn 0;\n \n \tif (oidset_contains(&options->gitmodules_found, oid)) {\n-\t\tstruct config_options config_opts = { 0 };\n+\t\tstruct config_parse_options config_opts = { 0 };\n \t\tstruct fsck_gitmodules_data data;\n \n \t\toidset_insert(&options->gitmodules_done, oid);\n-- \n2.42.0.rc1.204.g551eb34607-goog\n\n"},{"id":"480980","messageId":"e034d0780c886a334c51bc91fe8f145cbba65353.1692827403.git.steadmon@google.com","threadId":"60017","inReplyTo":"cover.1692827403.git.steadmon@google.com","subject":"[PATCH v2 3/4] config.c: accept config_parse_options in git_config_from_stdin","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-08-23T21:53:50Z","receivedAt":"2023-08-23T21:54:58Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nA later commit will move git_config_from_stdin() to a library, so it\nwill need to accept event listeners.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n config.c | 10 +++++-----\n 1 file changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 6cf4dafc6c..40cc9dbc40 100644\n--- a/config.c\n+++ b/config.c\n@@ -2024,12 +2024,11 @@ static int do_config_from_file(config_fn_t fn,\n }\n \n static int git_config_from_stdin(config_fn_t fn, void *data,\n-\t\t\t\t enum config_scope scope)\n+\t\t\t\t enum config_scope scope,\n+\t\t\t\t const struct config_parse_options *config_opts)\n {\n-\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n-\n \treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n-\t\t\t\t   data, scope, &config_opts);\n+\t\t\t\t   data, scope, config_opts);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2264,7 +2263,8 @@ int config_with_options(config_fn_t fn, void *data,\n \t * regular lookup sequence.\n \t */\n \tif (config_source && config_source->use_stdin) {\n-\t\tret = git_config_from_stdin(fn, data, config_source->scope);\n+\t\tret = git_config_from_stdin(fn, data, config_source->scope,\n+\t\t\t\t\t    &opts->parse_options);\n \t} else if (config_source && config_source->file) {\n \t\tret = git_config_from_file_with_options(fn, config_source->file,\n \t\t\t\t\t\t\tdata, config_source->scope,\n-- \n2.42.0.rc1.204.g551eb34607-goog\n\n"},{"id":"480981","messageId":"cb92a1f2e3098bede386d04da32fcc4f27fca51f.1692827403.git.steadmon@google.com","threadId":"60017","inReplyTo":"cover.1692827403.git.steadmon@google.com","subject":"[PATCH v2 2/4] config: report config parse errors using cb","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-08-23T21:53:49Z","receivedAt":"2023-08-23T21:54:58Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nIn a subsequent commit, config parsing will become its own library, and\nit's likely that the caller will want flexibility in handling errors\n(instead of being limited to the error handling we have in-tree).\n\nMove the Git-specific error handling into a config_parser_event_fn_t\nthat responds to config errors, and make git_parse_source() always\nreturn -1 (careful inspection shows that it was always returning -1\nalready). This makes CONFIG_ERROR_SILENT obsolete since that is\nequivalent to not specifying an error event listener. Also, remove\nCONFIG_ERROR_UNSET and the config_source 'default', since all callers\nare now expected to specify the error handling they want.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n builtin/config.c   |   4 +-\n bundle-uri.c       |   4 +-\n config.c           | 175 ++++++++++++++++++++++++++-------------------\n config.h           |  20 ++++--\n fsck.c             |   4 +-\n submodule-config.c |   9 ++-\n 6 files changed, 129 insertions(+), 87 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 1c75cbc43d..e2cf49de7a 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -42,7 +42,9 @@ static int actions, type;\n static char *default_value;\n static int end_nul;\n static int respect_includes_opt = -1;\n-static struct config_options config_options;\n+static struct config_options config_options = {\n+\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE)\n+};\n static int show_origin;\n static int show_scope;\n static int fixed_value;\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex f93ca6a486..856bffdcad 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -237,9 +237,7 @@ int bundle_uri_parse_config_format(const char *uri,\n \t\t\t\t   struct bundle_list *list)\n {\n \tint result;\n-\tstruct config_parse_options opts = {\n-\t\t.error_action = CONFIG_ERROR_ERROR,\n-\t};\n+\tstruct config_parse_options opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);\n \n \tif (!list->baseURI) {\n \t\tstruct strbuf baseURI = STRBUF_INIT;\ndiff --git a/config.c b/config.c\nindex 1518f70fc2..6cf4dafc6c 100644\n--- a/config.c\n+++ b/config.c\n@@ -55,7 +55,6 @@ struct config_source {\n \tenum config_origin_type origin_type;\n \tconst char *name;\n \tconst char *path;\n-\tenum config_error_action default_error_action;\n \tint linenr;\n \tint eof;\n \tsize_t total_len;\n@@ -185,13 +184,15 @@ static int handle_path_include(const struct key_value_info *kvi,\n \t}\n \n \tif (!access_or_die(path, R_OK, 0)) {\n+\t\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n+\n \t\tif (++inc->depth > MAX_INCLUDE_DEPTH)\n \t\t\tdie(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,\n \t\t\t    !kvi ? \"<unknown>\" :\n \t\t\t    kvi->filename ? kvi->filename :\n \t\t\t    \"the command line\");\n \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n-\t\t\t\t\t\t\tkvi->scope, NULL);\n+\t\t\t\t\t\t\tkvi->scope, &config_opts);\n \t\tinc->depth--;\n \t}\n cleanup:\n@@ -339,7 +340,9 @@ static int add_remote_url(const char *var, const char *value,\n \n static void populate_remote_urls(struct config_include_data *inc)\n {\n-\tstruct config_options opts;\n+\tstruct config_options opts = {\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t};\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n@@ -1028,6 +1031,56 @@ static void kvi_from_source(struct config_source *cs,\n \tout->path = cs->path;\n }\n \n+int git_config_err_fn(enum config_event_t type, size_t begin_offset UNUSED,\n+\t\t      size_t end_offset UNUSED, struct config_source *cs,\n+\t\t      void *data)\n+{\n+\tchar *error_msg = NULL;\n+\tint error_return = 0;\n+\tenum config_error_action *action = data;\n+\n+\tif (type != CONFIG_EVENT_ERROR)\n+\t\treturn 0;\n+\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      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      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      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       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       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      cs->linenr, cs->name);\n+\t}\n+\n+\tswitch (*action) {\n+\tcase CONFIG_ERROR_DIE:\n+\t\tdie(\"%s\", error_msg);\n+\t\tbreak;\n+\tcase CONFIG_ERROR_ERROR:\n+\t\terror_return = error(\"%s\", error_msg);\n+\t\tbreak;\n+\t}\n+\n+\tfree(error_msg);\n+\treturn error_return;\n+}\n+\n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t\t    struct key_value_info *kvi, void *data,\n \t\t\t    const struct config_parse_options *opts)\n@@ -1035,8 +1088,6 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \tint comment = 0;\n \tsize_t baselen = 0;\n \tstruct strbuf *var = &cs->var;\n-\tint error_return = 0;\n-\tchar *error_msg = NULL;\n \n \t/* U+FEFF Byte Order Mark in UTF8 */\n \tconst char *bomptr = utf8_bom;\n@@ -1118,53 +1169,14 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t\tbreak;\n \t}\n \n-\tif (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)\n-\t\treturn -1;\n-\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      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      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      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       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       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      cs->linenr, cs->name);\n-\t}\n-\n-\tswitch (opts && opts->error_action ?\n-\t\topts->error_action :\n-\t\tcs->default_error_action) {\n-\tcase CONFIG_ERROR_DIE:\n-\t\tdie(\"%s\", error_msg);\n-\t\tbreak;\n-\tcase CONFIG_ERROR_ERROR:\n-\t\terror_return = error(\"%s\", error_msg);\n-\t\tbreak;\n-\tcase CONFIG_ERROR_SILENT:\n-\t\terror_return = -1;\n-\t\tbreak;\n-\tcase CONFIG_ERROR_UNSET:\n-\t\tBUG(\"config error action unset\");\n-\t}\n-\n-\tfree(error_msg);\n-\treturn error_return;\n+\t/*\n+\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n+\t * in order for our callback to receive the error event, we have to call\n+\t * do_event twice\n+\t */\n+\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n+\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n+\treturn -1;\n }\n \n static uintmax_t get_unit_factor(const char *end)\n@@ -2001,7 +2013,6 @@ static int do_config_from_file(config_fn_t fn,\n \ttop.origin_type = origin_type;\n \ttop.name = name;\n \ttop.path = path;\n-\ttop.default_error_action = CONFIG_ERROR_DIE;\n \ttop.do_fgetc = config_file_fgetc;\n \ttop.do_ungetc = config_file_ungetc;\n \ttop.do_ftell = config_file_ftell;\n@@ -2015,8 +2026,10 @@ static int do_config_from_file(config_fn_t fn,\n static int git_config_from_stdin(config_fn_t fn, void *data,\n \t\t\t\t enum config_scope scope)\n {\n+\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n+\n \treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n-\t\t\t\t   data, scope, NULL);\n+\t\t\t\t   data, scope, &config_opts);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2039,8 +2052,10 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \n int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n {\n+\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n+\n \treturn git_config_from_file_with_options(fn, filename, data,\n-\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, NULL);\n+\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, &config_opts);\n }\n \n int git_config_from_mem(config_fn_t fn,\n@@ -2057,7 +2072,6 @@ int git_config_from_mem(config_fn_t fn,\n \ttop.origin_type = origin_type;\n \ttop.name = name;\n \ttop.path = NULL;\n-\ttop.default_error_action = CONFIG_ERROR_ERROR;\n \ttop.do_fgetc = config_buf_fgetc;\n \ttop.do_ungetc = config_buf_ungetc;\n \ttop.do_ftell = config_buf_ftell;\n@@ -2076,6 +2090,7 @@ int git_config_from_blob_oid(config_fn_t fn,\n \tchar *buf;\n \tunsigned long size;\n \tint ret;\n+\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);\n \n \tbuf = repo_read_object_file(repo, oid, &type, &size);\n \tif (!buf)\n@@ -2086,7 +2101,7 @@ int git_config_from_blob_oid(config_fn_t fn,\n \t}\n \n \tret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size,\n-\t\t\t\t  data, scope, NULL);\n+\t\t\t\t  data, scope, &config_opts);\n \tfree(buf);\n \n \treturn ret;\n@@ -2187,29 +2202,32 @@ static int do_git_config_sequence(const struct config_options *opts,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n \t\tret += git_config_from_file_with_options(fn, system_config,\n \t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n-\t\t\t\t\t\t\t NULL);\n+\t\t\t\t\t\t\t &opts->parse_options);\n \n \tgit_global_config(&user_config, &xdg_config);\n \n \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n \t\tret += git_config_from_file_with_options(fn, xdg_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL,\n+\t\t\t\t\t\t\t &opts->parse_options);\n \n \tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n \t\tret += git_config_from_file_with_options(fn, user_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL,\n+\t\t\t\t\t\t\t &opts->parse_options);\n \n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n \t\tret += git_config_from_file_with_options(fn, repo_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n+\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL,\n+\t\t\t\t\t\t\t &opts->parse_options);\n \n \tif (!opts->ignore_worktree && worktree_config &&\n \t    repo && repo->repository_format_worktree_config &&\n \t    !access_or_die(worktree_config, R_OK, 0)) {\n \t\t\tret += git_config_from_file_with_options(fn, worktree_config, data,\n \t\t\t\t\t\t\t\t CONFIG_SCOPE_WORKTREE,\n-\t\t\t\t\t\t\t\t NULL);\n+\t\t\t\t\t\t\t\t &opts->parse_options);\n \t}\n \n \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n@@ -2250,7 +2268,7 @@ int config_with_options(config_fn_t fn, void *data,\n \t} else if (config_source && config_source->file) {\n \t\tret = git_config_from_file_with_options(fn, config_source->file,\n \t\t\t\t\t\t\tdata, config_source->scope,\n-\t\t\t\t\t\t\tNULL);\n+\t\t\t\t\t\t\t&opts->parse_options);\n \t} else if (config_source && config_source->blob) {\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n \t\t\t\t\t       data, config_source->scope);\n@@ -2288,9 +2306,11 @@ static void configset_iter(struct config_set *set, config_fn_t fn, void *data)\n \n void read_early_config(config_fn_t cb, void *data)\n {\n-\tstruct config_options opts = {0};\n \tstruct strbuf commondir = STRBUF_INIT;\n \tstruct strbuf gitdir = STRBUF_INIT;\n+\tstruct config_options opts = {\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t};\n \n \topts.respect_includes = 1;\n \n@@ -2322,7 +2342,9 @@ void read_early_config(config_fn_t cb, void *data)\n  */\n void read_very_early_config(config_fn_t cb, void *data)\n {\n-\tstruct config_options opts = { 0 };\n+\tstruct config_options opts = {\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t};\n \n \topts.respect_includes = 1;\n \topts.ignore_repo = 1;\n@@ -2613,7 +2635,9 @@ int git_configset_get_pathname(struct config_set *set, const char *key, const ch\n /* Functions use to read configuration from a repository */\n static void repo_read_config(struct repository *repo)\n {\n-\tstruct config_options opts = { 0 };\n+\tstruct config_options opts = {\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t};\n \n \topts.respect_includes = 1;\n \topts.commondir = repo->commondir;\n@@ -2760,12 +2784,14 @@ int repo_config_get_pathname(struct repository *repo,\n static void read_protected_config(void)\n {\n \tstruct config_options opts = {\n-\t\t.respect_includes = 1,\n-\t\t.ignore_repo = 1,\n-\t\t.ignore_worktree = 1,\n-\t\t.system_gently = 1,\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n \t};\n \n+\topts.respect_includes = 1;\n+\topts.ignore_repo = 1;\n+\topts.ignore_worktree = 1;\n+\topts.system_gently = 1;\n+\n \tgit_configset_init(&protected_config);\n \tconfig_with_options(config_set_callback, &protected_config, NULL,\n \t\t\t    NULL, &opts);\n@@ -2976,6 +3002,7 @@ struct config_store_data {\n \t\tenum config_event_t type;\n \t\tint is_keys_section;\n \t} *parsed;\n+\tenum config_error_action error_action;\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@@ -3043,6 +3070,10 @@ static int store_aux_event(enum config_event_t type, size_t begin, size_t end,\n \t\t\tstore->seen[store->seen_nr] = store->parsed_nr;\n \t\t}\n \t}\n+\tif (type == CONFIG_EVENT_ERROR) {\n+\t\treturn git_config_err_fn(type, begin, end, cs,\n+\t\t\t\t\t &store->error_action);\n+\t}\n \n \tstore->parsed_nr++;\n \n@@ -3380,7 +3411,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \t\tstruct stat st;\n \t\tsize_t copy_begin, copy_end;\n \t\tint i, new_line = 0;\n-\t\tstruct config_parse_options opts;\n+\t\tstruct config_parse_options opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n \n \t\tif (!value_pattern)\n \t\t\tstore.value_pattern = NULL;\n@@ -3407,8 +3438,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \n \t\tALLOC_GROW(store.parsed, 1, store.parsed_alloc);\n \t\tstore.parsed[0].end = 0;\n+\t\tstore.error_action = CONFIG_ERROR_DIE;\n \n-\t\tmemset(&opts, 0, sizeof(opts));\n \t\topts.event_fn = store_aux_event;\n \t\topts.event_fn_data = &store;\n \ndiff --git a/config.h b/config.h\nindex 2537516446..8ad399580f 100644\n--- a/config.h\n+++ b/config.h\n@@ -86,12 +86,6 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n \t\t\t\t\tvoid *event_fn_data);\n \n struct config_parse_options {\n-\tenum config_error_action {\n-\t\tCONFIG_ERROR_UNSET = 0, /* use source-specific default */\n-\t\tCONFIG_ERROR_DIE, /* die() on error */\n-\t\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n-\t\tCONFIG_ERROR_SILENT, /* return -1 */\n-\t} error_action;\n \t/*\n \t * event_fn and event_fn_data are for internal use only. Handles events\n \t * emitted by the config parser.\n@@ -100,6 +94,11 @@ struct config_parse_options {\n \tvoid *event_fn_data;\n };\n \n+#define CP_OPTS_INIT(error_action) { \\\n+\t.event_fn = git_config_err_fn, \\\n+\t.event_fn_data = (enum config_error_action []){(error_action)}, \\\n+}\n+\n struct config_options {\n \tunsigned int respect_includes : 1;\n \tunsigned int ignore_repo : 1;\n@@ -119,6 +118,15 @@ struct config_options {\n \tunsigned int unconditional_remote_url : 1;\n };\n \n+enum config_error_action {\n+\tCONFIG_ERROR_DIE, /* die() on error */\n+\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n+};\n+\n+int git_config_err_fn(enum config_event_t type, size_t begin_offset,\n+\t\t      size_t end_offset, struct config_source *cs,\n+\t\t      void *event_fn_data);\n+\n /* Config source metadata for a given config key-value pair */\n struct key_value_info {\n \tconst char *filename;\ndiff --git a/fsck.c b/fsck.c\nindex 522ee1c18a..bc0ca11421 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1219,7 +1219,6 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\treturn 0;\n \n \tif (oidset_contains(&options->gitmodules_found, oid)) {\n-\t\tstruct config_parse_options config_opts = { 0 };\n \t\tstruct fsck_gitmodules_data data;\n \n \t\toidset_insert(&options->gitmodules_done, oid);\n@@ -1238,10 +1237,9 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\tdata.oid = oid;\n \t\tdata.options = options;\n \t\tdata.ret = 0;\n-\t\tconfig_opts.error_action = CONFIG_ERROR_SILENT;\n \t\tif (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB,\n \t\t\t\t\t\".gitmodules\", buf, size, &data,\n-\t\t\t\t\tCONFIG_SCOPE_UNKNOWN, &config_opts))\n+\t\t\t\t\tCONFIG_SCOPE_UNKNOWN, NULL))\n \t\t\tdata.ret |= report(options, oid, OBJ_BLOB,\n \t\t\t\t\tFSCK_MSG_GITMODULES_PARSE,\n \t\t\t\t\t\"could not parse gitmodules blob\");\ndiff --git a/submodule-config.c b/submodule-config.c\nindex b6908e295f..d97135c917 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -565,6 +565,8 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \tenum object_type type;\n \tconst struct submodule *submodule = NULL;\n \tstruct parse_config_parameter parameter;\n+\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);\n+\n \n \t/*\n \t * If any parameter except the cache is a NULL pointer just\n@@ -608,7 +610,8 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \tparameter.gitmodules_oid = &oid;\n \tparameter.overwrite = 0;\n \tgit_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf,\n-\t\t\t    config, config_size, &parameter, CONFIG_SCOPE_UNKNOWN, NULL);\n+\t\t\t    config, config_size, &parameter,\n+\t\t\t    CONFIG_SCOPE_UNKNOWN, &config_opts);\n \tstrbuf_release(&rev);\n \tfree(config);\n \n@@ -652,7 +655,9 @@ static void config_from_gitmodules(config_fn_t fn, struct repository *repo, void\n \t\tstruct git_config_source config_source = {\n \t\t\t0, .scope = CONFIG_SCOPE_SUBMODULE\n \t\t};\n-\t\tconst struct config_options opts = { 0 };\n+\t\tstruct config_options opts = {\n+\t\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t\t};\n \t\tstruct object_id oid;\n \t\tchar *file;\n \t\tchar *oidstr = NULL;\n-- \n2.42.0.rc1.204.g551eb34607-goog\n\n"},{"id":"480982","messageId":"74c5dcd5a27fd5749a0b0bedfa58e806dc78d5f0.1692827403.git.steadmon@google.com","threadId":"60017","inReplyTo":"cover.1692827403.git.steadmon@google.com","subject":"[PATCH v2 4/4] config-parse: split library out of config.[c|h]","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-08-23T21:53:51Z","receivedAt":"2023-08-23T21:54:58Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThe config parsing machinery (besides \"include\" directives) is usable by\nprograms other than Git - it works with any file written in Git config\nsyntax (IOW it doesn't rely on 'core' Git features like a repository),\nand as of the series ending at 6e8e7981eb (config: pass source to\nconfig_parser_event_fn_t, 2023-06-28), it no longer relies on global\nstate. Thus, we can and should start turning it into a library other\nprograms can use.\n\nBegin this process by splitting the config parsing code out of\nconfig.[c|h] and into config-parse.[c|h]. Do not change interfaces or\nfunction bodies, but tweak visibility and includes where appropriate,\nnamely:\n\n- git_config_from_stdin() is now non-static so that it can be seen by\n  config.c.\n\n- \"struct config_source\" is now defined in the .h file so that it can be\n  seen by config.c. And as a result, config-lib.h needs to \"#include\n  strbuf.h\".\n\nIn theory, this makes it possible for in-tree files to decide whether\nthey only need all of the config functionality or only config parsing,\nand bring in the smallest bit of functionality needed. But for now,\nthere are no in-tree files that can swap \"#include config.h\" for\n\"#include config-parse.h\". E.g. Bundle URIs would only need config\nparsing to parse bundle lists, but bundle-uri.c uses other config.h\nfunctionality like key parsing and reading repo settings.\n\nThe resulting library is usable, though it is unergonomic to do so,\ne.g. the caller needs to \"#include git-compat-util.h\" and other\ndependencies, and we don't have an easy way of linking in the required\nobjects. This isn't the end state we want for our libraries, but at\nleast we have _some_ library whose usability we can improve in future\nseries.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n Makefile       |   1 +\n config-parse.c | 561 +++++++++++++++++++++++++++++++++++++++++++++++\n config-parse.h | 155 +++++++++++++\n config.c       | 582 -------------------------------------------------\n config.h       | 119 +---------\n 5 files changed, 718 insertions(+), 700 deletions(-)\n create mode 100644 config-parse.c\n create mode 100644 config-parse.h\n\ndiff --git a/Makefile b/Makefile\nindex fb541dedc9..67e05bcee5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -992,6 +992,7 @@ LIB_OBJS += compat/obstack.o\n LIB_OBJS += compat/terminal.o\n LIB_OBJS += compat/zlib-uncompress2.o\n LIB_OBJS += config.o\n+LIB_OBJS += config-parse.o\n LIB_OBJS += connect.o\n LIB_OBJS += connected.o\n LIB_OBJS += convert.o\ndiff --git a/config-parse.c b/config-parse.c\nnew file mode 100644\nindex 0000000000..97ebd6d72b\n--- /dev/null\n+++ b/config-parse.c\n@@ -0,0 +1,561 @@\n+#include \"git-compat-util.h\"\n+#include \"strbuf.h\"\n+#include \"gettext.h\"\n+#include \"hashmap.h\"\n+#include \"utf8.h\"\n+#include \"config-parse.h\"\n+\n+static int config_file_fgetc(struct config_source *conf)\n+{\n+\treturn getc_unlocked(conf->u.file);\n+}\n+\n+static int config_file_ungetc(int c, struct config_source *conf)\n+{\n+\treturn ungetc(c, conf->u.file);\n+}\n+\n+static long config_file_ftell(struct config_source *conf)\n+{\n+\treturn ftell(conf->u.file);\n+}\n+\n+\n+static int config_buf_fgetc(struct config_source *conf)\n+{\n+\tif (conf->u.buf.pos < conf->u.buf.len)\n+\t\treturn conf->u.buf.buf[conf->u.buf.pos++];\n+\n+\treturn EOF;\n+}\n+\n+static int config_buf_ungetc(int c, struct config_source *conf)\n+{\n+\tif (conf->u.buf.pos > 0) {\n+\t\tconf->u.buf.pos--;\n+\t\tif (conf->u.buf.buf[conf->u.buf.pos] != c)\n+\t\t\tBUG(\"config_buf can only ungetc the same character\");\n+\t\treturn c;\n+\t}\n+\n+\treturn EOF;\n+}\n+\n+static long config_buf_ftell(struct config_source *conf)\n+{\n+\treturn conf->u.buf.pos;\n+}\n+\n+static inline int iskeychar(int c)\n+{\n+\treturn isalnum(c) || c == '-';\n+}\n+\n+/*\n+ * Auxiliary function to sanity-check and split the key into the section\n+ * identifier and variable name.\n+ *\n+ * Returns 0 on success, -CONFIG_INVALID_KEY when there is an invalid character\n+ * in the key and -CONFIG_NO_SECTION_OR_NAME if there is no section name in the\n+ * key.\n+ *\n+ * store_key - pointer to char* which will hold a copy of the key with\n+ *             lowercase section and variable name\n+ * baselen - pointer to size_t which will hold the length of the\n+ *           section + subsection part, can be NULL\n+ */\n+int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)\n+{\n+\tsize_t i, baselen;\n+\tint dot;\n+\tconst char *last_dot = strrchr(key, '.');\n+\n+\t/*\n+\t * Since \"key\" actually contains the section name and the real\n+\t * key name separated by a dot, we have to know where the dot is.\n+\t */\n+\n+\tif (last_dot == NULL || last_dot == key) {\n+\t\terror(_(\"key does not contain a section: %s\"), key);\n+\t\treturn -CONFIG_NO_SECTION_OR_NAME;\n+\t}\n+\n+\tif (!last_dot[1]) {\n+\t\terror(_(\"key does not contain variable name: %s\"), key);\n+\t\treturn -CONFIG_NO_SECTION_OR_NAME;\n+\t}\n+\n+\tbaselen = last_dot - key;\n+\tif (baselen_)\n+\t\t*baselen_ = baselen;\n+\n+\t/*\n+\t * Validate the key and while at it, lower case it for matching.\n+\t */\n+\t*store_key = xmallocz(strlen(key));\n+\n+\tdot = 0;\n+\tfor (i = 0; key[i]; i++) {\n+\t\tunsigned char c = key[i];\n+\t\tif (c == '.')\n+\t\t\tdot = 1;\n+\t\t/* Leave the extended basename untouched.. */\n+\t\tif (!dot || i > baselen) {\n+\t\t\tif (!iskeychar(c) ||\n+\t\t\t    (i == baselen + 1 && !isalpha(c))) {\n+\t\t\t\terror(_(\"invalid key: %s\"), key);\n+\t\t\t\tgoto out_free_ret_1;\n+\t\t\t}\n+\t\t\tc = tolower(c);\n+\t\t} else if (c == '\\n') {\n+\t\t\terror(_(\"invalid key (newline): %s\"), key);\n+\t\t\tgoto out_free_ret_1;\n+\t\t}\n+\t\t(*store_key)[i] = c;\n+\t}\n+\n+\treturn 0;\n+\n+out_free_ret_1:\n+\tFREE_AND_NULL(*store_key);\n+\treturn -CONFIG_INVALID_KEY;\n+}\n+\n+static int get_next_char(struct config_source *cs)\n+{\n+\tint c = cs->do_fgetc(cs);\n+\n+\tif (c == '\\r') {\n+\t\t/* DOS like systems */\n+\t\tc = cs->do_fgetc(cs);\n+\t\tif (c != '\\n') {\n+\t\t\tif (c != EOF)\n+\t\t\t\tcs->do_ungetc(c, cs);\n+\t\t\tc = '\\r';\n+\t\t}\n+\t}\n+\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+\t\t * overflows. Note that we can't return an error specifically,\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\treturn 0;\n+\t}\n+\n+\tif (c == '\\n')\n+\t\tcs->linenr++;\n+\tif (c == EOF) {\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 *cs)\n+{\n+\tint quote = 0, comment = 0, space = 0;\n+\n+\tstrbuf_reset(&cs->value);\n+\tfor (;;) {\n+\t\tint c = get_next_char(cs);\n+\t\tif (c == '\\n') {\n+\t\t\tif (quote) {\n+\t\t\t\tcs->linenr--;\n+\t\t\t\treturn NULL;\n+\t\t\t}\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 (cs->value.len)\n+\t\t\t\tspace++;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!quote) {\n+\t\t\tif (c == ';' || c == '#') {\n+\t\t\t\tcomment = 1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\t\tfor (; space; space--)\n+\t\t\tstrbuf_addch(&cs->value, ' ');\n+\t\tif (c == '\\\\') {\n+\t\t\tc = get_next_char(cs);\n+\t\t\tswitch (c) {\n+\t\t\tcase '\\n':\n+\t\t\t\tcontinue;\n+\t\t\tcase 't':\n+\t\t\t\tc = '\\t';\n+\t\t\t\tbreak;\n+\t\t\tcase 'b':\n+\t\t\t\tc = '\\b';\n+\t\t\t\tbreak;\n+\t\t\tcase 'n':\n+\t\t\t\tc = '\\n';\n+\t\t\t\tbreak;\n+\t\t\t/* Some characters escape as themselves */\n+\t\t\tcase '\\\\': case '\"':\n+\t\t\t\tbreak;\n+\t\t\t/* Reject unknown escape sequences */\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\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}\n+}\n+\n+static int get_value(struct config_source *cs, struct key_value_info *kvi,\n+\t\t     config_fn_t fn, void *data, struct strbuf *name)\n+{\n+\tint c;\n+\tchar *value;\n+\tint ret;\n+\tstruct config_context ctx = {\n+\t\t.kvi = kvi,\n+\t};\n+\n+\t/* Get the full name */\n+\tfor (;;) {\n+\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+\t\tstrbuf_addch(name, tolower(c));\n+\t}\n+\n+\twhile (c == ' ' || c == '\\t')\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(cs);\n+\t\tif (!value)\n+\t\t\treturn -1;\n+\t}\n+\t/*\n+\t * We already consumed the \\n, but we need linenr to point to\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+\tkvi->linenr = cs->linenr;\n+\tret = fn(name->buf, value, &ctx, data);\n+\tif (ret >= 0)\n+\t\tcs->linenr++;\n+\treturn ret;\n+}\n+\n+static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n+\t\t\t\t int c)\n+{\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(cs);\n+\t} while (isspace(c));\n+\n+\t/* We require the format to be '[base \"extension\"]' */\n+\tif (c != '\"')\n+\t\treturn -1;\n+\tstrbuf_addch(name, '.');\n+\n+\tfor (;;) {\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(cs);\n+\t\t\tif (c == '\\n')\n+\t\t\t\tgoto error_incomplete_line;\n+\t\t}\n+\t\tstrbuf_addch(name, c);\n+\t}\n+\n+\t/* Final ']' */\n+\tif (get_next_char(cs) != ']')\n+\t\treturn -1;\n+\treturn 0;\n+error_incomplete_line:\n+\tcs->linenr--;\n+\treturn -1;\n+}\n+\n+static int get_base_var(struct config_source *cs, struct strbuf *name)\n+{\n+\tcs->subsection_case_sensitive = 1;\n+\tfor (;;) {\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(cs, name, c);\n+\t\tif (!iskeychar(c) && c != '.')\n+\t\t\treturn -1;\n+\t\tstrbuf_addch(name, tolower(c));\n+\t}\n+}\n+\n+struct parse_event_data {\n+\tenum config_event_t previous_type;\n+\tsize_t previous_offset;\n+\tconst struct config_parse_options *opts;\n+};\n+\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+\tif (!data->opts || !data->opts->event_fn)\n+\t\treturn 0;\n+\n+\tif (type == CONFIG_EVENT_WHITESPACE &&\n+\t    data->previous_type == type)\n+\t\treturn 0;\n+\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+\t * we will already have advanced to the next event.\n+\t */\n+\tif (type != CONFIG_EVENT_EOF)\n+\t\toffset--;\n+\n+\tif (data->previous_type != CONFIG_EVENT_EOF &&\n+\t    data->opts->event_fn(data->previous_type, data->previous_offset,\n+\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n+\t\treturn -1;\n+\n+\tdata->previous_type = type;\n+\tdata->previous_offset = offset;\n+\n+\treturn 0;\n+}\n+\n+static void kvi_from_source(struct config_source *cs,\n+\t\t\t    enum config_scope scope,\n+\t\t\t    struct key_value_info *out)\n+{\n+\tout->filename = strintern(cs->name);\n+\tout->origin_type = cs->origin_type;\n+\tout->linenr = cs->linenr;\n+\tout->scope = scope;\n+\tout->path = cs->path;\n+}\n+\n+static int git_parse_source(struct config_source *cs, config_fn_t fn,\n+\t\t\t    struct key_value_info *kvi, void *data,\n+\t\t\t    const struct config_parse_options *opts)\n+{\n+\tint comment = 0;\n+\tsize_t baselen = 0;\n+\tstruct strbuf *var = &cs->var;\n+\n+\t/* U+FEFF Byte Order Mark in UTF8 */\n+\tconst char *bomptr = utf8_bom;\n+\n+\t/* For the parser event callback */\n+\tstruct parse_event_data event_data = {\n+\t\tCONFIG_EVENT_EOF, 0, opts\n+\t};\n+\n+\tfor (;;) {\n+\t\tint c;\n+\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+\t\t\t * own, but e.g. Windows Notepad will do it happily. */\n+\t\t\tif (c == (*bomptr & 0377)) {\n+\t\t\t\tbomptr++;\n+\t\t\t\tcontinue;\n+\t\t\t} else {\n+\t\t\t\t/* Do not tolerate partial BOM. */\n+\t\t\t\tif (bomptr != utf8_bom)\n+\t\t\t\t\tbreak;\n+\t\t\t\t/* No BOM at file beginning. Cool. */\n+\t\t\t\tbomptr = NULL;\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\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\t\treturn -1;\n+\t\t\tcomment = 0;\n+\t\t\tcontinue;\n+\t\t}\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\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\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\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\t\tbreak;\n+\t\t\tstrbuf_addch(var, '.');\n+\t\t\tbaselen = var->len;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!isalpha(c))\n+\t\t\tbreak;\n+\n+\t\tif (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)\n+\t\t\treturn -1;\n+\n+\t\t/*\n+\t\t * Truncate the var name back to the section header\n+\t\t * stem prior to grabbing the suffix part of the name\n+\t\t * and the value.\n+\t\t */\n+\t\tstrbuf_setlen(var, baselen);\n+\t\tstrbuf_addch(var, tolower(c));\n+\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n+\t\t\tbreak;\n+\t}\n+\t/*\n+\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n+\t * in order for our callback to receive the error event, we have to call\n+\t * do_event twice\n+\t */\n+\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n+\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n+\treturn -1;\n+}\n+\n+/*\n+ * All source specific fields in the union, die_on_error, name and the callbacks\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,\n+\t\t\t  void *data, enum config_scope scope,\n+\t\t\t  const struct config_parse_options *opts)\n+{\n+\tstruct key_value_info kvi = KVI_INIT;\n+\tint ret;\n+\n+\t/* push config-file parsing state stack */\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+\tkvi_from_source(top, scope, &kvi);\n+\n+\tret = git_parse_source(top, fn, &kvi, data, opts);\n+\n+\tstrbuf_release(&top->value);\n+\tstrbuf_release(&top->var);\n+\n+\treturn ret;\n+}\n+\n+static int do_config_from_file(config_fn_t fn,\n+\t\t\t       const enum config_origin_type origin_type,\n+\t\t\t       const char *name, const char *path, FILE *f,\n+\t\t\t       void *data, enum config_scope scope,\n+\t\t\t       const struct config_parse_options *opts)\n+{\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n+\tint ret;\n+\n+\ttop.u.file = f;\n+\ttop.origin_type = origin_type;\n+\ttop.name = name;\n+\ttop.path = path;\n+\ttop.do_fgetc = config_file_fgetc;\n+\ttop.do_ungetc = config_file_ungetc;\n+\ttop.do_ftell = config_file_ftell;\n+\n+\tflockfile(f);\n+\tret = do_config_from(&top, fn, data, scope, opts);\n+\tfunlockfile(f);\n+\treturn ret;\n+}\n+\n+int git_config_from_stdin(config_fn_t fn, void *data, enum config_scope scope,\n+\t\t\t  const struct config_parse_options *config_opts)\n+{\n+\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n+\t\t\t\t   data, scope, config_opts);\n+}\n+\n+int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n+\t\t\t\t      void *data, enum config_scope scope,\n+\t\t\t\t      const struct config_parse_options *opts)\n+{\n+\tint ret = -1;\n+\tFILE *f;\n+\n+\tif (!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, scope, opts);\n+\t\tfclose(f);\n+\t}\n+\treturn ret;\n+}\n+\n+int git_config_from_mem(config_fn_t fn,\n+\t\t\tconst enum config_origin_type origin_type,\n+\t\t\tconst char *name, const char *buf, size_t len,\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_parse_options *opts)\n+{\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n+\n+\ttop.u.buf.buf = buf;\n+\ttop.u.buf.len = len;\n+\ttop.u.buf.pos = 0;\n+\ttop.origin_type = origin_type;\n+\ttop.name = name;\n+\ttop.path = NULL;\n+\ttop.do_fgetc = config_buf_fgetc;\n+\ttop.do_ungetc = config_buf_ungetc;\n+\ttop.do_ftell = config_buf_ftell;\n+\n+\treturn do_config_from(&top, fn, data, scope, opts);\n+}\ndiff --git a/config-parse.h b/config-parse.h\nnew file mode 100644\nindex 0000000000..ac73a826d9\n--- /dev/null\n+++ b/config-parse.h\n@@ -0,0 +1,155 @@\n+/*\n+ * Low level config parsing.\n+ */\n+#ifndef CONFIG_PARSE_H\n+#define CONFIG_PARSE_H\n+\n+#include \"strbuf.h\"\n+\n+/* git_config_parse_key() returns these negated: */\n+#define CONFIG_INVALID_KEY 1\n+#define CONFIG_NO_SECTION_OR_NAME 2\n+\n+int git_config_parse_key(const char *, char **, size_t *);\n+\n+enum config_scope {\n+\tCONFIG_SCOPE_UNKNOWN = 0,\n+\tCONFIG_SCOPE_SYSTEM,\n+\tCONFIG_SCOPE_GLOBAL,\n+\tCONFIG_SCOPE_LOCAL,\n+\tCONFIG_SCOPE_WORKTREE,\n+\tCONFIG_SCOPE_COMMAND,\n+\tCONFIG_SCOPE_SUBMODULE,\n+};\n+const char *config_scope_name(enum config_scope scope);\n+\n+enum config_origin_type {\n+\tCONFIG_ORIGIN_UNKNOWN = 0,\n+\tCONFIG_ORIGIN_BLOB,\n+\tCONFIG_ORIGIN_FILE,\n+\tCONFIG_ORIGIN_STDIN,\n+\tCONFIG_ORIGIN_SUBMODULE_BLOB,\n+\tCONFIG_ORIGIN_CMDLINE\n+};\n+\n+enum config_event_t {\n+\tCONFIG_EVENT_SECTION,\n+\tCONFIG_EVENT_ENTRY,\n+\tCONFIG_EVENT_WHITESPACE,\n+\tCONFIG_EVENT_COMMENT,\n+\tCONFIG_EVENT_EOF,\n+\tCONFIG_EVENT_ERROR\n+};\n+\n+struct config_source;\n+/*\n+ * The parser event function (if not NULL) is called with the event type and\n+ * the begin/end offsets of the parsed elements.\n+ *\n+ * Note: for CONFIG_EVENT_ENTRY (i.e. config variables), the trailing newline\n+ * character is considered part of the element.\n+ */\n+typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n+\t\t\t\t\tsize_t begin_offset, size_t end_offset,\n+\t\t\t\t\tstruct config_source *cs,\n+\t\t\t\t\tvoid *event_fn_data);\n+\n+struct config_parse_options {\n+\t/*\n+\t * event_fn and event_fn_data are for internal use only. Handles events\n+\t * emitted by the config parser.\n+\t */\n+\tconfig_parser_event_fn_t event_fn;\n+\tvoid *event_fn_data;\n+};\n+\n+struct config_source {\n+\tstruct config_source *prev;\n+\tunion {\n+\t\tFILE *file;\n+\t\tstruct config_buf {\n+\t\t\tconst char *buf;\n+\t\t\tsize_t len;\n+\t\t\tsize_t pos;\n+\t\t} buf;\n+\t} u;\n+\tenum config_origin_type origin_type;\n+\tconst char *name;\n+\tconst char *path;\n+\tint linenr;\n+\tint eof;\n+\tsize_t total_len;\n+\tstruct strbuf value;\n+\tstruct strbuf var;\n+\tunsigned subsection_case_sensitive : 1;\n+\n+\tint (*do_fgetc)(struct config_source *c);\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+/* Config source metadata for a given config key-value pair */\n+struct key_value_info {\n+\tconst char *filename;\n+\tint linenr;\n+\tenum config_origin_type origin_type;\n+\tenum config_scope scope;\n+\tconst char *path;\n+};\n+#define KVI_INIT { \\\n+\t.filename = NULL, \\\n+\t.linenr = -1, \\\n+\t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n+\t.scope = CONFIG_SCOPE_UNKNOWN, \\\n+\t.path = NULL, \\\n+}\n+\n+/* Captures additional information that a config callback can use. */\n+struct config_context {\n+\t/* Config source metadata for key and value. */\n+\tconst struct key_value_info *kvi;\n+};\n+#define CONFIG_CONTEXT_INIT { 0 }\n+\n+/**\n+ * A config callback function takes four parameters:\n+ *\n+ * - the name of the parsed variable. This is in canonical \"flat\" form: the\n+ *   section, subsection, and variable segments will be separated by dots,\n+ *   and the section and variable segments will be all lowercase. E.g.,\n+ *   `core.ignorecase`, `diff.SomeType.textconv`.\n+ *\n+ * - the value of the found variable, as a string. If the variable had no\n+ *   value specified, the value will be NULL (typically this means it\n+ *   should be interpreted as boolean true).\n+ *\n+ * - the 'config context', that is, additional information about the config\n+ *   iteration operation provided by the config machinery. For example, this\n+ *   includes information about the config source being parsed (e.g. the\n+ *   filename).\n+ *\n+ * - a void pointer passed in by the caller of the config API; this can\n+ *   contain callback-specific data\n+ *\n+ * A config callback should return 0 for success, or -1 if the variable\n+ * could not be parsed properly.\n+ */\n+typedef int (*config_fn_t)(const char *, const char *,\n+\t\t\t   const struct config_context *, void *);\n+\n+int git_config_from_file_with_options(config_fn_t fn, const char *,\n+\t\t\t\t      void *, enum config_scope,\n+\t\t\t\t      const struct config_parse_options *);\n+\n+int git_config_from_mem(config_fn_t fn,\n+\t\t\tconst enum config_origin_type,\n+\t\t\tconst char *name,\n+\t\t\tconst char *buf, size_t len,\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_parse_options *opts);\n+\n+int git_config_from_stdin(config_fn_t fn, void *data, enum config_scope scope,\n+\t\t\t  const struct config_parse_options *config_opts);\n+\n+#endif /* CONFIG_PARSE_H */\ndiff --git a/config.c b/config.c\nindex 40cc9dbc40..787b42c228 100644\n--- a/config.c\n+++ b/config.c\n@@ -42,32 +42,6 @@\n #include \"wrapper.h\"\n #include \"write-or-die.h\"\n \n-struct config_source {\n-\tstruct config_source *prev;\n-\tunion {\n-\t\tFILE *file;\n-\t\tstruct config_buf {\n-\t\t\tconst char *buf;\n-\t\t\tsize_t len;\n-\t\t\tsize_t pos;\n-\t\t} buf;\n-\t} u;\n-\tenum config_origin_type origin_type;\n-\tconst char *name;\n-\tconst char *path;\n-\tint linenr;\n-\tint eof;\n-\tsize_t total_len;\n-\tstruct strbuf value;\n-\tstruct strbuf var;\n-\tunsigned subsection_case_sensitive : 1;\n-\n-\tint (*do_fgetc)(struct config_source *c);\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 static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -82,47 +56,6 @@ static int zlib_compression_seen;\n  */\n static struct config_set protected_config;\n \n-static int config_file_fgetc(struct config_source *conf)\n-{\n-\treturn getc_unlocked(conf->u.file);\n-}\n-\n-static int config_file_ungetc(int c, struct config_source *conf)\n-{\n-\treturn ungetc(c, conf->u.file);\n-}\n-\n-static long config_file_ftell(struct config_source *conf)\n-{\n-\treturn ftell(conf->u.file);\n-}\n-\n-\n-static int config_buf_fgetc(struct config_source *conf)\n-{\n-\tif (conf->u.buf.pos < conf->u.buf.len)\n-\t\treturn conf->u.buf.buf[conf->u.buf.pos++];\n-\n-\treturn EOF;\n-}\n-\n-static int config_buf_ungetc(int c, struct config_source *conf)\n-{\n-\tif (conf->u.buf.pos > 0) {\n-\t\tconf->u.buf.pos--;\n-\t\tif (conf->u.buf.buf[conf->u.buf.pos] != c)\n-\t\t\tBUG(\"config_buf can only ungetc the same character\");\n-\t\treturn c;\n-\t}\n-\n-\treturn EOF;\n-}\n-\n-static long config_buf_ftell(struct config_source *conf)\n-{\n-\treturn conf->u.buf.pos;\n-}\n-\n struct config_include_data {\n \tint depth;\n \tconfig_fn_t fn;\n@@ -528,80 +461,6 @@ void git_config_push_env(const char *spec)\n \tfree(key);\n }\n \n-static inline int iskeychar(int c)\n-{\n-\treturn isalnum(c) || c == '-';\n-}\n-\n-/*\n- * Auxiliary function to sanity-check and split the key into the section\n- * identifier and variable name.\n- *\n- * Returns 0 on success, -1 when there is an invalid character in the key and\n- * -2 if there is no section name in the key.\n- *\n- * store_key - pointer to char* which will hold a copy of the key with\n- *             lowercase section and variable name\n- * baselen - pointer to size_t which will hold the length of the\n- *           section + subsection part, can be NULL\n- */\n-int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)\n-{\n-\tsize_t i, baselen;\n-\tint dot;\n-\tconst char *last_dot = strrchr(key, '.');\n-\n-\t/*\n-\t * Since \"key\" actually contains the section name and the real\n-\t * key name separated by a dot, we have to know where the dot is.\n-\t */\n-\n-\tif (last_dot == NULL || last_dot == key) {\n-\t\terror(_(\"key does not contain a section: %s\"), key);\n-\t\treturn -CONFIG_NO_SECTION_OR_NAME;\n-\t}\n-\n-\tif (!last_dot[1]) {\n-\t\terror(_(\"key does not contain variable name: %s\"), key);\n-\t\treturn -CONFIG_NO_SECTION_OR_NAME;\n-\t}\n-\n-\tbaselen = last_dot - key;\n-\tif (baselen_)\n-\t\t*baselen_ = baselen;\n-\n-\t/*\n-\t * Validate the key and while at it, lower case it for matching.\n-\t */\n-\t*store_key = xmallocz(strlen(key));\n-\n-\tdot = 0;\n-\tfor (i = 0; key[i]; i++) {\n-\t\tunsigned char c = key[i];\n-\t\tif (c == '.')\n-\t\t\tdot = 1;\n-\t\t/* Leave the extended basename untouched.. */\n-\t\tif (!dot || i > baselen) {\n-\t\t\tif (!iskeychar(c) ||\n-\t\t\t    (i == baselen + 1 && !isalpha(c))) {\n-\t\t\t\terror(_(\"invalid key: %s\"), key);\n-\t\t\t\tgoto out_free_ret_1;\n-\t\t\t}\n-\t\t\tc = tolower(c);\n-\t\t} else if (c == '\\n') {\n-\t\t\terror(_(\"invalid key (newline): %s\"), key);\n-\t\t\tgoto out_free_ret_1;\n-\t\t}\n-\t\t(*store_key)[i] = c;\n-\t}\n-\n-\treturn 0;\n-\n-out_free_ret_1:\n-\tFREE_AND_NULL(*store_key);\n-\treturn -CONFIG_INVALID_KEY;\n-}\n-\n static int config_parse_pair(const char *key, const char *value,\n \t\t\t     struct key_value_info *kvi,\n \t\t\t     config_fn_t fn, void *data)\n@@ -786,251 +645,6 @@ 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-{\n-\tint c = cs->do_fgetc(cs);\n-\n-\tif (c == '\\r') {\n-\t\t/* DOS like systems */\n-\t\tc = cs->do_fgetc(cs);\n-\t\tif (c != '\\n') {\n-\t\t\tif (c != EOF)\n-\t\t\t\tcs->do_ungetc(c, cs);\n-\t\t\tc = '\\r';\n-\t\t}\n-\t}\n-\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-\t\t * overflows. Note that we can't return an error specifically,\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\treturn 0;\n-\t}\n-\n-\tif (c == '\\n')\n-\t\tcs->linenr++;\n-\tif (c == EOF) {\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 *cs)\n-{\n-\tint quote = 0, comment = 0, space = 0;\n-\n-\tstrbuf_reset(&cs->value);\n-\tfor (;;) {\n-\t\tint c = get_next_char(cs);\n-\t\tif (c == '\\n') {\n-\t\t\tif (quote) {\n-\t\t\t\tcs->linenr--;\n-\t\t\t\treturn NULL;\n-\t\t\t}\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 (cs->value.len)\n-\t\t\t\tspace++;\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (!quote) {\n-\t\t\tif (c == ';' || c == '#') {\n-\t\t\t\tcomment = 1;\n-\t\t\t\tcontinue;\n-\t\t\t}\n-\t\t}\n-\t\tfor (; space; space--)\n-\t\t\tstrbuf_addch(&cs->value, ' ');\n-\t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char(cs);\n-\t\t\tswitch (c) {\n-\t\t\tcase '\\n':\n-\t\t\t\tcontinue;\n-\t\t\tcase 't':\n-\t\t\t\tc = '\\t';\n-\t\t\t\tbreak;\n-\t\t\tcase 'b':\n-\t\t\t\tc = '\\b';\n-\t\t\t\tbreak;\n-\t\t\tcase 'n':\n-\t\t\t\tc = '\\n';\n-\t\t\t\tbreak;\n-\t\t\t/* Some characters escape as themselves */\n-\t\t\tcase '\\\\': case '\"':\n-\t\t\t\tbreak;\n-\t\t\t/* Reject unknown escape sequences */\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\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}\n-}\n-\n-static int get_value(struct config_source *cs, struct key_value_info *kvi,\n-\t\t     config_fn_t fn, void *data, struct strbuf *name)\n-{\n-\tint c;\n-\tchar *value;\n-\tint ret;\n-\tstruct config_context ctx = {\n-\t\t.kvi = kvi,\n-\t};\n-\n-\t/* Get the full name */\n-\tfor (;;) {\n-\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-\t\tstrbuf_addch(name, tolower(c));\n-\t}\n-\n-\twhile (c == ' ' || c == '\\t')\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(cs);\n-\t\tif (!value)\n-\t\t\treturn -1;\n-\t}\n-\t/*\n-\t * We already consumed the \\n, but we need linenr to point to\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-\tkvi->linenr = cs->linenr;\n-\tret = fn(name->buf, value, &ctx, data);\n-\tif (ret >= 0)\n-\t\tcs->linenr++;\n-\treturn ret;\n-}\n-\n-static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n-\t\t\t\t int c)\n-{\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(cs);\n-\t} while (isspace(c));\n-\n-\t/* We require the format to be '[base \"extension\"]' */\n-\tif (c != '\"')\n-\t\treturn -1;\n-\tstrbuf_addch(name, '.');\n-\n-\tfor (;;) {\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(cs);\n-\t\t\tif (c == '\\n')\n-\t\t\t\tgoto error_incomplete_line;\n-\t\t}\n-\t\tstrbuf_addch(name, c);\n-\t}\n-\n-\t/* Final ']' */\n-\tif (get_next_char(cs) != ']')\n-\t\treturn -1;\n-\treturn 0;\n-error_incomplete_line:\n-\tcs->linenr--;\n-\treturn -1;\n-}\n-\n-static int get_base_var(struct config_source *cs, struct strbuf *name)\n-{\n-\tcs->subsection_case_sensitive = 1;\n-\tfor (;;) {\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(cs, name, c);\n-\t\tif (!iskeychar(c) && c != '.')\n-\t\t\treturn -1;\n-\t\tstrbuf_addch(name, tolower(c));\n-\t}\n-}\n-\n-struct parse_event_data {\n-\tenum config_event_t previous_type;\n-\tsize_t previous_offset;\n-\tconst struct config_parse_options *opts;\n-};\n-\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-\tif (!data->opts || !data->opts->event_fn)\n-\t\treturn 0;\n-\n-\tif (type == CONFIG_EVENT_WHITESPACE &&\n-\t    data->previous_type == type)\n-\t\treturn 0;\n-\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-\t * we will already have advanced to the next event.\n-\t */\n-\tif (type != CONFIG_EVENT_EOF)\n-\t\toffset--;\n-\n-\tif (data->previous_type != CONFIG_EVENT_EOF &&\n-\t    data->opts->event_fn(data->previous_type, data->previous_offset,\n-\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n-\t\treturn -1;\n-\n-\tdata->previous_type = type;\n-\tdata->previous_offset = offset;\n-\n-\treturn 0;\n-}\n-\n-static void kvi_from_source(struct config_source *cs,\n-\t\t\t    enum config_scope scope,\n-\t\t\t    struct key_value_info *out)\n-{\n-\tout->filename = strintern(cs->name);\n-\tout->origin_type = cs->origin_type;\n-\tout->linenr = cs->linenr;\n-\tout->scope = scope;\n-\tout->path = cs->path;\n-}\n-\n int git_config_err_fn(enum config_event_t type, size_t begin_offset UNUSED,\n \t\t      size_t end_offset UNUSED, struct config_source *cs,\n \t\t      void *data)\n@@ -1081,104 +695,6 @@ int git_config_err_fn(enum config_event_t type, size_t begin_offset UNUSED,\n \treturn error_return;\n }\n \n-static int git_parse_source(struct config_source *cs, config_fn_t fn,\n-\t\t\t    struct key_value_info *kvi, void *data,\n-\t\t\t    const struct config_parse_options *opts)\n-{\n-\tint comment = 0;\n-\tsize_t baselen = 0;\n-\tstruct strbuf *var = &cs->var;\n-\n-\t/* U+FEFF Byte Order Mark in UTF8 */\n-\tconst char *bomptr = utf8_bom;\n-\n-\t/* For the parser event callback */\n-\tstruct parse_event_data event_data = {\n-\t\tCONFIG_EVENT_EOF, 0, opts\n-\t};\n-\n-\tfor (;;) {\n-\t\tint c;\n-\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-\t\t\t * own, but e.g. Windows Notepad will do it happily. */\n-\t\t\tif (c == (*bomptr & 0377)) {\n-\t\t\t\tbomptr++;\n-\t\t\t\tcontinue;\n-\t\t\t} else {\n-\t\t\t\t/* Do not tolerate partial BOM. */\n-\t\t\t\tif (bomptr != utf8_bom)\n-\t\t\t\t\tbreak;\n-\t\t\t\t/* No BOM at file beginning. Cool. */\n-\t\t\t\tbomptr = NULL;\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\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\t\treturn -1;\n-\t\t\tcomment = 0;\n-\t\t\tcontinue;\n-\t\t}\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\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\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\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\t\tbreak;\n-\t\t\tstrbuf_addch(var, '.');\n-\t\t\tbaselen = var->len;\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (!isalpha(c))\n-\t\t\tbreak;\n-\n-\t\tif (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)\n-\t\t\treturn -1;\n-\n-\t\t/*\n-\t\t * Truncate the var name back to the section header\n-\t\t * stem prior to grabbing the suffix part of the name\n-\t\t * and the value.\n-\t\t */\n-\t\tstrbuf_setlen(var, baselen);\n-\t\tstrbuf_addch(var, tolower(c));\n-\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n-\t\t\tbreak;\n-\t}\n-\n-\t/*\n-\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n-\t * in order for our callback to receive the error event, we have to call\n-\t * do_event twice\n-\t */\n-\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n-\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n-\treturn -1;\n-}\n-\n static uintmax_t get_unit_factor(const char *end)\n {\n \tif (!*end)\n@@ -1972,83 +1488,6 @@ int git_default_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-/*\n- * All source specific fields in the union, die_on_error, name and the callbacks\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,\n-\t\t\t  void *data, enum config_scope scope,\n-\t\t\t  const struct config_parse_options *opts)\n-{\n-\tstruct key_value_info kvi = KVI_INIT;\n-\tint ret;\n-\n-\t/* push config-file parsing state stack */\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-\tkvi_from_source(top, scope, &kvi);\n-\n-\tret = git_parse_source(top, fn, &kvi, data, opts);\n-\n-\tstrbuf_release(&top->value);\n-\tstrbuf_release(&top->var);\n-\n-\treturn ret;\n-}\n-\n-static int do_config_from_file(config_fn_t fn,\n-\t\t\t       const enum config_origin_type origin_type,\n-\t\t\t       const char *name, const char *path, FILE *f,\n-\t\t\t       void *data, enum config_scope scope,\n-\t\t\t       const struct config_parse_options *opts)\n-{\n-\tstruct config_source top = CONFIG_SOURCE_INIT;\n-\tint ret;\n-\n-\ttop.u.file = f;\n-\ttop.origin_type = origin_type;\n-\ttop.name = name;\n-\ttop.path = path;\n-\ttop.do_fgetc = config_file_fgetc;\n-\ttop.do_ungetc = config_file_ungetc;\n-\ttop.do_ftell = config_file_ftell;\n-\n-\tflockfile(f);\n-\tret = do_config_from(&top, fn, data, scope, opts);\n-\tfunlockfile(f);\n-\treturn ret;\n-}\n-\n-static int git_config_from_stdin(config_fn_t fn, void *data,\n-\t\t\t\t enum config_scope scope,\n-\t\t\t\t const struct config_parse_options *config_opts)\n-{\n-\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n-\t\t\t\t   data, scope, config_opts);\n-}\n-\n-int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n-\t\t\t\t      void *data, enum config_scope scope,\n-\t\t\t\t      const struct config_parse_options *opts)\n-{\n-\tint ret = -1;\n-\tFILE *f;\n-\n-\tif (!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, scope, opts);\n-\t\tfclose(f);\n-\t}\n-\treturn ret;\n-}\n-\n int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n {\n \tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n@@ -2057,27 +1496,6 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n \t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, &config_opts);\n }\n \n-int git_config_from_mem(config_fn_t fn,\n-\t\t\tconst enum config_origin_type origin_type,\n-\t\t\tconst char *name, const char *buf, size_t len,\n-\t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_parse_options *opts)\n-{\n-\tstruct config_source top = CONFIG_SOURCE_INIT;\n-\n-\ttop.u.buf.buf = buf;\n-\ttop.u.buf.len = len;\n-\ttop.u.buf.pos = 0;\n-\ttop.origin_type = origin_type;\n-\ttop.name = name;\n-\ttop.path = NULL;\n-\ttop.do_fgetc = config_buf_fgetc;\n-\ttop.do_ungetc = config_buf_ungetc;\n-\ttop.do_ftell = config_buf_ftell;\n-\n-\treturn do_config_from(&top, fn, data, scope, opts);\n-}\n-\n int git_config_from_blob_oid(config_fn_t fn,\n \t\t\t      const char *name,\n \t\t\t      struct repository *repo,\ndiff --git a/config.h b/config.h\nindex 8ad399580f..3bad5e1c32 100644\n--- a/config.h\n+++ b/config.h\n@@ -4,7 +4,7 @@\n #include \"hashmap.h\"\n #include \"string-list.h\"\n #include \"repository.h\"\n-\n+#include \"config-parse.h\"\n \n /**\n  * The config API gives callers a way to access Git configuration files\n@@ -23,9 +23,6 @@\n \n struct object_id;\n \n-/* git_config_parse_key() returns these negated: */\n-#define CONFIG_INVALID_KEY 1\n-#define CONFIG_NO_SECTION_OR_NAME 2\n /* git_config_set_gently(), git_config_set_multivar_gently() return the above or these: */\n #define CONFIG_NO_LOCK -1\n #define CONFIG_INVALID_FILE 3\n@@ -36,17 +33,6 @@ struct object_id;\n \n #define CONFIG_REGEX_NONE ((void *)1)\n \n-enum config_scope {\n-\tCONFIG_SCOPE_UNKNOWN = 0,\n-\tCONFIG_SCOPE_SYSTEM,\n-\tCONFIG_SCOPE_GLOBAL,\n-\tCONFIG_SCOPE_LOCAL,\n-\tCONFIG_SCOPE_WORKTREE,\n-\tCONFIG_SCOPE_COMMAND,\n-\tCONFIG_SCOPE_SUBMODULE,\n-};\n-const char *config_scope_name(enum config_scope scope);\n-\n struct git_config_source {\n \tunsigned int use_stdin:1;\n \tconst char *file;\n@@ -54,46 +40,6 @@ struct git_config_source {\n \tenum config_scope scope;\n };\n \n-enum config_origin_type {\n-\tCONFIG_ORIGIN_UNKNOWN = 0,\n-\tCONFIG_ORIGIN_BLOB,\n-\tCONFIG_ORIGIN_FILE,\n-\tCONFIG_ORIGIN_STDIN,\n-\tCONFIG_ORIGIN_SUBMODULE_BLOB,\n-\tCONFIG_ORIGIN_CMDLINE\n-};\n-\n-enum config_event_t {\n-\tCONFIG_EVENT_SECTION,\n-\tCONFIG_EVENT_ENTRY,\n-\tCONFIG_EVENT_WHITESPACE,\n-\tCONFIG_EVENT_COMMENT,\n-\tCONFIG_EVENT_EOF,\n-\tCONFIG_EVENT_ERROR\n-};\n-\n-struct config_source;\n-/*\n- * The parser event function (if not NULL) is called with the event type and\n- * the begin/end offsets of the parsed elements.\n- *\n- * Note: for CONFIG_EVENT_ENTRY (i.e. config variables), the trailing newline\n- * character is considered part of the element.\n- */\n-typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n-\t\t\t\t\tsize_t begin_offset, size_t end_offset,\n-\t\t\t\t\tstruct config_source *cs,\n-\t\t\t\t\tvoid *event_fn_data);\n-\n-struct config_parse_options {\n-\t/*\n-\t * event_fn and event_fn_data are for internal use only. Handles events\n-\t * emitted by the config parser.\n-\t */\n-\tconfig_parser_event_fn_t event_fn;\n-\tvoid *event_fn_data;\n-};\n-\n #define CP_OPTS_INIT(error_action) { \\\n \t.event_fn = git_config_err_fn, \\\n \t.event_fn_data = (enum config_error_action []){(error_action)}, \\\n@@ -126,59 +72,8 @@ enum config_error_action {\n int git_config_err_fn(enum config_event_t type, size_t begin_offset,\n \t\t      size_t end_offset, struct config_source *cs,\n \t\t      void *event_fn_data);\n-\n-/* Config source metadata for a given config key-value pair */\n-struct key_value_info {\n-\tconst char *filename;\n-\tint linenr;\n-\tenum config_origin_type origin_type;\n-\tenum config_scope scope;\n-\tconst char *path;\n-};\n-#define KVI_INIT { \\\n-\t.filename = NULL, \\\n-\t.linenr = -1, \\\n-\t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n-\t.scope = CONFIG_SCOPE_UNKNOWN, \\\n-\t.path = NULL, \\\n-}\n-\n-/* Captures additional information that a config callback can use. */\n-struct config_context {\n-\t/* Config source metadata for key and value. */\n-\tconst struct key_value_info *kvi;\n-};\n-#define CONFIG_CONTEXT_INIT { 0 }\n-\n-/**\n- * A config callback function takes four parameters:\n- *\n- * - the name of the parsed variable. This is in canonical \"flat\" form: the\n- *   section, subsection, and variable segments will be separated by dots,\n- *   and the section and variable segments will be all lowercase. E.g.,\n- *   `core.ignorecase`, `diff.SomeType.textconv`.\n- *\n- * - the value of the found variable, as a string. If the variable had no\n- *   value specified, the value will be NULL (typically this means it\n- *   should be interpreted as boolean true).\n- *\n- * - the 'config context', that is, additional information about the config\n- *   iteration operation provided by the config machinery. For example, this\n- *   includes information about the config source being parsed (e.g. the\n- *   filename).\n- *\n- * - a void pointer passed in by the caller of the config API; this can\n- *   contain callback-specific data\n- *\n- * A config callback should return 0 for success, or -1 if the variable\n- * could not be parsed properly.\n- */\n-typedef int (*config_fn_t)(const char *, const char *,\n-\t\t\t   const struct config_context *, void *);\n-\n int git_default_config(const char *, const char *,\n \t\t       const struct config_context *, void *);\n-\n /**\n  * Read a specific file in git-config format.\n  * This function takes the same callback and data parameters as `git_config`.\n@@ -186,16 +81,6 @@ int git_default_config(const char *, const char *,\n  * Unlike git_config(), this function does not respect includes.\n  */\n int git_config_from_file(config_fn_t fn, const char *, void *);\n-\n-int git_config_from_file_with_options(config_fn_t fn, const char *,\n-\t\t\t\t      void *, enum config_scope,\n-\t\t\t\t      const struct config_parse_options *);\n-int git_config_from_mem(config_fn_t fn,\n-\t\t\tconst enum config_origin_type,\n-\t\t\tconst char *name,\n-\t\t\tconst char *buf, size_t len,\n-\t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_parse_options *opts);\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     struct repository *repo,\n \t\t\t     const struct object_id *oid, void *data,\n@@ -333,8 +218,6 @@ int repo_config_set_worktree_gently(struct repository *, const char *, const cha\n  */\n void git_config_set(const char *, const char *);\n \n-int git_config_parse_key(const char *, char **, size_t *);\n-\n /*\n  * The following macros specify flag bits that alter the behavior\n  * of the git_config_set_multivar*() methods.\n-- \n2.42.0.rc1.204.g551eb34607-goog\n\n"},{"id":"480985","messageId":"xmqq4jkpe3we.fsf@gitster.g","threadId":"60017","inReplyTo":"5c676fbac370a2ba9d499ae2ef003e75b1d70081.1692827403.git.steadmon@google.com","subject":"Re: [PATCH v2 1/4] config: split out config_parse_options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-08-23T23:26:57Z","receivedAt":"2023-08-23T23:28:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> From: Glen Choo <chooglen@google.com>\n>\n> \"struct config_options\" is a disjoint set of options options used by the\n> config parser (e.g. event listners) and options used by\n> config_with_options() (e.g. to handle includes, choose which config\n> files to parse).\n\nThere is some punctuation missing on the first line.  Perhaps an em-dash\nbetween \"options---options\" or something like that?\n\n> Split parser-only options into config_parse_options.\n>\n> Signed-off-by: Glen Choo <chooglen@google.com>\n> Signed-off-by: Josh Steadmon <steadmon@google.com>\n> ---\n>  bundle-uri.c |  2 +-\n>  config.c     | 14 +++++++-------\n>  config.h     | 37 ++++++++++++++++++++-----------------\n>  fsck.c       |  2 +-\n>  4 files changed, 29 insertions(+), 26 deletions(-)\n\n> diff --git a/bundle-uri.c b/bundle-uri.c\n> index 4b5c49b93d..f93ca6a486 100644\n> --- a/bundle-uri.c\n> +++ b/bundle-uri.c\n> @@ -237,7 +237,7 @@ int bundle_uri_parse_config_format(const char *uri,\n>  \t\t\t\t   struct bundle_list *list)\n>  {\n>  \tint result;\n> -\tstruct config_options opts = {\n> +\tstruct config_parse_options opts = {\n>  \t\t.error_action = CONFIG_ERROR_ERROR,\n>  \t};\n\nOK, and this one only needs the parse_options half, and presumably\nall hunks (other than the one that splits the struct into two in\nconfig.h) are about turning the users of config_options that only\nneed config_parse_options half.\n\nAs we do not see any funny casts in the patch text, compilers should\ncatch all questionable conversion in this step, if there were any.\n\nOK.\n"},{"id":"480986","messageId":"xmqqttspck4a.fsf@gitster.g","threadId":"60017","inReplyTo":"cb92a1f2e3098bede386d04da32fcc4f27fca51f.1692827403.git.steadmon@google.com","subject":"Re: [PATCH v2 2/4] config: report config parse errors using cb","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-08-24T01:19:33Z","receivedAt":"2023-08-24T01:20:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> From: Glen Choo <chooglen@google.com>\n>\n> In a subsequent commit, config parsing will become its own library, and\n> it's likely that the caller will want flexibility in handling errors\n> (instead of being limited to the error handling we have in-tree).\n\nAnd the in-tree error handling is abstracted out as the\ngit_config_err_fn() function; in other words, we become the first\nclient of the library interface, which makes sense.\n\n> @@ -1035,8 +1088,6 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n>  \tint comment = 0;\n>  \tsize_t baselen = 0;\n>  \tstruct strbuf *var = &cs->var;\n> ...\n> +\t/*\n> +\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n> +\t * in order for our callback to receive the error event, we have to call\n> +\t * do_event twice\n> +\t */\n> +\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n> +\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n> +\treturn -1;\n>  }\n\nThis indeed is very curious and needs to be looked into before we\nproceed further.  How does the current control flow cope with the\nbehaviour?\n\n> @@ -2322,7 +2342,9 @@ void read_early_config(config_fn_t cb, void *data)\n>   */\n>  void read_very_early_config(config_fn_t cb, void *data)\n>  {\n> -\tstruct config_options opts = { 0 };\n> +\tstruct config_options opts = {\n> +\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n> +\t};\n>  \n>  \topts.respect_includes = 1;\n>  \topts.ignore_repo = 1;\n\nThis uses a bit more assignments to various members of opts. to\ninitialize it, which could have been done with designated\ninitializer, like the one in read_protected_config() used to do.\n\n> @@ -2760,12 +2784,14 @@ int repo_config_get_pathname(struct repository *repo,\n>  static void read_protected_config(void)\n>  {\n>  \tstruct config_options opts = {\n> -\t\t.respect_includes = 1,\n> -\t\t.ignore_repo = 1,\n> -\t\t.ignore_worktree = 1,\n> -\t\t.system_gently = 1,\n> +\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n>  \t};\n>  \n> +\topts.respect_includes = 1;\n> +\topts.ignore_repo = 1;\n> +\topts.ignore_worktree = 1;\n> +\topts.system_gently = 1;\n> +\n\nIt is curious why you want to switch to manual assignment, instead\nof keeping the designated initializer for this one.  I would have\nexpected the initialization in read_very_early_config() to start\nusing designated initializer to be consistent, instead.\n\nThanks.\n"},{"id":"481003","messageId":"20230824173136.2252024-1-jonathantanmy@google.com","threadId":"60017","inReplyTo":"xmqqttspck4a.fsf@gitster.g","subject":"Re: [PATCH v2 2/4] config: report config parse errors using cb","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-08-24T17:31:36Z","receivedAt":"2023-08-24T17:32:23Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Junio C Hamano <gitster@pobox.com> writes:\n> > @@ -1035,8 +1088,6 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n> >  \tint comment = 0;\n> >  \tsize_t baselen = 0;\n> >  \tstruct strbuf *var = &cs->var;\n> > ...\n> > +\t/*\n> > +\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n> > +\t * in order for our callback to receive the error event, we have to call\n> > +\t * do_event twice\n> > +\t */\n> > +\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n> > +\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n> > +\treturn -1;\n> >  }\n> \n> This indeed is very curious and needs to be looked into before we\n> proceed further.  How does the current control flow cope with the\n> behaviour?\n\nI plan to look at this more fully later, but about this, I wrote about\nthis in a reply to the previous version:\nhttps://lore.kernel.org/git/20230804213457.1174493-1-jonathantanmy@google.com/\n"},{"id":"481008","messageId":"xmqqfs48b7k7.fsf@gitster.g","threadId":"60017","inReplyTo":"20230824173136.2252024-1-jonathantanmy@google.com","subject":"Re: [PATCH v2 2/4] config: report config parse errors using cb","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-08-24T18:48:24Z","receivedAt":"2023-08-24T18:49:28Z","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> Junio C Hamano <gitster@pobox.com> writes:\n>> > @@ -1035,8 +1088,6 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n>> >  \tint comment = 0;\n>> >  \tsize_t baselen = 0;\n>> >  \tstruct strbuf *var = &cs->var;\n>> > ...\n>> > +\t/*\n>> > +\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n>> > +\t * in order for our callback to receive the error event, we have to call\n>> > +\t * do_event twice\n>> > +\t */\n>> > +\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n>> > +\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n>> > +\treturn -1;\n>> >  }\n>> \n>> This indeed is very curious and needs to be looked into before we\n>> proceed further.  How does the current control flow cope with the\n>> behaviour?\n>\n> I plan to look at this more fully later, but about this, I wrote about\n> this in a reply to the previous version:\n> https://lore.kernel.org/git/20230804213457.1174493-1-jonathantanmy@google.com/\n\nThanks.\n\nAlso if you have time, can you comment on the latest round of the\nfix to the Bloom filter hash functions that Taylor sent the latest\niteration for, either to give it a \"looks perfect\" to nudge it to\nmerge it down, to give it a further polishing, or whatever.  Thanks.\n\n\n"},{"id":"481010","messageId":"ZOe5T0jsNAMfT/K2@google.com","threadId":"60017","inReplyTo":"cover.1692827403.git.steadmon@google.com","subject":"Re: [PATCH v2 0/4] config-parse: create config parsing library","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-08-24T20:10:55Z","receivedAt":"2023-08-24T20:12:02Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2023.08.23 14:53, Josh Steadmon wrote:\n> I'll be taking over this series from Glen (thank you for the work so\n> far).\n\nBTW, I'm going to be AFK for a couple weeks, so it will be a while\nbefore I'm able to address feedback on this series. Thanks in advance.\n"},{"id":"482109","messageId":"ZQyw1OcKmOeAEFPM@google.com","threadId":"60017","inReplyTo":"xmqq4jkpe3we.fsf@gitster.g","subject":"Re: [PATCH v2 1/4] config: split out config_parse_options","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-09-21T21:08:36Z","receivedAt":"2023-09-21T21:32:46Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2023.08.23 16:26, Junio C Hamano wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> \n> > From: Glen Choo <chooglen@google.com>\n> >\n> > \"struct config_options\" is a disjoint set of options options used by the\n> > config parser (e.g. event listners) and options used by\n> > config_with_options() (e.g. to handle includes, choose which config\n> > files to parse).\n> \n> There is some punctuation missing on the first line.  Perhaps an em-dash\n> between \"options---options\" or something like that?\n\nCleaned up this and an additional typo in the description.\n\n> > Split parser-only options into config_parse_options.\n> >\n> > Signed-off-by: Glen Choo <chooglen@google.com>\n> > Signed-off-by: Josh Steadmon <steadmon@google.com>\n> > ---\n> >  bundle-uri.c |  2 +-\n> >  config.c     | 14 +++++++-------\n> >  config.h     | 37 ++++++++++++++++++++-----------------\n> >  fsck.c       |  2 +-\n> >  4 files changed, 29 insertions(+), 26 deletions(-)\n> \n> > diff --git a/bundle-uri.c b/bundle-uri.c\n> > index 4b5c49b93d..f93ca6a486 100644\n> > --- a/bundle-uri.c\n> > +++ b/bundle-uri.c\n> > @@ -237,7 +237,7 @@ int bundle_uri_parse_config_format(const char *uri,\n> >  \t\t\t\t   struct bundle_list *list)\n> >  {\n> >  \tint result;\n> > -\tstruct config_options opts = {\n> > +\tstruct config_parse_options opts = {\n> >  \t\t.error_action = CONFIG_ERROR_ERROR,\n> >  \t};\n> \n> OK, and this one only needs the parse_options half, and presumably\n> all hunks (other than the one that splits the struct into two in\n> config.h) are about turning the users of config_options that only\n> need config_parse_options half.\n> \n> As we do not see any funny casts in the patch text, compilers should\n> catch all questionable conversion in this step, if there were any.\n> \n> OK.\n"},{"id":"482110","messageId":"fa55b7836f112cb7c7ab9b80e745b9969421c768.1695330852.git.steadmon@google.com","threadId":"60017","inReplyTo":"cover.1695330852.git.steadmon@google.com","subject":"[PATCH v3 1/5] config: split out config_parse_options","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-09-21T21:17:20Z","receivedAt":"2023-09-21T21:40:19Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\n\"struct config_options\" is a disjoint set of options used by the config\nparser (e.g. event listeners) and options used by config_with_options()\n(e.g. to handle includes, choose which config files to parse). Split\nparser-only options into config_parse_options.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n bundle-uri.c |  2 +-\n config.c     | 14 +++++++-------\n config.h     | 37 ++++++++++++++++++++-----------------\n fsck.c       |  2 +-\n 4 files changed, 29 insertions(+), 26 deletions(-)\n\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex 4b5c49b93d..f93ca6a486 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -237,7 +237,7 @@ int bundle_uri_parse_config_format(const char *uri,\n \t\t\t\t   struct bundle_list *list)\n {\n \tint result;\n-\tstruct config_options opts = {\n+\tstruct config_parse_options opts = {\n \t\t.error_action = CONFIG_ERROR_ERROR,\n \t};\n \ndiff --git a/config.c b/config.c\nindex 85c5f35132..1518f70fc2 100644\n--- a/config.c\n+++ b/config.c\n@@ -982,7 +982,7 @@ static int get_base_var(struct config_source *cs, struct strbuf *name)\n struct parse_event_data {\n \tenum config_event_t previous_type;\n \tsize_t previous_offset;\n-\tconst struct config_options *opts;\n+\tconst struct config_parse_options *opts;\n };\n \n static int do_event(struct config_source *cs, enum config_event_t type,\n@@ -1030,7 +1030,7 @@ static void kvi_from_source(struct config_source *cs,\n \n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t\t    struct key_value_info *kvi, void *data,\n-\t\t\t    const struct config_options *opts)\n+\t\t\t    const struct config_parse_options *opts)\n {\n \tint comment = 0;\n \tsize_t baselen = 0;\n@@ -1967,7 +1967,7 @@ int git_default_config(const char *var, const char *value,\n  */\n static int do_config_from(struct config_source *top, config_fn_t fn,\n \t\t\t  void *data, enum config_scope scope,\n-\t\t\t  const struct config_options *opts)\n+\t\t\t  const struct config_parse_options *opts)\n {\n \tstruct key_value_info kvi = KVI_INIT;\n \tint ret;\n@@ -1992,7 +1992,7 @@ static int do_config_from_file(config_fn_t fn,\n \t\t\t       const enum config_origin_type origin_type,\n \t\t\t       const char *name, const char *path, FILE *f,\n \t\t\t       void *data, enum config_scope scope,\n-\t\t\t       const struct config_options *opts)\n+\t\t\t       const struct config_parse_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \tint ret;\n@@ -2021,7 +2021,7 @@ static int git_config_from_stdin(config_fn_t fn, void *data,\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \t\t\t\t      void *data, enum config_scope scope,\n-\t\t\t\t      const struct config_options *opts)\n+\t\t\t\t      const struct config_parse_options *opts)\n {\n \tint ret = -1;\n \tFILE *f;\n@@ -2047,7 +2047,7 @@ int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type origin_type,\n \t\t\tconst char *name, const char *buf, size_t len,\n \t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_options *opts)\n+\t\t\tconst struct config_parse_options *opts)\n {\n \tstruct config_source top = CONFIG_SOURCE_INIT;\n \n@@ -3380,7 +3380,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \t\tstruct stat st;\n \t\tsize_t copy_begin, copy_end;\n \t\tint i, new_line = 0;\n-\t\tstruct config_options opts;\n+\t\tstruct config_parse_options opts;\n \n \t\tif (!value_pattern)\n \t\t\tstore.value_pattern = NULL;\ndiff --git a/config.h b/config.h\nindex 6332d74904..2537516446 100644\n--- a/config.h\n+++ b/config.h\n@@ -85,6 +85,21 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n \t\t\t\t\tstruct config_source *cs,\n \t\t\t\t\tvoid *event_fn_data);\n \n+struct config_parse_options {\n+\tenum config_error_action {\n+\t\tCONFIG_ERROR_UNSET = 0, /* use source-specific default */\n+\t\tCONFIG_ERROR_DIE, /* die() on error */\n+\t\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n+\t\tCONFIG_ERROR_SILENT, /* return -1 */\n+\t} error_action;\n+\t/*\n+\t * event_fn and event_fn_data are for internal use only. Handles events\n+\t * emitted by the config parser.\n+\t */\n+\tconfig_parser_event_fn_t event_fn;\n+\tvoid *event_fn_data;\n+};\n+\n struct config_options {\n \tunsigned int respect_includes : 1;\n \tunsigned int ignore_repo : 1;\n@@ -92,6 +107,9 @@ struct config_options {\n \tunsigned int ignore_cmdline : 1;\n \tunsigned int system_gently : 1;\n \n+\tconst char *commondir;\n+\tconst char *git_dir;\n+\tstruct config_parse_options parse_options;\n \t/*\n \t * For internal use. Include all includeif.hasremoteurl paths without\n \t * checking if the repo has that remote URL, and when doing so, verify\n@@ -99,21 +117,6 @@ struct config_options {\n \t * themselves.\n \t */\n \tunsigned int unconditional_remote_url : 1;\n-\n-\tconst char *commondir;\n-\tconst char *git_dir;\n-\t/*\n-\t * event_fn and event_fn_data are for internal use only. Handles events\n-\t * emitted by the config parser.\n-\t */\n-\tconfig_parser_event_fn_t event_fn;\n-\tvoid *event_fn_data;\n-\tenum config_error_action {\n-\t\tCONFIG_ERROR_UNSET = 0, /* use source-specific default */\n-\t\tCONFIG_ERROR_DIE, /* die() on error */\n-\t\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n-\t\tCONFIG_ERROR_SILENT, /* return -1 */\n-\t} error_action;\n };\n \n /* Config source metadata for a given config key-value pair */\n@@ -178,13 +181,13 @@ int git_config_from_file(config_fn_t fn, const char *, void *);\n \n int git_config_from_file_with_options(config_fn_t fn, const char *,\n \t\t\t\t      void *, enum config_scope,\n-\t\t\t\t      const struct config_options *);\n+\t\t\t\t      const struct config_parse_options *);\n int git_config_from_mem(config_fn_t fn,\n \t\t\tconst enum config_origin_type,\n \t\t\tconst char *name,\n \t\t\tconst char *buf, size_t len,\n \t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_options *opts);\n+\t\t\tconst struct config_parse_options *opts);\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     struct repository *repo,\n \t\t\t     const struct object_id *oid, void *data,\ndiff --git a/fsck.c b/fsck.c\nindex 3be86616c5..522ee1c18a 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1219,7 +1219,7 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\treturn 0;\n \n \tif (oidset_contains(&options->gitmodules_found, oid)) {\n-\t\tstruct config_options config_opts = { 0 };\n+\t\tstruct config_parse_options config_opts = { 0 };\n \t\tstruct fsck_gitmodules_data data;\n \n \t\toidset_insert(&options->gitmodules_done, oid);\n-- \n2.42.0.515.g380fc7ccd1-goog\n\n"},{"id":"482112","messageId":"e59ca992d0566f793a07d78f3e65219b51239492.1695330852.git.steadmon@google.com","threadId":"60017","inReplyTo":"cover.1695330852.git.steadmon@google.com","subject":"[PATCH v3 5/5] config-parse: split library out of config.[c|h]","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-09-21T21:17:24Z","receivedAt":"2023-09-21T21:40:20Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nThe config parsing machinery (besides \"include\" directives) is usable by\nprograms other than Git - it works with any file written in Git config\nsyntax (IOW it doesn't rely on 'core' Git features like a repository),\nand as of the series ending at 6e8e7981eb (config: pass source to\nconfig_parser_event_fn_t, 2023-06-28), it no longer relies on global\nstate. Thus, we can and should start turning it into a library other\nprograms can use.\n\nBegin this process by splitting the config parsing code out of\nconfig.[c|h] and into config-parse.[c|h]. Do not change interfaces or\nfunction bodies, but tweak visibility and includes where appropriate,\nnamely:\n\n- git_config_from_stdin() is now non-static so that it can be seen by\n  config.c.\n\n- \"struct config_source\" is now defined in the .h file so that it can be\n  seen by config.c. And as a result, config-lib.h needs to \"#include\n  strbuf.h\".\n\nIn theory, this makes it possible for in-tree files to decide whether\nthey only need all of the config functionality or only config parsing,\nand bring in the smallest bit of functionality needed. But for now,\nthere are no in-tree files that can swap \"#include config.h\" for\n\"#include config-parse.h\". E.g. Bundle URIs would only need config\nparsing to parse bundle lists, but bundle-uri.c uses other config.h\nfunctionality like key parsing and reading repo settings.\n\nThe resulting library is usable, though it is unergonomic to do so,\ne.g. the caller needs to \"#include git-compat-util.h\" and other\ndependencies, and we don't have an easy way of linking in the required\nobjects. This isn't the end state we want for our libraries, but at\nleast we have _some_ library whose usability we can improve in future\nseries.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n Makefile       |   1 +\n config-parse.c | 601 +++++++++++++++++++++++++++++++++++++++++++++++\n config-parse.h | 155 ++++++++++++\n config.c       | 621 -------------------------------------------------\n config.h       | 119 +---------\n 5 files changed, 758 insertions(+), 739 deletions(-)\n create mode 100644 config-parse.c\n create mode 100644 config-parse.h\n\ndiff --git a/Makefile b/Makefile\nindex fb541dedc9..67e05bcee5 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -992,6 +992,7 @@ LIB_OBJS += compat/obstack.o\n LIB_OBJS += compat/terminal.o\n LIB_OBJS += compat/zlib-uncompress2.o\n LIB_OBJS += config.o\n+LIB_OBJS += config-parse.o\n LIB_OBJS += connect.o\n LIB_OBJS += connected.o\n LIB_OBJS += convert.o\ndiff --git a/config-parse.c b/config-parse.c\nnew file mode 100644\nindex 0000000000..66e5953e29\n--- /dev/null\n+++ b/config-parse.c\n@@ -0,0 +1,601 @@\n+#include \"git-compat-util.h\"\n+#include \"strbuf.h\"\n+#include \"gettext.h\"\n+#include \"hashmap.h\"\n+#include \"utf8.h\"\n+#include \"config-parse.h\"\n+\n+static int config_file_fgetc(struct config_source *conf)\n+{\n+\treturn getc_unlocked(conf->u.file);\n+}\n+\n+static int config_file_ungetc(int c, struct config_source *conf)\n+{\n+\treturn ungetc(c, conf->u.file);\n+}\n+\n+static long config_file_ftell(struct config_source *conf)\n+{\n+\treturn ftell(conf->u.file);\n+}\n+\n+\n+static int config_buf_fgetc(struct config_source *conf)\n+{\n+\tif (conf->u.buf.pos < conf->u.buf.len)\n+\t\treturn conf->u.buf.buf[conf->u.buf.pos++];\n+\n+\treturn EOF;\n+}\n+\n+static int config_buf_ungetc(int c, struct config_source *conf)\n+{\n+\tif (conf->u.buf.pos > 0) {\n+\t\tconf->u.buf.pos--;\n+\t\tif (conf->u.buf.buf[conf->u.buf.pos] != c)\n+\t\t\tBUG(\"config_buf can only ungetc the same character\");\n+\t\treturn c;\n+\t}\n+\n+\treturn EOF;\n+}\n+\n+static long config_buf_ftell(struct config_source *conf)\n+{\n+\treturn conf->u.buf.pos;\n+}\n+\n+static inline int iskeychar(int c)\n+{\n+\treturn isalnum(c) || c == '-';\n+}\n+\n+/*\n+ * Auxiliary function to sanity-check and split the key into the section\n+ * identifier and variable name.\n+ *\n+ * Returns 0 on success, -CONFIG_INVALID_KEY when there is an invalid character\n+ * in the key and -CONFIG_NO_SECTION_OR_NAME if there is no section name in the\n+ * key.\n+ *\n+ * store_key - pointer to char* which will hold a copy of the key with\n+ *             lowercase section and variable name\n+ * baselen - pointer to size_t which will hold the length of the\n+ *           section + subsection part, can be NULL\n+ */\n+int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)\n+{\n+\tsize_t i, baselen;\n+\tint dot;\n+\tconst char *last_dot = strrchr(key, '.');\n+\n+\t/*\n+\t * Since \"key\" actually contains the section name and the real\n+\t * key name separated by a dot, we have to know where the dot is.\n+\t */\n+\n+\tif (last_dot == NULL || last_dot == key) {\n+\t\terror(_(\"key does not contain a section: %s\"), key);\n+\t\treturn -CONFIG_NO_SECTION_OR_NAME;\n+\t}\n+\n+\tif (!last_dot[1]) {\n+\t\terror(_(\"key does not contain variable name: %s\"), key);\n+\t\treturn -CONFIG_NO_SECTION_OR_NAME;\n+\t}\n+\n+\tbaselen = last_dot - key;\n+\tif (baselen_)\n+\t\t*baselen_ = baselen;\n+\n+\t/*\n+\t * Validate the key and while at it, lower case it for matching.\n+\t */\n+\t*store_key = xmallocz(strlen(key));\n+\n+\tdot = 0;\n+\tfor (i = 0; key[i]; i++) {\n+\t\tunsigned char c = key[i];\n+\t\tif (c == '.')\n+\t\t\tdot = 1;\n+\t\t/* Leave the extended basename untouched.. */\n+\t\tif (!dot || i > baselen) {\n+\t\t\tif (!iskeychar(c) ||\n+\t\t\t    (i == baselen + 1 && !isalpha(c))) {\n+\t\t\t\terror(_(\"invalid key: %s\"), key);\n+\t\t\t\tgoto out_free_ret_1;\n+\t\t\t}\n+\t\t\tc = tolower(c);\n+\t\t} else if (c == '\\n') {\n+\t\t\terror(_(\"invalid key (newline): %s\"), key);\n+\t\t\tgoto out_free_ret_1;\n+\t\t}\n+\t\t(*store_key)[i] = c;\n+\t}\n+\n+\treturn 0;\n+\n+out_free_ret_1:\n+\tFREE_AND_NULL(*store_key);\n+\treturn -CONFIG_INVALID_KEY;\n+}\n+\n+static int get_next_char(struct config_source *cs)\n+{\n+\tint c = cs->do_fgetc(cs);\n+\n+\tif (c == '\\r') {\n+\t\t/* DOS like systems */\n+\t\tc = cs->do_fgetc(cs);\n+\t\tif (c != '\\n') {\n+\t\t\tif (c != EOF)\n+\t\t\t\tcs->do_ungetc(c, cs);\n+\t\t\tc = '\\r';\n+\t\t}\n+\t}\n+\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+\t\t * overflows. Note that we can't return an error specifically,\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\treturn 0;\n+\t}\n+\n+\tif (c == '\\n')\n+\t\tcs->linenr++;\n+\tif (c == EOF) {\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 *cs)\n+{\n+\tint quote = 0, comment = 0, space = 0;\n+\n+\tstrbuf_reset(&cs->value);\n+\tfor (;;) {\n+\t\tint c = get_next_char(cs);\n+\t\tif (c == '\\n') {\n+\t\t\tif (quote) {\n+\t\t\t\tcs->linenr--;\n+\t\t\t\treturn NULL;\n+\t\t\t}\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 (cs->value.len)\n+\t\t\t\tspace++;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!quote) {\n+\t\t\tif (c == ';' || c == '#') {\n+\t\t\t\tcomment = 1;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\t\tfor (; space; space--)\n+\t\t\tstrbuf_addch(&cs->value, ' ');\n+\t\tif (c == '\\\\') {\n+\t\t\tc = get_next_char(cs);\n+\t\t\tswitch (c) {\n+\t\t\tcase '\\n':\n+\t\t\t\tcontinue;\n+\t\t\tcase 't':\n+\t\t\t\tc = '\\t';\n+\t\t\t\tbreak;\n+\t\t\tcase 'b':\n+\t\t\t\tc = '\\b';\n+\t\t\t\tbreak;\n+\t\t\tcase 'n':\n+\t\t\t\tc = '\\n';\n+\t\t\t\tbreak;\n+\t\t\t/* Some characters escape as themselves */\n+\t\t\tcase '\\\\': case '\"':\n+\t\t\t\tbreak;\n+\t\t\t/* Reject unknown escape sequences */\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\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}\n+}\n+\n+static int get_value(struct config_source *cs, struct key_value_info *kvi,\n+\t\t     config_fn_t fn, void *data, struct strbuf *name)\n+{\n+\tint c;\n+\tchar *value;\n+\tint ret;\n+\tstruct config_context ctx = {\n+\t\t.kvi = kvi,\n+\t};\n+\n+\t/* Get the full name */\n+\tfor (;;) {\n+\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+\t\tstrbuf_addch(name, tolower(c));\n+\t}\n+\n+\twhile (c == ' ' || c == '\\t')\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(cs);\n+\t\tif (!value)\n+\t\t\treturn -1;\n+\t}\n+\t/*\n+\t * We already consumed the \\n, but we need linenr to point to\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+\tkvi->linenr = cs->linenr;\n+\tret = fn(name->buf, value, &ctx, data);\n+\tif (ret >= 0)\n+\t\tcs->linenr++;\n+\treturn ret;\n+}\n+\n+static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n+\t\t\t\t int c)\n+{\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(cs);\n+\t} while (isspace(c));\n+\n+\t/* We require the format to be '[base \"extension\"]' */\n+\tif (c != '\"')\n+\t\treturn -1;\n+\tstrbuf_addch(name, '.');\n+\n+\tfor (;;) {\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(cs);\n+\t\t\tif (c == '\\n')\n+\t\t\t\tgoto error_incomplete_line;\n+\t\t}\n+\t\tstrbuf_addch(name, c);\n+\t}\n+\n+\t/* Final ']' */\n+\tif (get_next_char(cs) != ']')\n+\t\treturn -1;\n+\treturn 0;\n+error_incomplete_line:\n+\tcs->linenr--;\n+\treturn -1;\n+}\n+\n+static int get_base_var(struct config_source *cs, struct strbuf *name)\n+{\n+\tcs->subsection_case_sensitive = 1;\n+\tfor (;;) {\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(cs, name, c);\n+\t\tif (!iskeychar(c) && c != '.')\n+\t\t\treturn -1;\n+\t\tstrbuf_addch(name, tolower(c));\n+\t}\n+}\n+\n+struct parse_event_data {\n+\tenum config_event_t previous_type;\n+\tsize_t previous_offset;\n+\tconst struct config_parse_options *opts;\n+};\n+\n+static size_t get_corrected_offset(struct config_source *cs,\n+\t\t\t\t   enum config_event_t type)\n+{\n+\tsize_t offset = cs->do_ftell(cs);\n+\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+\t * we will already have advanced to the next event.\n+\t */\n+\tif (type != CONFIG_EVENT_EOF)\n+\t\toffset--;\n+\treturn offset;\n+}\n+\n+static void start_event(struct config_source *cs, enum config_event_t type,\n+\t\t       struct parse_event_data *data)\n+{\n+\tdata->previous_type = type;\n+\tdata->previous_offset = get_corrected_offset(cs, type);\n+}\n+\n+static int flush_event(struct config_source *cs, enum config_event_t type,\n+\t\t       struct parse_event_data *data)\n+{\n+\tif (!data->opts || !data->opts->event_fn)\n+\t\treturn 0;\n+\n+\tif (type == CONFIG_EVENT_WHITESPACE &&\n+\t    data->previous_type == type)\n+\t\treturn 0;\n+\n+\tif (data->previous_type != CONFIG_EVENT_EOF &&\n+\t    data->opts->event_fn(data->previous_type, data->previous_offset,\n+\t\t\t\t get_corrected_offset(cs, type), cs,\n+\t\t\t\t data->opts->event_fn_data) < 0)\n+\t\treturn -1;\n+\n+\treturn 1;\n+}\n+\n+static int do_event(struct config_source *cs, enum config_event_t type,\n+\t\t    struct parse_event_data *data)\n+{\n+\tint maybe_ret;\n+\n+\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n+\t\treturn maybe_ret;\n+\n+\tstart_event(cs, type, data);\n+\n+\treturn 0;\n+}\n+\n+static int do_event_and_flush(struct config_source *cs,\n+\t\t\t      enum config_event_t type,\n+\t\t\t      struct parse_event_data *data)\n+{\n+\tint maybe_ret;\n+\n+\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n+\t\treturn maybe_ret;\n+\n+\tstart_event(cs, type, data);\n+\n+\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n+\t\treturn maybe_ret;\n+\n+\t/*\n+\t * Not actually EOF, but this indicates we don't have a valid event\n+\t * to flush next time around.\n+\t */\n+\tdata->previous_type = CONFIG_EVENT_EOF;\n+\n+\treturn 0;\n+}\n+\n+static void kvi_from_source(struct config_source *cs,\n+\t\t\t    enum config_scope scope,\n+\t\t\t    struct key_value_info *out)\n+{\n+\tout->filename = strintern(cs->name);\n+\tout->origin_type = cs->origin_type;\n+\tout->linenr = cs->linenr;\n+\tout->scope = scope;\n+\tout->path = cs->path;\n+}\n+\n+static int git_parse_source(struct config_source *cs, config_fn_t fn,\n+\t\t\t    struct key_value_info *kvi, void *data,\n+\t\t\t    const struct config_parse_options *opts)\n+{\n+\tint comment = 0;\n+\tsize_t baselen = 0;\n+\tstruct strbuf *var = &cs->var;\n+\n+\t/* U+FEFF Byte Order Mark in UTF8 */\n+\tconst char *bomptr = utf8_bom;\n+\n+\t/* For the parser event callback */\n+\tstruct parse_event_data event_data = {\n+\t\tCONFIG_EVENT_EOF, 0, opts\n+\t};\n+\n+\tfor (;;) {\n+\t\tint c;\n+\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+\t\t\t * own, but e.g. Windows Notepad will do it happily. */\n+\t\t\tif (c == (*bomptr & 0377)) {\n+\t\t\t\tbomptr++;\n+\t\t\t\tcontinue;\n+\t\t\t} else {\n+\t\t\t\t/* Do not tolerate partial BOM. */\n+\t\t\t\tif (bomptr != utf8_bom)\n+\t\t\t\t\tbreak;\n+\t\t\t\t/* No BOM at file beginning. Cool. */\n+\t\t\t\tbomptr = NULL;\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\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\t\treturn -1;\n+\t\t\tcomment = 0;\n+\t\t\tcontinue;\n+\t\t}\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\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\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\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\t\tbreak;\n+\t\t\tstrbuf_addch(var, '.');\n+\t\t\tbaselen = var->len;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (!isalpha(c))\n+\t\t\tbreak;\n+\n+\t\tif (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)\n+\t\t\treturn -1;\n+\n+\t\t/*\n+\t\t * Truncate the var name back to the section header\n+\t\t * stem prior to grabbing the suffix part of the name\n+\t\t * and the value.\n+\t\t */\n+\t\tstrbuf_setlen(var, baselen);\n+\t\tstrbuf_addch(var, tolower(c));\n+\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n+\t\t\tbreak;\n+\t}\n+\n+\tdo_event_and_flush(cs, CONFIG_EVENT_ERROR, &event_data);\n+\treturn -1;\n+}\n+\n+/*\n+ * All source specific fields in the union, die_on_error, name and the callbacks\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,\n+\t\t\t  void *data, enum config_scope scope,\n+\t\t\t  const struct config_parse_options *opts)\n+{\n+\tstruct key_value_info kvi = KVI_INIT;\n+\tint ret;\n+\n+\t/* push config-file parsing state stack */\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+\tkvi_from_source(top, scope, &kvi);\n+\n+\tret = git_parse_source(top, fn, &kvi, data, opts);\n+\n+\tstrbuf_release(&top->value);\n+\tstrbuf_release(&top->var);\n+\n+\treturn ret;\n+}\n+\n+static int do_config_from_file(config_fn_t fn,\n+\t\t\t       const enum config_origin_type origin_type,\n+\t\t\t       const char *name, const char *path, FILE *f,\n+\t\t\t       void *data, enum config_scope scope,\n+\t\t\t       const struct config_parse_options *opts)\n+{\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n+\tint ret;\n+\n+\ttop.u.file = f;\n+\ttop.origin_type = origin_type;\n+\ttop.name = name;\n+\ttop.path = path;\n+\ttop.do_fgetc = config_file_fgetc;\n+\ttop.do_ungetc = config_file_ungetc;\n+\ttop.do_ftell = config_file_ftell;\n+\n+\tflockfile(f);\n+\tret = do_config_from(&top, fn, data, scope, opts);\n+\tfunlockfile(f);\n+\treturn ret;\n+}\n+\n+int git_config_from_stdin(config_fn_t fn, void *data, enum config_scope scope,\n+\t\t\t  const struct config_parse_options *config_opts)\n+{\n+\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n+\t\t\t\t   data, scope, config_opts);\n+}\n+\n+int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n+\t\t\t\t      void *data, enum config_scope scope,\n+\t\t\t\t      const struct config_parse_options *opts)\n+{\n+\tint ret = -1;\n+\tFILE *f;\n+\n+\tif (!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, scope, opts);\n+\t\tfclose(f);\n+\t}\n+\treturn ret;\n+}\n+\n+int git_config_from_mem(config_fn_t fn,\n+\t\t\tconst enum config_origin_type origin_type,\n+\t\t\tconst char *name, const char *buf, size_t len,\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_parse_options *opts)\n+{\n+\tstruct config_source top = CONFIG_SOURCE_INIT;\n+\n+\ttop.u.buf.buf = buf;\n+\ttop.u.buf.len = len;\n+\ttop.u.buf.pos = 0;\n+\ttop.origin_type = origin_type;\n+\ttop.name = name;\n+\ttop.path = NULL;\n+\ttop.do_fgetc = config_buf_fgetc;\n+\ttop.do_ungetc = config_buf_ungetc;\n+\ttop.do_ftell = config_buf_ftell;\n+\n+\treturn do_config_from(&top, fn, data, scope, opts);\n+}\ndiff --git a/config-parse.h b/config-parse.h\nnew file mode 100644\nindex 0000000000..ac73a826d9\n--- /dev/null\n+++ b/config-parse.h\n@@ -0,0 +1,155 @@\n+/*\n+ * Low level config parsing.\n+ */\n+#ifndef CONFIG_PARSE_H\n+#define CONFIG_PARSE_H\n+\n+#include \"strbuf.h\"\n+\n+/* git_config_parse_key() returns these negated: */\n+#define CONFIG_INVALID_KEY 1\n+#define CONFIG_NO_SECTION_OR_NAME 2\n+\n+int git_config_parse_key(const char *, char **, size_t *);\n+\n+enum config_scope {\n+\tCONFIG_SCOPE_UNKNOWN = 0,\n+\tCONFIG_SCOPE_SYSTEM,\n+\tCONFIG_SCOPE_GLOBAL,\n+\tCONFIG_SCOPE_LOCAL,\n+\tCONFIG_SCOPE_WORKTREE,\n+\tCONFIG_SCOPE_COMMAND,\n+\tCONFIG_SCOPE_SUBMODULE,\n+};\n+const char *config_scope_name(enum config_scope scope);\n+\n+enum config_origin_type {\n+\tCONFIG_ORIGIN_UNKNOWN = 0,\n+\tCONFIG_ORIGIN_BLOB,\n+\tCONFIG_ORIGIN_FILE,\n+\tCONFIG_ORIGIN_STDIN,\n+\tCONFIG_ORIGIN_SUBMODULE_BLOB,\n+\tCONFIG_ORIGIN_CMDLINE\n+};\n+\n+enum config_event_t {\n+\tCONFIG_EVENT_SECTION,\n+\tCONFIG_EVENT_ENTRY,\n+\tCONFIG_EVENT_WHITESPACE,\n+\tCONFIG_EVENT_COMMENT,\n+\tCONFIG_EVENT_EOF,\n+\tCONFIG_EVENT_ERROR\n+};\n+\n+struct config_source;\n+/*\n+ * The parser event function (if not NULL) is called with the event type and\n+ * the begin/end offsets of the parsed elements.\n+ *\n+ * Note: for CONFIG_EVENT_ENTRY (i.e. config variables), the trailing newline\n+ * character is considered part of the element.\n+ */\n+typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n+\t\t\t\t\tsize_t begin_offset, size_t end_offset,\n+\t\t\t\t\tstruct config_source *cs,\n+\t\t\t\t\tvoid *event_fn_data);\n+\n+struct config_parse_options {\n+\t/*\n+\t * event_fn and event_fn_data are for internal use only. Handles events\n+\t * emitted by the config parser.\n+\t */\n+\tconfig_parser_event_fn_t event_fn;\n+\tvoid *event_fn_data;\n+};\n+\n+struct config_source {\n+\tstruct config_source *prev;\n+\tunion {\n+\t\tFILE *file;\n+\t\tstruct config_buf {\n+\t\t\tconst char *buf;\n+\t\t\tsize_t len;\n+\t\t\tsize_t pos;\n+\t\t} buf;\n+\t} u;\n+\tenum config_origin_type origin_type;\n+\tconst char *name;\n+\tconst char *path;\n+\tint linenr;\n+\tint eof;\n+\tsize_t total_len;\n+\tstruct strbuf value;\n+\tstruct strbuf var;\n+\tunsigned subsection_case_sensitive : 1;\n+\n+\tint (*do_fgetc)(struct config_source *c);\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+/* Config source metadata for a given config key-value pair */\n+struct key_value_info {\n+\tconst char *filename;\n+\tint linenr;\n+\tenum config_origin_type origin_type;\n+\tenum config_scope scope;\n+\tconst char *path;\n+};\n+#define KVI_INIT { \\\n+\t.filename = NULL, \\\n+\t.linenr = -1, \\\n+\t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n+\t.scope = CONFIG_SCOPE_UNKNOWN, \\\n+\t.path = NULL, \\\n+}\n+\n+/* Captures additional information that a config callback can use. */\n+struct config_context {\n+\t/* Config source metadata for key and value. */\n+\tconst struct key_value_info *kvi;\n+};\n+#define CONFIG_CONTEXT_INIT { 0 }\n+\n+/**\n+ * A config callback function takes four parameters:\n+ *\n+ * - the name of the parsed variable. This is in canonical \"flat\" form: the\n+ *   section, subsection, and variable segments will be separated by dots,\n+ *   and the section and variable segments will be all lowercase. E.g.,\n+ *   `core.ignorecase`, `diff.SomeType.textconv`.\n+ *\n+ * - the value of the found variable, as a string. If the variable had no\n+ *   value specified, the value will be NULL (typically this means it\n+ *   should be interpreted as boolean true).\n+ *\n+ * - the 'config context', that is, additional information about the config\n+ *   iteration operation provided by the config machinery. For example, this\n+ *   includes information about the config source being parsed (e.g. the\n+ *   filename).\n+ *\n+ * - a void pointer passed in by the caller of the config API; this can\n+ *   contain callback-specific data\n+ *\n+ * A config callback should return 0 for success, or -1 if the variable\n+ * could not be parsed properly.\n+ */\n+typedef int (*config_fn_t)(const char *, const char *,\n+\t\t\t   const struct config_context *, void *);\n+\n+int git_config_from_file_with_options(config_fn_t fn, const char *,\n+\t\t\t\t      void *, enum config_scope,\n+\t\t\t\t      const struct config_parse_options *);\n+\n+int git_config_from_mem(config_fn_t fn,\n+\t\t\tconst enum config_origin_type,\n+\t\t\tconst char *name,\n+\t\t\tconst char *buf, size_t len,\n+\t\t\tvoid *data, enum config_scope scope,\n+\t\t\tconst struct config_parse_options *opts);\n+\n+int git_config_from_stdin(config_fn_t fn, void *data, enum config_scope scope,\n+\t\t\t  const struct config_parse_options *config_opts);\n+\n+#endif /* CONFIG_PARSE_H */\ndiff --git a/config.c b/config.c\nindex 50188f469a..e10901514a 100644\n--- a/config.c\n+++ b/config.c\n@@ -42,32 +42,6 @@\n #include \"wrapper.h\"\n #include \"write-or-die.h\"\n \n-struct config_source {\n-\tstruct config_source *prev;\n-\tunion {\n-\t\tFILE *file;\n-\t\tstruct config_buf {\n-\t\t\tconst char *buf;\n-\t\t\tsize_t len;\n-\t\t\tsize_t pos;\n-\t\t} buf;\n-\t} u;\n-\tenum config_origin_type origin_type;\n-\tconst char *name;\n-\tconst char *path;\n-\tint linenr;\n-\tint eof;\n-\tsize_t total_len;\n-\tstruct strbuf value;\n-\tstruct strbuf var;\n-\tunsigned subsection_case_sensitive : 1;\n-\n-\tint (*do_fgetc)(struct config_source *c);\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 static int pack_compression_seen;\n static int zlib_compression_seen;\n \n@@ -82,47 +56,6 @@ static int zlib_compression_seen;\n  */\n static struct config_set protected_config;\n \n-static int config_file_fgetc(struct config_source *conf)\n-{\n-\treturn getc_unlocked(conf->u.file);\n-}\n-\n-static int config_file_ungetc(int c, struct config_source *conf)\n-{\n-\treturn ungetc(c, conf->u.file);\n-}\n-\n-static long config_file_ftell(struct config_source *conf)\n-{\n-\treturn ftell(conf->u.file);\n-}\n-\n-\n-static int config_buf_fgetc(struct config_source *conf)\n-{\n-\tif (conf->u.buf.pos < conf->u.buf.len)\n-\t\treturn conf->u.buf.buf[conf->u.buf.pos++];\n-\n-\treturn EOF;\n-}\n-\n-static int config_buf_ungetc(int c, struct config_source *conf)\n-{\n-\tif (conf->u.buf.pos > 0) {\n-\t\tconf->u.buf.pos--;\n-\t\tif (conf->u.buf.buf[conf->u.buf.pos] != c)\n-\t\t\tBUG(\"config_buf can only ungetc the same character\");\n-\t\treturn c;\n-\t}\n-\n-\treturn EOF;\n-}\n-\n-static long config_buf_ftell(struct config_source *conf)\n-{\n-\treturn conf->u.buf.pos;\n-}\n-\n struct config_include_data {\n \tint depth;\n \tconfig_fn_t fn;\n@@ -528,80 +461,6 @@ void git_config_push_env(const char *spec)\n \tfree(key);\n }\n \n-static inline int iskeychar(int c)\n-{\n-\treturn isalnum(c) || c == '-';\n-}\n-\n-/*\n- * Auxiliary function to sanity-check and split the key into the section\n- * identifier and variable name.\n- *\n- * Returns 0 on success, -1 when there is an invalid character in the key and\n- * -2 if there is no section name in the key.\n- *\n- * store_key - pointer to char* which will hold a copy of the key with\n- *             lowercase section and variable name\n- * baselen - pointer to size_t which will hold the length of the\n- *           section + subsection part, can be NULL\n- */\n-int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)\n-{\n-\tsize_t i, baselen;\n-\tint dot;\n-\tconst char *last_dot = strrchr(key, '.');\n-\n-\t/*\n-\t * Since \"key\" actually contains the section name and the real\n-\t * key name separated by a dot, we have to know where the dot is.\n-\t */\n-\n-\tif (last_dot == NULL || last_dot == key) {\n-\t\terror(_(\"key does not contain a section: %s\"), key);\n-\t\treturn -CONFIG_NO_SECTION_OR_NAME;\n-\t}\n-\n-\tif (!last_dot[1]) {\n-\t\terror(_(\"key does not contain variable name: %s\"), key);\n-\t\treturn -CONFIG_NO_SECTION_OR_NAME;\n-\t}\n-\n-\tbaselen = last_dot - key;\n-\tif (baselen_)\n-\t\t*baselen_ = baselen;\n-\n-\t/*\n-\t * Validate the key and while at it, lower case it for matching.\n-\t */\n-\t*store_key = xmallocz(strlen(key));\n-\n-\tdot = 0;\n-\tfor (i = 0; key[i]; i++) {\n-\t\tunsigned char c = key[i];\n-\t\tif (c == '.')\n-\t\t\tdot = 1;\n-\t\t/* Leave the extended basename untouched.. */\n-\t\tif (!dot || i > baselen) {\n-\t\t\tif (!iskeychar(c) ||\n-\t\t\t    (i == baselen + 1 && !isalpha(c))) {\n-\t\t\t\terror(_(\"invalid key: %s\"), key);\n-\t\t\t\tgoto out_free_ret_1;\n-\t\t\t}\n-\t\t\tc = tolower(c);\n-\t\t} else if (c == '\\n') {\n-\t\t\terror(_(\"invalid key (newline): %s\"), key);\n-\t\t\tgoto out_free_ret_1;\n-\t\t}\n-\t\t(*store_key)[i] = c;\n-\t}\n-\n-\treturn 0;\n-\n-out_free_ret_1:\n-\tFREE_AND_NULL(*store_key);\n-\treturn -CONFIG_INVALID_KEY;\n-}\n-\n static int config_parse_pair(const char *key, const char *value,\n \t\t\t     struct key_value_info *kvi,\n \t\t\t     config_fn_t fn, void *data)\n@@ -786,296 +645,6 @@ 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-{\n-\tint c = cs->do_fgetc(cs);\n-\n-\tif (c == '\\r') {\n-\t\t/* DOS like systems */\n-\t\tc = cs->do_fgetc(cs);\n-\t\tif (c != '\\n') {\n-\t\t\tif (c != EOF)\n-\t\t\t\tcs->do_ungetc(c, cs);\n-\t\t\tc = '\\r';\n-\t\t}\n-\t}\n-\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-\t\t * overflows. Note that we can't return an error specifically,\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\treturn 0;\n-\t}\n-\n-\tif (c == '\\n')\n-\t\tcs->linenr++;\n-\tif (c == EOF) {\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 *cs)\n-{\n-\tint quote = 0, comment = 0, space = 0;\n-\n-\tstrbuf_reset(&cs->value);\n-\tfor (;;) {\n-\t\tint c = get_next_char(cs);\n-\t\tif (c == '\\n') {\n-\t\t\tif (quote) {\n-\t\t\t\tcs->linenr--;\n-\t\t\t\treturn NULL;\n-\t\t\t}\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 (cs->value.len)\n-\t\t\t\tspace++;\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (!quote) {\n-\t\t\tif (c == ';' || c == '#') {\n-\t\t\t\tcomment = 1;\n-\t\t\t\tcontinue;\n-\t\t\t}\n-\t\t}\n-\t\tfor (; space; space--)\n-\t\t\tstrbuf_addch(&cs->value, ' ');\n-\t\tif (c == '\\\\') {\n-\t\t\tc = get_next_char(cs);\n-\t\t\tswitch (c) {\n-\t\t\tcase '\\n':\n-\t\t\t\tcontinue;\n-\t\t\tcase 't':\n-\t\t\t\tc = '\\t';\n-\t\t\t\tbreak;\n-\t\t\tcase 'b':\n-\t\t\t\tc = '\\b';\n-\t\t\t\tbreak;\n-\t\t\tcase 'n':\n-\t\t\t\tc = '\\n';\n-\t\t\t\tbreak;\n-\t\t\t/* Some characters escape as themselves */\n-\t\t\tcase '\\\\': case '\"':\n-\t\t\t\tbreak;\n-\t\t\t/* Reject unknown escape sequences */\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\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}\n-}\n-\n-static int get_value(struct config_source *cs, struct key_value_info *kvi,\n-\t\t     config_fn_t fn, void *data, struct strbuf *name)\n-{\n-\tint c;\n-\tchar *value;\n-\tint ret;\n-\tstruct config_context ctx = {\n-\t\t.kvi = kvi,\n-\t};\n-\n-\t/* Get the full name */\n-\tfor (;;) {\n-\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-\t\tstrbuf_addch(name, tolower(c));\n-\t}\n-\n-\twhile (c == ' ' || c == '\\t')\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(cs);\n-\t\tif (!value)\n-\t\t\treturn -1;\n-\t}\n-\t/*\n-\t * We already consumed the \\n, but we need linenr to point to\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-\tkvi->linenr = cs->linenr;\n-\tret = fn(name->buf, value, &ctx, data);\n-\tif (ret >= 0)\n-\t\tcs->linenr++;\n-\treturn ret;\n-}\n-\n-static int get_extended_base_var(struct config_source *cs, struct strbuf *name,\n-\t\t\t\t int c)\n-{\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(cs);\n-\t} while (isspace(c));\n-\n-\t/* We require the format to be '[base \"extension\"]' */\n-\tif (c != '\"')\n-\t\treturn -1;\n-\tstrbuf_addch(name, '.');\n-\n-\tfor (;;) {\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(cs);\n-\t\t\tif (c == '\\n')\n-\t\t\t\tgoto error_incomplete_line;\n-\t\t}\n-\t\tstrbuf_addch(name, c);\n-\t}\n-\n-\t/* Final ']' */\n-\tif (get_next_char(cs) != ']')\n-\t\treturn -1;\n-\treturn 0;\n-error_incomplete_line:\n-\tcs->linenr--;\n-\treturn -1;\n-}\n-\n-static int get_base_var(struct config_source *cs, struct strbuf *name)\n-{\n-\tcs->subsection_case_sensitive = 1;\n-\tfor (;;) {\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(cs, name, c);\n-\t\tif (!iskeychar(c) && c != '.')\n-\t\t\treturn -1;\n-\t\tstrbuf_addch(name, tolower(c));\n-\t}\n-}\n-\n-struct parse_event_data {\n-\tenum config_event_t previous_type;\n-\tsize_t previous_offset;\n-\tconst struct config_parse_options *opts;\n-};\n-\n-static size_t get_corrected_offset(struct config_source *cs,\n-\t\t\t\t   enum config_event_t type)\n-{\n-\tsize_t offset = cs->do_ftell(cs);\n-\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-\t * we will already have advanced to the next event.\n-\t */\n-\tif (type != CONFIG_EVENT_EOF)\n-\t\toffset--;\n-\treturn offset;\n-}\n-\n-static void start_event(struct config_source *cs, enum config_event_t type,\n-\t\t       struct parse_event_data *data)\n-{\n-\tdata->previous_type = type;\n-\tdata->previous_offset = get_corrected_offset(cs, type);\n-}\n-\n-static int flush_event(struct config_source *cs, enum config_event_t type,\n-\t\t       struct parse_event_data *data)\n-{\n-\tif (!data->opts || !data->opts->event_fn)\n-\t\treturn 0;\n-\n-\tif (type == CONFIG_EVENT_WHITESPACE &&\n-\t    data->previous_type == type)\n-\t\treturn 0;\n-\n-\tif (data->previous_type != CONFIG_EVENT_EOF &&\n-\t    data->opts->event_fn(data->previous_type, data->previous_offset,\n-\t\t\t\t get_corrected_offset(cs, type), cs,\n-\t\t\t\t data->opts->event_fn_data) < 0)\n-\t\treturn -1;\n-\n-\treturn 1;\n-}\n-\n-static int do_event(struct config_source *cs, enum config_event_t type,\n-\t\t    struct parse_event_data *data)\n-{\n-\tint maybe_ret;\n-\n-\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n-\t\treturn maybe_ret;\n-\n-\tstart_event(cs, type, data);\n-\n-\treturn 0;\n-}\n-\n-static int do_event_and_flush(struct config_source *cs,\n-\t\t\t      enum config_event_t type,\n-\t\t\t      struct parse_event_data *data)\n-{\n-\tint maybe_ret;\n-\n-\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n-\t\treturn maybe_ret;\n-\n-\tstart_event(cs, type, data);\n-\n-\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n-\t\treturn maybe_ret;\n-\n-\t/*\n-\t * Not actually EOF, but this indicates we don't have a valid event\n-\t * to flush next time around.\n-\t */\n-\tdata->previous_type = CONFIG_EVENT_EOF;\n-\n-\treturn 0;\n-}\n-\n-static void kvi_from_source(struct config_source *cs,\n-\t\t\t    enum config_scope scope,\n-\t\t\t    struct key_value_info *out)\n-{\n-\tout->filename = strintern(cs->name);\n-\tout->origin_type = cs->origin_type;\n-\tout->linenr = cs->linenr;\n-\tout->scope = scope;\n-\tout->path = cs->path;\n-}\n-\n int git_config_err_fn(enum config_event_t type, size_t begin_offset UNUSED,\n \t\t      size_t end_offset UNUSED, struct config_source *cs,\n \t\t      void *data)\n@@ -1126,98 +695,6 @@ int git_config_err_fn(enum config_event_t type, size_t begin_offset UNUSED,\n \treturn error_return;\n }\n \n-static int git_parse_source(struct config_source *cs, config_fn_t fn,\n-\t\t\t    struct key_value_info *kvi, void *data,\n-\t\t\t    const struct config_parse_options *opts)\n-{\n-\tint comment = 0;\n-\tsize_t baselen = 0;\n-\tstruct strbuf *var = &cs->var;\n-\n-\t/* U+FEFF Byte Order Mark in UTF8 */\n-\tconst char *bomptr = utf8_bom;\n-\n-\t/* For the parser event callback */\n-\tstruct parse_event_data event_data = {\n-\t\tCONFIG_EVENT_EOF, 0, opts\n-\t};\n-\n-\tfor (;;) {\n-\t\tint c;\n-\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-\t\t\t * own, but e.g. Windows Notepad will do it happily. */\n-\t\t\tif (c == (*bomptr & 0377)) {\n-\t\t\t\tbomptr++;\n-\t\t\t\tcontinue;\n-\t\t\t} else {\n-\t\t\t\t/* Do not tolerate partial BOM. */\n-\t\t\t\tif (bomptr != utf8_bom)\n-\t\t\t\t\tbreak;\n-\t\t\t\t/* No BOM at file beginning. Cool. */\n-\t\t\t\tbomptr = NULL;\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\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\t\treturn -1;\n-\t\t\tcomment = 0;\n-\t\t\tcontinue;\n-\t\t}\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\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\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\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\t\tbreak;\n-\t\t\tstrbuf_addch(var, '.');\n-\t\t\tbaselen = var->len;\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (!isalpha(c))\n-\t\t\tbreak;\n-\n-\t\tif (do_event(cs, CONFIG_EVENT_ENTRY, &event_data) < 0)\n-\t\t\treturn -1;\n-\n-\t\t/*\n-\t\t * Truncate the var name back to the section header\n-\t\t * stem prior to grabbing the suffix part of the name\n-\t\t * and the value.\n-\t\t */\n-\t\tstrbuf_setlen(var, baselen);\n-\t\tstrbuf_addch(var, tolower(c));\n-\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n-\t\t\tbreak;\n-\t}\n-\n-\tdo_event_and_flush(cs, CONFIG_EVENT_ERROR, &event_data);\n-\treturn -1;\n-}\n-\n static uintmax_t get_unit_factor(const char *end)\n {\n \tif (!*end)\n@@ -2011,83 +1488,6 @@ int git_default_config(const char *var, const char *value,\n \treturn 0;\n }\n \n-/*\n- * All source specific fields in the union, die_on_error, name and the callbacks\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,\n-\t\t\t  void *data, enum config_scope scope,\n-\t\t\t  const struct config_parse_options *opts)\n-{\n-\tstruct key_value_info kvi = KVI_INIT;\n-\tint ret;\n-\n-\t/* push config-file parsing state stack */\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-\tkvi_from_source(top, scope, &kvi);\n-\n-\tret = git_parse_source(top, fn, &kvi, data, opts);\n-\n-\tstrbuf_release(&top->value);\n-\tstrbuf_release(&top->var);\n-\n-\treturn ret;\n-}\n-\n-static int do_config_from_file(config_fn_t fn,\n-\t\t\t       const enum config_origin_type origin_type,\n-\t\t\t       const char *name, const char *path, FILE *f,\n-\t\t\t       void *data, enum config_scope scope,\n-\t\t\t       const struct config_parse_options *opts)\n-{\n-\tstruct config_source top = CONFIG_SOURCE_INIT;\n-\tint ret;\n-\n-\ttop.u.file = f;\n-\ttop.origin_type = origin_type;\n-\ttop.name = name;\n-\ttop.path = path;\n-\ttop.do_fgetc = config_file_fgetc;\n-\ttop.do_ungetc = config_file_ungetc;\n-\ttop.do_ftell = config_file_ftell;\n-\n-\tflockfile(f);\n-\tret = do_config_from(&top, fn, data, scope, opts);\n-\tfunlockfile(f);\n-\treturn ret;\n-}\n-\n-static int git_config_from_stdin(config_fn_t fn, void *data,\n-\t\t\t\t enum config_scope scope,\n-\t\t\t\t const struct config_parse_options *config_opts)\n-{\n-\treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n-\t\t\t\t   data, scope, config_opts);\n-}\n-\n-int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n-\t\t\t\t      void *data, enum config_scope scope,\n-\t\t\t\t      const struct config_parse_options *opts)\n-{\n-\tint ret = -1;\n-\tFILE *f;\n-\n-\tif (!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, scope, opts);\n-\t\tfclose(f);\n-\t}\n-\treturn ret;\n-}\n-\n int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n {\n \tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n@@ -2096,27 +1496,6 @@ int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n \t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, &config_opts);\n }\n \n-int git_config_from_mem(config_fn_t fn,\n-\t\t\tconst enum config_origin_type origin_type,\n-\t\t\tconst char *name, const char *buf, size_t len,\n-\t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_parse_options *opts)\n-{\n-\tstruct config_source top = CONFIG_SOURCE_INIT;\n-\n-\ttop.u.buf.buf = buf;\n-\ttop.u.buf.len = len;\n-\ttop.u.buf.pos = 0;\n-\ttop.origin_type = origin_type;\n-\ttop.name = name;\n-\ttop.path = NULL;\n-\ttop.do_fgetc = config_buf_fgetc;\n-\ttop.do_ungetc = config_buf_ungetc;\n-\ttop.do_ftell = config_buf_ftell;\n-\n-\treturn do_config_from(&top, fn, data, scope, opts);\n-}\n-\n int git_config_from_blob_oid(config_fn_t fn,\n \t\t\t      const char *name,\n \t\t\t      struct repository *repo,\ndiff --git a/config.h b/config.h\nindex 8ad399580f..3bad5e1c32 100644\n--- a/config.h\n+++ b/config.h\n@@ -4,7 +4,7 @@\n #include \"hashmap.h\"\n #include \"string-list.h\"\n #include \"repository.h\"\n-\n+#include \"config-parse.h\"\n \n /**\n  * The config API gives callers a way to access Git configuration files\n@@ -23,9 +23,6 @@\n \n struct object_id;\n \n-/* git_config_parse_key() returns these negated: */\n-#define CONFIG_INVALID_KEY 1\n-#define CONFIG_NO_SECTION_OR_NAME 2\n /* git_config_set_gently(), git_config_set_multivar_gently() return the above or these: */\n #define CONFIG_NO_LOCK -1\n #define CONFIG_INVALID_FILE 3\n@@ -36,17 +33,6 @@ struct object_id;\n \n #define CONFIG_REGEX_NONE ((void *)1)\n \n-enum config_scope {\n-\tCONFIG_SCOPE_UNKNOWN = 0,\n-\tCONFIG_SCOPE_SYSTEM,\n-\tCONFIG_SCOPE_GLOBAL,\n-\tCONFIG_SCOPE_LOCAL,\n-\tCONFIG_SCOPE_WORKTREE,\n-\tCONFIG_SCOPE_COMMAND,\n-\tCONFIG_SCOPE_SUBMODULE,\n-};\n-const char *config_scope_name(enum config_scope scope);\n-\n struct git_config_source {\n \tunsigned int use_stdin:1;\n \tconst char *file;\n@@ -54,46 +40,6 @@ struct git_config_source {\n \tenum config_scope scope;\n };\n \n-enum config_origin_type {\n-\tCONFIG_ORIGIN_UNKNOWN = 0,\n-\tCONFIG_ORIGIN_BLOB,\n-\tCONFIG_ORIGIN_FILE,\n-\tCONFIG_ORIGIN_STDIN,\n-\tCONFIG_ORIGIN_SUBMODULE_BLOB,\n-\tCONFIG_ORIGIN_CMDLINE\n-};\n-\n-enum config_event_t {\n-\tCONFIG_EVENT_SECTION,\n-\tCONFIG_EVENT_ENTRY,\n-\tCONFIG_EVENT_WHITESPACE,\n-\tCONFIG_EVENT_COMMENT,\n-\tCONFIG_EVENT_EOF,\n-\tCONFIG_EVENT_ERROR\n-};\n-\n-struct config_source;\n-/*\n- * The parser event function (if not NULL) is called with the event type and\n- * the begin/end offsets of the parsed elements.\n- *\n- * Note: for CONFIG_EVENT_ENTRY (i.e. config variables), the trailing newline\n- * character is considered part of the element.\n- */\n-typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n-\t\t\t\t\tsize_t begin_offset, size_t end_offset,\n-\t\t\t\t\tstruct config_source *cs,\n-\t\t\t\t\tvoid *event_fn_data);\n-\n-struct config_parse_options {\n-\t/*\n-\t * event_fn and event_fn_data are for internal use only. Handles events\n-\t * emitted by the config parser.\n-\t */\n-\tconfig_parser_event_fn_t event_fn;\n-\tvoid *event_fn_data;\n-};\n-\n #define CP_OPTS_INIT(error_action) { \\\n \t.event_fn = git_config_err_fn, \\\n \t.event_fn_data = (enum config_error_action []){(error_action)}, \\\n@@ -126,59 +72,8 @@ enum config_error_action {\n int git_config_err_fn(enum config_event_t type, size_t begin_offset,\n \t\t      size_t end_offset, struct config_source *cs,\n \t\t      void *event_fn_data);\n-\n-/* Config source metadata for a given config key-value pair */\n-struct key_value_info {\n-\tconst char *filename;\n-\tint linenr;\n-\tenum config_origin_type origin_type;\n-\tenum config_scope scope;\n-\tconst char *path;\n-};\n-#define KVI_INIT { \\\n-\t.filename = NULL, \\\n-\t.linenr = -1, \\\n-\t.origin_type = CONFIG_ORIGIN_UNKNOWN, \\\n-\t.scope = CONFIG_SCOPE_UNKNOWN, \\\n-\t.path = NULL, \\\n-}\n-\n-/* Captures additional information that a config callback can use. */\n-struct config_context {\n-\t/* Config source metadata for key and value. */\n-\tconst struct key_value_info *kvi;\n-};\n-#define CONFIG_CONTEXT_INIT { 0 }\n-\n-/**\n- * A config callback function takes four parameters:\n- *\n- * - the name of the parsed variable. This is in canonical \"flat\" form: the\n- *   section, subsection, and variable segments will be separated by dots,\n- *   and the section and variable segments will be all lowercase. E.g.,\n- *   `core.ignorecase`, `diff.SomeType.textconv`.\n- *\n- * - the value of the found variable, as a string. If the variable had no\n- *   value specified, the value will be NULL (typically this means it\n- *   should be interpreted as boolean true).\n- *\n- * - the 'config context', that is, additional information about the config\n- *   iteration operation provided by the config machinery. For example, this\n- *   includes information about the config source being parsed (e.g. the\n- *   filename).\n- *\n- * - a void pointer passed in by the caller of the config API; this can\n- *   contain callback-specific data\n- *\n- * A config callback should return 0 for success, or -1 if the variable\n- * could not be parsed properly.\n- */\n-typedef int (*config_fn_t)(const char *, const char *,\n-\t\t\t   const struct config_context *, void *);\n-\n int git_default_config(const char *, const char *,\n \t\t       const struct config_context *, void *);\n-\n /**\n  * Read a specific file in git-config format.\n  * This function takes the same callback and data parameters as `git_config`.\n@@ -186,16 +81,6 @@ int git_default_config(const char *, const char *,\n  * Unlike git_config(), this function does not respect includes.\n  */\n int git_config_from_file(config_fn_t fn, const char *, void *);\n-\n-int git_config_from_file_with_options(config_fn_t fn, const char *,\n-\t\t\t\t      void *, enum config_scope,\n-\t\t\t\t      const struct config_parse_options *);\n-int git_config_from_mem(config_fn_t fn,\n-\t\t\tconst enum config_origin_type,\n-\t\t\tconst char *name,\n-\t\t\tconst char *buf, size_t len,\n-\t\t\tvoid *data, enum config_scope scope,\n-\t\t\tconst struct config_parse_options *opts);\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     struct repository *repo,\n \t\t\t     const struct object_id *oid, void *data,\n@@ -333,8 +218,6 @@ int repo_config_set_worktree_gently(struct repository *, const char *, const cha\n  */\n void git_config_set(const char *, const char *);\n \n-int git_config_parse_key(const char *, char **, size_t *);\n-\n /*\n  * The following macros specify flag bits that alter the behavior\n  * of the git_config_set_multivar*() methods.\n-- \n2.42.0.515.g380fc7ccd1-goog\n\n"},{"id":"482111","messageId":"8a1463c223497fca2fd3f11a54db5d7e52d1d08a.1695330852.git.steadmon@google.com","threadId":"60017","inReplyTo":"cover.1695330852.git.steadmon@google.com","subject":"[PATCH v3 2/5] config: split do_event() into start and flush operations","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-09-21T21:17:21Z","receivedAt":"2023-09-21T21:40:22Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"When handling config-parsing events, the current do_event() handler is a\nbit confusing; calling it with a specific event type records the initial\noffset where the event occurred, and runs the supplied callback against\nthe previous event (whose end offset is now known).\n\nSplit this operation into \"start_event\" and \"flush_event\" functions.\nThen reimplement \"do_event\" (preserving the original behavior) using the\nnewly split functions.\n\nIn a later change, we can use these building blocks to also handle\n\"immediate\" events, where we want to run the callback without having to\ncalculate an end offset for the event.\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n config.c | 50 ++++++++++++++++++++++++++++++++++++--------------\n 1 file changed, 36 insertions(+), 14 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 1518f70fc2..ff138500a2 100644\n--- a/config.c\n+++ b/config.c\n@@ -985,19 +985,11 @@ struct parse_event_data {\n \tconst struct config_parse_options *opts;\n };\n \n-static int do_event(struct config_source *cs, enum config_event_t type,\n-\t\t    struct parse_event_data *data)\n+static size_t get_corrected_offset(struct config_source *cs,\n+\t\t\t\t   enum config_event_t type)\n {\n-\tsize_t offset;\n-\n-\tif (!data->opts || !data->opts->event_fn)\n-\t\treturn 0;\n-\n-\tif (type == CONFIG_EVENT_WHITESPACE &&\n-\t    data->previous_type == type)\n-\t\treturn 0;\n+\tsize_t offset = cs->do_ftell(cs);\n \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@@ -1005,14 +997,44 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \t */\n \tif (type != CONFIG_EVENT_EOF)\n \t\toffset--;\n+\treturn offset;\n+}\n+\n+static void start_event(struct config_source *cs, enum config_event_t type,\n+\t\t       struct parse_event_data *data)\n+{\n+\tdata->previous_type = type;\n+\tdata->previous_offset = get_corrected_offset(cs, type);\n+}\n+\n+static int flush_event(struct config_source *cs, enum config_event_t type,\n+\t\t       struct parse_event_data *data)\n+{\n+\tif (!data->opts || !data->opts->event_fn)\n+\t\treturn 0;\n+\n+\tif (type == CONFIG_EVENT_WHITESPACE &&\n+\t    data->previous_type == type)\n+\t\treturn 0;\n \n \tif (data->previous_type != CONFIG_EVENT_EOF &&\n \t    data->opts->event_fn(data->previous_type, data->previous_offset,\n-\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n+\t\t\t\t get_corrected_offset(cs, type), cs,\n+\t\t\t\t data->opts->event_fn_data) < 0)\n \t\treturn -1;\n \n-\tdata->previous_type = type;\n-\tdata->previous_offset = offset;\n+\treturn 1;\n+}\n+\n+static int do_event(struct config_source *cs, enum config_event_t type,\n+\t\t    struct parse_event_data *data)\n+{\n+\tint maybe_ret;\n+\n+\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n+\t\treturn maybe_ret;\n+\n+\tstart_event(cs, type, data);\n \n \treturn 0;\n }\n-- \n2.42.0.515.g380fc7ccd1-goog\n\n"},{"id":"482113","messageId":"cover.1695330852.git.steadmon@google.com","threadId":"60017","inReplyTo":"pull.1551.git.git.1689891436.gitgitgadget@gmail.com","subject":"[PATCH v3 0/5] config-parse: create config parsing library","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-09-21T21:17:19Z","receivedAt":"2023-09-21T21:41:06Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"Config parsing no longer uses global state as of gc/config-context, so the\nnatural next step for libification is to turn that into its own library.\nThis series starts that process by moving config parsing into\nconfig-parse.[c|h] so that other programs can include this functionality\nwithout pulling in all of config.[c|h].\n\nOpen questions:\n- How do folks feel about the do_event() refactor in patches 2 & 3?\n\nChanges since v2:\n- Added patch 2/5 to refactor do_event() into start_event() and\n  flush_event().\n- In patch 3/5, we can now add do_event_and_flush() to immediately run\n  an event callback, rather than having to do_event() twice in a row.\n\nChanges since v1.5:\n- Dropped patch 1/5: config: return positive from git_config_parse_key()\n\n\nGlen Choo (4):\n  config: split out config_parse_options\n  config: report config parse errors using cb\n  config.c: accept config_parse_options in git_config_from_stdin\n  config-parse: split library out of config.[c|h]\n\nJosh Steadmon (1):\n  config: split do_event() into start and flush operations\n\n Makefile           |   1 +\n builtin/config.c   |   4 +-\n bundle-uri.c       |   4 +-\n config-parse.c     | 601 +++++++++++++++++++++++++++++++++++++++++\n config-parse.h     | 155 +++++++++++\n config.c           | 658 ++++-----------------------------------------\n config.h           | 134 +--------\n fsck.c             |   4 +-\n submodule-config.c |   9 +-\n 9 files changed, 836 insertions(+), 734 deletions(-)\n create mode 100644 config-parse.c\n create mode 100644 config-parse.h\n\nRange-diff against v2:\n1:  5c676fbac3 ! 1:  fa55b7836f config: split out config_parse_options\n    @@ Metadata\n      ## Commit message ##\n         config: split out config_parse_options\n     \n    -    \"struct config_options\" is a disjoint set of options options used by the\n    -    config parser (e.g. event listners) and options used by\n    -    config_with_options() (e.g. to handle includes, choose which config\n    -    files to parse). Split parser-only options into config_parse_options.\n    +    \"struct config_options\" is a disjoint set of options used by the config\n    +    parser (e.g. event listeners) and options used by config_with_options()\n    +    (e.g. to handle includes, choose which config files to parse). Split\n    +    parser-only options into config_parse_options.\n     \n         Signed-off-by: Glen Choo <chooglen@google.com>\n     \n      ## bundle-uri.c ##\n-:  ---------- > 2:  8a1463c223 config: split do_event() into start and flush operations\n2:  cb92a1f2e3 ! 3:  a888045c04 config: report config parse errors using cb\n    @@ Commit message\n         CONFIG_ERROR_UNSET and the config_source 'default', since all callers\n         are now expected to specify the error handling they want.\n     \n    +    Add a new \"do_event_and_flush\" function for running event callbacks\n    +    immediately, where the event does not need to calculate an end offset.\n    +\n         Signed-off-by: Glen Choo <chooglen@google.com>\n     \n      ## builtin/config.c ##\n    @@ config.c: static int add_remote_url(const char *var, const char *value,\n      \n      \topts = *inc->opts;\n      \topts.unconditional_remote_url = 1;\n    +@@ config.c: static int do_event(struct config_source *cs, enum config_event_t type,\n    + \treturn 0;\n    + }\n    + \n    ++static int do_event_and_flush(struct config_source *cs,\n    ++\t\t\t      enum config_event_t type,\n    ++\t\t\t      struct parse_event_data *data)\n    ++{\n    ++\tint maybe_ret;\n    ++\n    ++\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n    ++\t\treturn maybe_ret;\n    ++\n    ++\tstart_event(cs, type, data);\n    ++\n    ++\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n    ++\t\treturn maybe_ret;\n    ++\n    ++\t/*\n    ++\t * Not actually EOF, but this indicates we don't have a valid event\n    ++\t * to flush next time around.\n    ++\t */\n    ++\tdata->previous_type = CONFIG_EVENT_EOF;\n    ++\n    ++\treturn 0;\n    ++}\n    ++\n    + static void kvi_from_source(struct config_source *cs,\n    + \t\t\t    enum config_scope scope,\n    + \t\t\t    struct key_value_info *out)\n     @@ config.c: static void kvi_from_source(struct config_source *cs,\n      \tout->path = cs->path;\n      }\n    @@ config.c: static int git_parse_source(struct config_source *cs, config_fn_t fn,\n     -\n     -\tfree(error_msg);\n     -\treturn error_return;\n    -+\t/*\n    -+\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n    -+\t * in order for our callback to receive the error event, we have to call\n    -+\t * do_event twice\n    -+\t */\n    -+\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n    -+\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n    ++\tdo_event_and_flush(cs, CONFIG_EVENT_ERROR, &event_data);\n     +\treturn -1;\n      }\n      \n    @@ config.c: void read_early_config(config_fn_t cb, void *data)\n      void read_very_early_config(config_fn_t cb, void *data)\n      {\n     -\tstruct config_options opts = { 0 };\n    +-\n    +-\topts.respect_includes = 1;\n    +-\topts.ignore_repo = 1;\n    +-\topts.ignore_worktree = 1;\n    +-\topts.ignore_cmdline = 1;\n    +-\topts.system_gently = 1;\n     +\tstruct config_options opts = {\n    ++\t\t.respect_includes = 1,\n    ++\t\t.ignore_repo = 1,\n    ++\t\t.ignore_worktree = 1,\n    ++\t\t.ignore_cmdline = 1,\n    ++\t\t.system_gently = 1,\n     +\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n     +\t};\n      \n    - \topts.respect_includes = 1;\n    - \topts.ignore_repo = 1;\n    + \tconfig_with_options(cb, data, NULL, NULL, &opts);\n    + }\n     @@ config.c: int git_configset_get_pathname(struct config_set *set, const char *key, const ch\n      /* Functions use to read configuration from a repository */\n      static void repo_read_config(struct repository *repo)\n    @@ config.c: int git_configset_get_pathname(struct config_set *set, const char *key\n      \n      \topts.respect_includes = 1;\n      \topts.commondir = repo->commondir;\n    -@@ config.c: int repo_config_get_pathname(struct repository *repo,\n    - static void read_protected_config(void)\n    - {\n    - \tstruct config_options opts = {\n    --\t\t.respect_includes = 1,\n    --\t\t.ignore_repo = 1,\n    --\t\t.ignore_worktree = 1,\n    --\t\t.system_gently = 1,\n    +@@ config.c: static void read_protected_config(void)\n    + \t\t.ignore_repo = 1,\n    + \t\t.ignore_worktree = 1,\n    + \t\t.system_gently = 1,\n     +\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n      \t};\n      \n    -+\topts.respect_includes = 1;\n    -+\topts.ignore_repo = 1;\n    -+\topts.ignore_worktree = 1;\n    -+\topts.system_gently = 1;\n     +\n      \tgit_configset_init(&protected_config);\n      \tconfig_with_options(config_set_callback, &protected_config, NULL,\n3:  e034d0780c = 4:  49d4b64991 config.c: accept config_parse_options in git_config_from_stdin\n4:  74c5dcd5a2 ! 5:  e59ca992d0 config-parse: split library out of config.[c|h]\n    @@ config-parse.c (new)\n     +\tconst struct config_parse_options *opts;\n     +};\n     +\n    -+static int do_event(struct config_source *cs, enum config_event_t type,\n    -+\t\t    struct parse_event_data *data)\n    ++static size_t get_corrected_offset(struct config_source *cs,\n    ++\t\t\t\t   enum config_event_t type)\n     +{\n    -+\tsize_t offset;\n    -+\n    -+\tif (!data->opts || !data->opts->event_fn)\n    -+\t\treturn 0;\n    ++\tsize_t offset = cs->do_ftell(cs);\n     +\n    -+\tif (type == CONFIG_EVENT_WHITESPACE &&\n    -+\t    data->previous_type == type)\n    -+\t\treturn 0;\n    -+\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    @@ config-parse.c (new)\n     +\t */\n     +\tif (type != CONFIG_EVENT_EOF)\n     +\t\toffset--;\n    ++\treturn offset;\n    ++}\n    ++\n    ++static void start_event(struct config_source *cs, enum config_event_t type,\n    ++\t\t       struct parse_event_data *data)\n    ++{\n    ++\tdata->previous_type = type;\n    ++\tdata->previous_offset = get_corrected_offset(cs, type);\n    ++}\n    ++\n    ++static int flush_event(struct config_source *cs, enum config_event_t type,\n    ++\t\t       struct parse_event_data *data)\n    ++{\n    ++\tif (!data->opts || !data->opts->event_fn)\n    ++\t\treturn 0;\n    ++\n    ++\tif (type == CONFIG_EVENT_WHITESPACE &&\n    ++\t    data->previous_type == type)\n    ++\t\treturn 0;\n     +\n     +\tif (data->previous_type != CONFIG_EVENT_EOF &&\n     +\t    data->opts->event_fn(data->previous_type, data->previous_offset,\n    -+\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n    ++\t\t\t\t get_corrected_offset(cs, type), cs,\n    ++\t\t\t\t data->opts->event_fn_data) < 0)\n     +\t\treturn -1;\n     +\n    -+\tdata->previous_type = type;\n    -+\tdata->previous_offset = offset;\n    ++\treturn 1;\n    ++}\n    ++\n    ++static int do_event(struct config_source *cs, enum config_event_t type,\n    ++\t\t    struct parse_event_data *data)\n    ++{\n    ++\tint maybe_ret;\n    ++\n    ++\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n    ++\t\treturn maybe_ret;\n    ++\n    ++\tstart_event(cs, type, data);\n    ++\n    ++\treturn 0;\n    ++}\n    ++\n    ++static int do_event_and_flush(struct config_source *cs,\n    ++\t\t\t      enum config_event_t type,\n    ++\t\t\t      struct parse_event_data *data)\n    ++{\n    ++\tint maybe_ret;\n    ++\n    ++\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n    ++\t\treturn maybe_ret;\n    ++\n    ++\tstart_event(cs, type, data);\n    ++\n    ++\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n    ++\t\treturn maybe_ret;\n    ++\n    ++\t/*\n    ++\t * Not actually EOF, but this indicates we don't have a valid event\n    ++\t * to flush next time around.\n    ++\t */\n    ++\tdata->previous_type = CONFIG_EVENT_EOF;\n     +\n     +\treturn 0;\n     +}\n    @@ config-parse.c (new)\n     +\t\tif (get_value(cs, kvi, fn, data, var) < 0)\n     +\t\t\tbreak;\n     +\t}\n    -+\t/*\n    -+\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n    -+\t * in order for our callback to receive the error event, we have to call\n    -+\t * do_event twice\n    -+\t */\n    -+\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n    -+\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n    ++\n    ++\tdo_event_and_flush(cs, CONFIG_EVENT_ERROR, &event_data);\n     +\treturn -1;\n     +}\n     +\n    @@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)\n     -\tconst struct config_parse_options *opts;\n     -};\n     -\n    --static int do_event(struct config_source *cs, enum config_event_t type,\n    --\t\t    struct parse_event_data *data)\n    +-static size_t get_corrected_offset(struct config_source *cs,\n    +-\t\t\t\t   enum config_event_t type)\n     -{\n    --\tsize_t offset;\n    --\n    --\tif (!data->opts || !data->opts->event_fn)\n    --\t\treturn 0;\n    +-\tsize_t offset = cs->do_ftell(cs);\n     -\n    --\tif (type == CONFIG_EVENT_WHITESPACE &&\n    --\t    data->previous_type == type)\n    --\t\treturn 0;\n    --\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    @@ config.c: int git_config_from_parameters(config_fn_t fn, void *data)\n     -\t */\n     -\tif (type != CONFIG_EVENT_EOF)\n     -\t\toffset--;\n    +-\treturn offset;\n    +-}\n    +-\n    +-static void start_event(struct config_source *cs, enum config_event_t type,\n    +-\t\t       struct parse_event_data *data)\n    +-{\n    +-\tdata->previous_type = type;\n    +-\tdata->previous_offset = get_corrected_offset(cs, type);\n    +-}\n    +-\n    +-static int flush_event(struct config_source *cs, enum config_event_t type,\n    +-\t\t       struct parse_event_data *data)\n    +-{\n    +-\tif (!data->opts || !data->opts->event_fn)\n    +-\t\treturn 0;\n    +-\n    +-\tif (type == CONFIG_EVENT_WHITESPACE &&\n    +-\t    data->previous_type == type)\n    +-\t\treturn 0;\n     -\n     -\tif (data->previous_type != CONFIG_EVENT_EOF &&\n     -\t    data->opts->event_fn(data->previous_type, data->previous_offset,\n    --\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n    +-\t\t\t\t get_corrected_offset(cs, type), cs,\n    +-\t\t\t\t data->opts->event_fn_data) < 0)\n     -\t\treturn -1;\n     -\n    --\tdata->previous_type = type;\n    --\tdata->previous_offset = offset;\n    +-\treturn 1;\n    +-}\n    +-\n    +-static int do_event(struct config_source *cs, enum config_event_t type,\n    +-\t\t    struct parse_event_data *data)\n    +-{\n    +-\tint maybe_ret;\n    +-\n    +-\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n    +-\t\treturn maybe_ret;\n    +-\n    +-\tstart_event(cs, type, data);\n    +-\n    +-\treturn 0;\n    +-}\n    +-\n    +-static int do_event_and_flush(struct config_source *cs,\n    +-\t\t\t      enum config_event_t type,\n    +-\t\t\t      struct parse_event_data *data)\n    +-{\n    +-\tint maybe_ret;\n    +-\n    +-\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n    +-\t\treturn maybe_ret;\n    +-\n    +-\tstart_event(cs, type, data);\n    +-\n    +-\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n    +-\t\treturn maybe_ret;\n    +-\n    +-\t/*\n    +-\t * Not actually EOF, but this indicates we don't have a valid event\n    +-\t * to flush next time around.\n    +-\t */\n    +-\tdata->previous_type = CONFIG_EVENT_EOF;\n     -\n     -\treturn 0;\n     -}\n    @@ config.c: int git_config_err_fn(enum config_event_t type, size_t begin_offset UN\n     -\t\t\tbreak;\n     -\t}\n     -\n    --\t/*\n    --\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n    --\t * in order for our callback to receive the error event, we have to call\n    --\t * do_event twice\n    --\t */\n    --\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n    --\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n    +-\tdo_event_and_flush(cs, CONFIG_EVENT_ERROR, &event_data);\n     -\treturn -1;\n     -}\n     -\n\nbase-commit: aa9166bcc0ba654fc21f198a30647ec087f733ed\n-- \n2.42.0.515.g380fc7ccd1-goog\n\n"},{"id":"482114","messageId":"a888045c04d27864edf5751ea8641fdba596779c.1695330852.git.steadmon@google.com","threadId":"60017","inReplyTo":"cover.1695330852.git.steadmon@google.com","subject":"[PATCH v3 3/5] config: report config parse errors using cb","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-09-21T21:17:22Z","receivedAt":"2023-09-21T21:41:13Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nIn a subsequent commit, config parsing will become its own library, and\nit's likely that the caller will want flexibility in handling errors\n(instead of being limited to the error handling we have in-tree).\n\nMove the Git-specific error handling into a config_parser_event_fn_t\nthat responds to config errors, and make git_parse_source() always\nreturn -1 (careful inspection shows that it was always returning -1\nalready). This makes CONFIG_ERROR_SILENT obsolete since that is\nequivalent to not specifying an error event listener. Also, remove\nCONFIG_ERROR_UNSET and the config_source 'default', since all callers\nare now expected to specify the error handling they want.\n\nAdd a new \"do_event_and_flush\" function for running event callbacks\nimmediately, where the event does not need to calculate an end offset.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n builtin/config.c   |   4 +-\n bundle-uri.c       |   4 +-\n config.c           | 195 ++++++++++++++++++++++++++++-----------------\n config.h           |  20 +++--\n fsck.c             |   4 +-\n submodule-config.c |   9 ++-\n 6 files changed, 147 insertions(+), 89 deletions(-)\n\ndiff --git a/builtin/config.c b/builtin/config.c\nindex 1c75cbc43d..e2cf49de7a 100644\n--- a/builtin/config.c\n+++ b/builtin/config.c\n@@ -42,7 +42,9 @@ static int actions, type;\n static char *default_value;\n static int end_nul;\n static int respect_includes_opt = -1;\n-static struct config_options config_options;\n+static struct config_options config_options = {\n+\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE)\n+};\n static int show_origin;\n static int show_scope;\n static int fixed_value;\ndiff --git a/bundle-uri.c b/bundle-uri.c\nindex f93ca6a486..856bffdcad 100644\n--- a/bundle-uri.c\n+++ b/bundle-uri.c\n@@ -237,9 +237,7 @@ int bundle_uri_parse_config_format(const char *uri,\n \t\t\t\t   struct bundle_list *list)\n {\n \tint result;\n-\tstruct config_parse_options opts = {\n-\t\t.error_action = CONFIG_ERROR_ERROR,\n-\t};\n+\tstruct config_parse_options opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);\n \n \tif (!list->baseURI) {\n \t\tstruct strbuf baseURI = STRBUF_INIT;\ndiff --git a/config.c b/config.c\nindex ff138500a2..0c4f1a2874 100644\n--- a/config.c\n+++ b/config.c\n@@ -55,7 +55,6 @@ struct config_source {\n \tenum config_origin_type origin_type;\n \tconst char *name;\n \tconst char *path;\n-\tenum config_error_action default_error_action;\n \tint linenr;\n \tint eof;\n \tsize_t total_len;\n@@ -185,13 +184,15 @@ static int handle_path_include(const struct key_value_info *kvi,\n \t}\n \n \tif (!access_or_die(path, R_OK, 0)) {\n+\t\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n+\n \t\tif (++inc->depth > MAX_INCLUDE_DEPTH)\n \t\t\tdie(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,\n \t\t\t    !kvi ? \"<unknown>\" :\n \t\t\t    kvi->filename ? kvi->filename :\n \t\t\t    \"the command line\");\n \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n-\t\t\t\t\t\t\tkvi->scope, NULL);\n+\t\t\t\t\t\t\tkvi->scope, &config_opts);\n \t\tinc->depth--;\n \t}\n cleanup:\n@@ -339,7 +340,9 @@ static int add_remote_url(const char *var, const char *value,\n \n static void populate_remote_urls(struct config_include_data *inc)\n {\n-\tstruct config_options opts;\n+\tstruct config_options opts = {\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t};\n \n \topts = *inc->opts;\n \topts.unconditional_remote_url = 1;\n@@ -1039,6 +1042,29 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n \treturn 0;\n }\n \n+static int do_event_and_flush(struct config_source *cs,\n+\t\t\t      enum config_event_t type,\n+\t\t\t      struct parse_event_data *data)\n+{\n+\tint maybe_ret;\n+\n+\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n+\t\treturn maybe_ret;\n+\n+\tstart_event(cs, type, data);\n+\n+\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n+\t\treturn maybe_ret;\n+\n+\t/*\n+\t * Not actually EOF, but this indicates we don't have a valid event\n+\t * to flush next time around.\n+\t */\n+\tdata->previous_type = CONFIG_EVENT_EOF;\n+\n+\treturn 0;\n+}\n+\n static void kvi_from_source(struct config_source *cs,\n \t\t\t    enum config_scope scope,\n \t\t\t    struct key_value_info *out)\n@@ -1050,6 +1076,56 @@ static void kvi_from_source(struct config_source *cs,\n \tout->path = cs->path;\n }\n \n+int git_config_err_fn(enum config_event_t type, size_t begin_offset UNUSED,\n+\t\t      size_t end_offset UNUSED, struct config_source *cs,\n+\t\t      void *data)\n+{\n+\tchar *error_msg = NULL;\n+\tint error_return = 0;\n+\tenum config_error_action *action = data;\n+\n+\tif (type != CONFIG_EVENT_ERROR)\n+\t\treturn 0;\n+\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      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      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      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       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       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      cs->linenr, cs->name);\n+\t}\n+\n+\tswitch (*action) {\n+\tcase CONFIG_ERROR_DIE:\n+\t\tdie(\"%s\", error_msg);\n+\t\tbreak;\n+\tcase CONFIG_ERROR_ERROR:\n+\t\terror_return = error(\"%s\", error_msg);\n+\t\tbreak;\n+\t}\n+\n+\tfree(error_msg);\n+\treturn error_return;\n+}\n+\n static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t\t    struct key_value_info *kvi, void *data,\n \t\t\t    const struct config_parse_options *opts)\n@@ -1057,8 +1133,6 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \tint comment = 0;\n \tsize_t baselen = 0;\n \tstruct strbuf *var = &cs->var;\n-\tint error_return = 0;\n-\tchar *error_msg = NULL;\n \n \t/* U+FEFF Byte Order Mark in UTF8 */\n \tconst char *bomptr = utf8_bom;\n@@ -1140,53 +1214,8 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n \t\t\tbreak;\n \t}\n \n-\tif (do_event(cs, CONFIG_EVENT_ERROR, &event_data) < 0)\n-\t\treturn -1;\n-\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      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      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      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       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       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      cs->linenr, cs->name);\n-\t}\n-\n-\tswitch (opts && opts->error_action ?\n-\t\topts->error_action :\n-\t\tcs->default_error_action) {\n-\tcase CONFIG_ERROR_DIE:\n-\t\tdie(\"%s\", error_msg);\n-\t\tbreak;\n-\tcase CONFIG_ERROR_ERROR:\n-\t\terror_return = error(\"%s\", error_msg);\n-\t\tbreak;\n-\tcase CONFIG_ERROR_SILENT:\n-\t\terror_return = -1;\n-\t\tbreak;\n-\tcase CONFIG_ERROR_UNSET:\n-\t\tBUG(\"config error action unset\");\n-\t}\n-\n-\tfree(error_msg);\n-\treturn error_return;\n+\tdo_event_and_flush(cs, CONFIG_EVENT_ERROR, &event_data);\n+\treturn -1;\n }\n \n static uintmax_t get_unit_factor(const char *end)\n@@ -2023,7 +2052,6 @@ static int do_config_from_file(config_fn_t fn,\n \ttop.origin_type = origin_type;\n \ttop.name = name;\n \ttop.path = path;\n-\ttop.default_error_action = CONFIG_ERROR_DIE;\n \ttop.do_fgetc = config_file_fgetc;\n \ttop.do_ungetc = config_file_ungetc;\n \ttop.do_ftell = config_file_ftell;\n@@ -2037,8 +2065,10 @@ static int do_config_from_file(config_fn_t fn,\n static int git_config_from_stdin(config_fn_t fn, void *data,\n \t\t\t\t enum config_scope scope)\n {\n+\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n+\n \treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n-\t\t\t\t   data, scope, NULL);\n+\t\t\t\t   data, scope, &config_opts);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2061,8 +2091,10 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n \n int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n {\n+\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n+\n \treturn git_config_from_file_with_options(fn, filename, data,\n-\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, NULL);\n+\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, &config_opts);\n }\n \n int git_config_from_mem(config_fn_t fn,\n@@ -2079,7 +2111,6 @@ int git_config_from_mem(config_fn_t fn,\n \ttop.origin_type = origin_type;\n \ttop.name = name;\n \ttop.path = NULL;\n-\ttop.default_error_action = CONFIG_ERROR_ERROR;\n \ttop.do_fgetc = config_buf_fgetc;\n \ttop.do_ungetc = config_buf_ungetc;\n \ttop.do_ftell = config_buf_ftell;\n@@ -2098,6 +2129,7 @@ int git_config_from_blob_oid(config_fn_t fn,\n \tchar *buf;\n \tunsigned long size;\n \tint ret;\n+\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);\n \n \tbuf = repo_read_object_file(repo, oid, &type, &size);\n \tif (!buf)\n@@ -2108,7 +2140,7 @@ int git_config_from_blob_oid(config_fn_t fn,\n \t}\n \n \tret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size,\n-\t\t\t\t  data, scope, NULL);\n+\t\t\t\t  data, scope, &config_opts);\n \tfree(buf);\n \n \treturn ret;\n@@ -2209,29 +2241,32 @@ static int do_git_config_sequence(const struct config_options *opts,\n \t\t\t   opts->system_gently ? ACCESS_EACCES_OK : 0))\n \t\tret += git_config_from_file_with_options(fn, system_config,\n \t\t\t\t\t\t\t data, CONFIG_SCOPE_SYSTEM,\n-\t\t\t\t\t\t\t NULL);\n+\t\t\t\t\t\t\t &opts->parse_options);\n \n \tgit_global_config(&user_config, &xdg_config);\n \n \tif (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK))\n \t\tret += git_config_from_file_with_options(fn, xdg_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL,\n+\t\t\t\t\t\t\t &opts->parse_options);\n \n \tif (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK))\n \t\tret += git_config_from_file_with_options(fn, user_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL, NULL);\n+\t\t\t\t\t\t\t CONFIG_SCOPE_GLOBAL,\n+\t\t\t\t\t\t\t &opts->parse_options);\n \n \tif (!opts->ignore_repo && repo_config &&\n \t    !access_or_die(repo_config, R_OK, 0))\n \t\tret += git_config_from_file_with_options(fn, repo_config, data,\n-\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL, NULL);\n+\t\t\t\t\t\t\t CONFIG_SCOPE_LOCAL,\n+\t\t\t\t\t\t\t &opts->parse_options);\n \n \tif (!opts->ignore_worktree && worktree_config &&\n \t    repo && repo->repository_format_worktree_config &&\n \t    !access_or_die(worktree_config, R_OK, 0)) {\n \t\t\tret += git_config_from_file_with_options(fn, worktree_config, data,\n \t\t\t\t\t\t\t\t CONFIG_SCOPE_WORKTREE,\n-\t\t\t\t\t\t\t\t NULL);\n+\t\t\t\t\t\t\t\t &opts->parse_options);\n \t}\n \n \tif (!opts->ignore_cmdline && git_config_from_parameters(fn, data) < 0)\n@@ -2272,7 +2307,7 @@ int config_with_options(config_fn_t fn, void *data,\n \t} else if (config_source && config_source->file) {\n \t\tret = git_config_from_file_with_options(fn, config_source->file,\n \t\t\t\t\t\t\tdata, config_source->scope,\n-\t\t\t\t\t\t\tNULL);\n+\t\t\t\t\t\t\t&opts->parse_options);\n \t} else if (config_source && config_source->blob) {\n \t\tret = git_config_from_blob_ref(fn, repo, config_source->blob,\n \t\t\t\t\t       data, config_source->scope);\n@@ -2310,9 +2345,11 @@ static void configset_iter(struct config_set *set, config_fn_t fn, void *data)\n \n void read_early_config(config_fn_t cb, void *data)\n {\n-\tstruct config_options opts = {0};\n \tstruct strbuf commondir = STRBUF_INIT;\n \tstruct strbuf gitdir = STRBUF_INIT;\n+\tstruct config_options opts = {\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t};\n \n \topts.respect_includes = 1;\n \n@@ -2344,13 +2381,14 @@ void read_early_config(config_fn_t cb, void *data)\n  */\n void read_very_early_config(config_fn_t cb, void *data)\n {\n-\tstruct config_options opts = { 0 };\n-\n-\topts.respect_includes = 1;\n-\topts.ignore_repo = 1;\n-\topts.ignore_worktree = 1;\n-\topts.ignore_cmdline = 1;\n-\topts.system_gently = 1;\n+\tstruct config_options opts = {\n+\t\t.respect_includes = 1,\n+\t\t.ignore_repo = 1,\n+\t\t.ignore_worktree = 1,\n+\t\t.ignore_cmdline = 1,\n+\t\t.system_gently = 1,\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t};\n \n \tconfig_with_options(cb, data, NULL, NULL, &opts);\n }\n@@ -2635,7 +2673,9 @@ int git_configset_get_pathname(struct config_set *set, const char *key, const ch\n /* Functions use to read configuration from a repository */\n static void repo_read_config(struct repository *repo)\n {\n-\tstruct config_options opts = { 0 };\n+\tstruct config_options opts = {\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t};\n \n \topts.respect_includes = 1;\n \topts.commondir = repo->commondir;\n@@ -2786,8 +2826,10 @@ static void read_protected_config(void)\n \t\t.ignore_repo = 1,\n \t\t.ignore_worktree = 1,\n \t\t.system_gently = 1,\n+\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n \t};\n \n+\n \tgit_configset_init(&protected_config);\n \tconfig_with_options(config_set_callback, &protected_config, NULL,\n \t\t\t    NULL, &opts);\n@@ -2998,6 +3040,7 @@ struct config_store_data {\n \t\tenum config_event_t type;\n \t\tint is_keys_section;\n \t} *parsed;\n+\tenum config_error_action error_action;\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@@ -3065,6 +3108,10 @@ static int store_aux_event(enum config_event_t type, size_t begin, size_t end,\n \t\t\tstore->seen[store->seen_nr] = store->parsed_nr;\n \t\t}\n \t}\n+\tif (type == CONFIG_EVENT_ERROR) {\n+\t\treturn git_config_err_fn(type, begin, end, cs,\n+\t\t\t\t\t &store->error_action);\n+\t}\n \n \tstore->parsed_nr++;\n \n@@ -3402,7 +3449,7 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \t\tstruct stat st;\n \t\tsize_t copy_begin, copy_end;\n \t\tint i, new_line = 0;\n-\t\tstruct config_parse_options opts;\n+\t\tstruct config_parse_options opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n \n \t\tif (!value_pattern)\n \t\t\tstore.value_pattern = NULL;\n@@ -3429,8 +3476,8 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,\n \n \t\tALLOC_GROW(store.parsed, 1, store.parsed_alloc);\n \t\tstore.parsed[0].end = 0;\n+\t\tstore.error_action = CONFIG_ERROR_DIE;\n \n-\t\tmemset(&opts, 0, sizeof(opts));\n \t\topts.event_fn = store_aux_event;\n \t\topts.event_fn_data = &store;\n \ndiff --git a/config.h b/config.h\nindex 2537516446..8ad399580f 100644\n--- a/config.h\n+++ b/config.h\n@@ -86,12 +86,6 @@ typedef int (*config_parser_event_fn_t)(enum config_event_t type,\n \t\t\t\t\tvoid *event_fn_data);\n \n struct config_parse_options {\n-\tenum config_error_action {\n-\t\tCONFIG_ERROR_UNSET = 0, /* use source-specific default */\n-\t\tCONFIG_ERROR_DIE, /* die() on error */\n-\t\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n-\t\tCONFIG_ERROR_SILENT, /* return -1 */\n-\t} error_action;\n \t/*\n \t * event_fn and event_fn_data are for internal use only. Handles events\n \t * emitted by the config parser.\n@@ -100,6 +94,11 @@ struct config_parse_options {\n \tvoid *event_fn_data;\n };\n \n+#define CP_OPTS_INIT(error_action) { \\\n+\t.event_fn = git_config_err_fn, \\\n+\t.event_fn_data = (enum config_error_action []){(error_action)}, \\\n+}\n+\n struct config_options {\n \tunsigned int respect_includes : 1;\n \tunsigned int ignore_repo : 1;\n@@ -119,6 +118,15 @@ struct config_options {\n \tunsigned int unconditional_remote_url : 1;\n };\n \n+enum config_error_action {\n+\tCONFIG_ERROR_DIE, /* die() on error */\n+\tCONFIG_ERROR_ERROR, /* error() on error, return -1 */\n+};\n+\n+int git_config_err_fn(enum config_event_t type, size_t begin_offset,\n+\t\t      size_t end_offset, struct config_source *cs,\n+\t\t      void *event_fn_data);\n+\n /* Config source metadata for a given config key-value pair */\n struct key_value_info {\n \tconst char *filename;\ndiff --git a/fsck.c b/fsck.c\nindex 522ee1c18a..bc0ca11421 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -1219,7 +1219,6 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\treturn 0;\n \n \tif (oidset_contains(&options->gitmodules_found, oid)) {\n-\t\tstruct config_parse_options config_opts = { 0 };\n \t\tstruct fsck_gitmodules_data data;\n \n \t\toidset_insert(&options->gitmodules_done, oid);\n@@ -1238,10 +1237,9 @@ static int fsck_blob(const struct object_id *oid, const char *buf,\n \t\tdata.oid = oid;\n \t\tdata.options = options;\n \t\tdata.ret = 0;\n-\t\tconfig_opts.error_action = CONFIG_ERROR_SILENT;\n \t\tif (git_config_from_mem(fsck_gitmodules_fn, CONFIG_ORIGIN_BLOB,\n \t\t\t\t\t\".gitmodules\", buf, size, &data,\n-\t\t\t\t\tCONFIG_SCOPE_UNKNOWN, &config_opts))\n+\t\t\t\t\tCONFIG_SCOPE_UNKNOWN, NULL))\n \t\t\tdata.ret |= report(options, oid, OBJ_BLOB,\n \t\t\t\t\tFSCK_MSG_GITMODULES_PARSE,\n \t\t\t\t\t\"could not parse gitmodules blob\");\ndiff --git a/submodule-config.c b/submodule-config.c\nindex b6908e295f..d97135c917 100644\n--- a/submodule-config.c\n+++ b/submodule-config.c\n@@ -565,6 +565,8 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \tenum object_type type;\n \tconst struct submodule *submodule = NULL;\n \tstruct parse_config_parameter parameter;\n+\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);\n+\n \n \t/*\n \t * If any parameter except the cache is a NULL pointer just\n@@ -608,7 +610,8 @@ static const struct submodule *config_from(struct submodule_cache *cache,\n \tparameter.gitmodules_oid = &oid;\n \tparameter.overwrite = 0;\n \tgit_config_from_mem(parse_config, CONFIG_ORIGIN_SUBMODULE_BLOB, rev.buf,\n-\t\t\t    config, config_size, &parameter, CONFIG_SCOPE_UNKNOWN, NULL);\n+\t\t\t    config, config_size, &parameter,\n+\t\t\t    CONFIG_SCOPE_UNKNOWN, &config_opts);\n \tstrbuf_release(&rev);\n \tfree(config);\n \n@@ -652,7 +655,9 @@ static void config_from_gitmodules(config_fn_t fn, struct repository *repo, void\n \t\tstruct git_config_source config_source = {\n \t\t\t0, .scope = CONFIG_SCOPE_SUBMODULE\n \t\t};\n-\t\tconst struct config_options opts = { 0 };\n+\t\tstruct config_options opts = {\n+\t\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n+\t\t};\n \t\tstruct object_id oid;\n \t\tchar *file;\n \t\tchar *oidstr = NULL;\n-- \n2.42.0.515.g380fc7ccd1-goog\n\n"},{"id":"482116","messageId":"49d4b649919e5661daa2113c2f5d674c5cd8dd0e.1695330852.git.steadmon@google.com","threadId":"60017","inReplyTo":"cover.1695330852.git.steadmon@google.com","subject":"[PATCH v3 4/5] config.c: accept config_parse_options in git_config_from_stdin","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-09-21T21:17:23Z","receivedAt":"2023-09-21T21:58:45Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"From: Glen Choo <chooglen@google.com>\n\nA later commit will move git_config_from_stdin() to a library, so it\nwill need to accept event listeners.\n\nSigned-off-by: Glen Choo <chooglen@google.com>\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n config.c | 10 +++++-----\n 1 file changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 0c4f1a2874..50188f469a 100644\n--- a/config.c\n+++ b/config.c\n@@ -2063,12 +2063,11 @@ static int do_config_from_file(config_fn_t fn,\n }\n \n static int git_config_from_stdin(config_fn_t fn, void *data,\n-\t\t\t\t enum config_scope scope)\n+\t\t\t\t enum config_scope scope,\n+\t\t\t\t const struct config_parse_options *config_opts)\n {\n-\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n-\n \treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n-\t\t\t\t   data, scope, &config_opts);\n+\t\t\t\t   data, scope, config_opts);\n }\n \n int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n@@ -2303,7 +2302,8 @@ int config_with_options(config_fn_t fn, void *data,\n \t * regular lookup sequence.\n \t */\n \tif (config_source && config_source->use_stdin) {\n-\t\tret = git_config_from_stdin(fn, data, config_source->scope);\n+\t\tret = git_config_from_stdin(fn, data, config_source->scope,\n+\t\t\t\t\t    &opts->parse_options);\n \t} else if (config_source && config_source->file) {\n \t\tret = git_config_from_file_with_options(fn, config_source->file,\n \t\t\t\t\t\t\tdata, config_source->scope,\n-- \n2.42.0.515.g380fc7ccd1-goog\n\n"},{"id":"482118","messageId":"ZQyxmq7HB12/+YYv@google.com","threadId":"60017","inReplyTo":"xmqqttspck4a.fsf@gitster.g","subject":"Re: [PATCH v2 2/4] config: report config parse errors using cb","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-09-21T21:11:54Z","receivedAt":"2023-09-21T22:06:09Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2023.08.23 18:19, Junio C Hamano wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> \n> > From: Glen Choo <chooglen@google.com>\n> >\n> > In a subsequent commit, config parsing will become its own library, and\n> > it's likely that the caller will want flexibility in handling errors\n> > (instead of being limited to the error handling we have in-tree).\n> \n> And the in-tree error handling is abstracted out as the\n> git_config_err_fn() function; in other words, we become the first\n> client of the library interface, which makes sense.\n> \n> > @@ -1035,8 +1088,6 @@ static int git_parse_source(struct config_source *cs, config_fn_t fn,\n> >  \tint comment = 0;\n> >  \tsize_t baselen = 0;\n> >  \tstruct strbuf *var = &cs->var;\n> > ...\n> > +\t/*\n> > +\t * FIXME for whatever reason, do_event passes the _previous_ event, so\n> > +\t * in order for our callback to receive the error event, we have to call\n> > +\t * do_event twice\n> > +\t */\n> > +\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n> > +\tdo_event(cs, CONFIG_EVENT_ERROR, &event_data);\n> > +\treturn -1;\n> >  }\n> \n> This indeed is very curious and needs to be looked into before we\n> proceed further.  How does the current control flow cope with the\n> behaviour?\n\nAs Jonathan Tan mentioned in [1], on calling do_event() we set the start\noffset of the new event, and execute the callback for the previous event\nwhose end offset we now know.\n\nI refactored this into \"start_event()\" and \"flush_event()\" functions as\nsuggested, and added a new \"do_event_and_flush()\" function for the case\nwhere we want to immediately execute a callback for an event.\n\n[1]: https://lore.kernel.org/git/20230804213457.1174493-1-jonathantanmy@google.com/\n\n> > @@ -2322,7 +2342,9 @@ void read_early_config(config_fn_t cb, void *data)\n> >   */\n> >  void read_very_early_config(config_fn_t cb, void *data)\n> >  {\n> > -\tstruct config_options opts = { 0 };\n> > +\tstruct config_options opts = {\n> > +\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n> > +\t};\n> >  \n> >  \topts.respect_includes = 1;\n> >  \topts.ignore_repo = 1;\n> \n> This uses a bit more assignments to various members of opts. to\n> initialize it, which could have been done with designated\n> initializer, like the one in read_protected_config() used to do.\n> \n> > @@ -2760,12 +2784,14 @@ int repo_config_get_pathname(struct repository *repo,\n> >  static void read_protected_config(void)\n> >  {\n> >  \tstruct config_options opts = {\n> > -\t\t.respect_includes = 1,\n> > -\t\t.ignore_repo = 1,\n> > -\t\t.ignore_worktree = 1,\n> > -\t\t.system_gently = 1,\n> > +\t\t.parse_options = CP_OPTS_INIT(CONFIG_ERROR_DIE),\n> >  \t};\n> >  \n> > +\topts.respect_includes = 1;\n> > +\topts.ignore_repo = 1;\n> > +\topts.ignore_worktree = 1;\n> > +\topts.system_gently = 1;\n> > +\n> \n> It is curious why you want to switch to manual assignment, instead\n> of keeping the designated initializer for this one.  I would have\n> expected the initialization in read_very_early_config() to start\n> using designated initializer to be consistent, instead.\n> \n> Thanks.\n\nAgreed, fixed here and above.\n"},{"id":"482122","messageId":"xmqqh6nn3xq3.fsf@gitster.g","threadId":"60017","inReplyTo":"ZQyxmq7HB12/+YYv@google.com","subject":"Re: [PATCH v2 2/4] config: report config parse errors using cb","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-09-21T23:36:36Z","receivedAt":"2023-09-21T23:36:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> As Jonathan Tan mentioned in [1], on calling do_event() we set the start\n> offset of the new event, and execute the callback for the previous event\n> whose end offset we now know.\n>\n> I refactored this into \"start_event()\" and \"flush_event()\" functions as\n> suggested, and added a new \"do_event_and_flush()\" function for the case\n> where we want to immediately execute a callback for an event.\n\nVery nicely done.  Thanks, both.\n"},{"id":"483355","messageId":"xmqq34y9jho2.fsf@gitster.g","threadId":"60017","inReplyTo":"cover.1695330852.git.steadmon@google.com","subject":"Re: [PATCH v3 0/5] config-parse: create config parsing library","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-10-17T17:13:49Z","receivedAt":"2023-10-17T17:13:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> Config parsing no longer uses global state as of gc/config-context, so the\n> natural next step for libification is to turn that into its own library.\n> This series starts that process by moving config parsing into\n> config-parse.[c|h] so that other programs can include this functionality\n> without pulling in all of config.[c|h].\n\nThis has been in list archive collecting dust.  It is unfortunate\nthat not many people appear to be interested in reviewing others'\npatches?\n\n> Open questions:\n> - How do folks feel about the do_event() refactor in patches 2 & 3?\n\nI gave a quick re-read and found that the code after patch 2 made it\neasier to see how config.c::do_event() does its thing (even though\nthe patch text of that exact step was somehow a bit hard to follow).\n\nHowever, the helper added by patch 3, do_event_and_flush(), that\nduplicates exactly what do_event() does, is hard to reason about, at\nleast for me.  It returns early without setting .previous_type to\nEOF and the value returned from the helper signals if that is the\ncase (the two early return points both return what flush_event()\ngave us), but the only caller of the helper does not even inspect\nthe return value, unlike all the callers of do_event(), which also\nlooks a bit fishy.\n\nThanks.\n\n"},{"id":"483692","messageId":"20231023175217.984090-1-jonathantanmy@google.com","threadId":"60017","inReplyTo":"fa55b7836f112cb7c7ab9b80e745b9969421c768.1695330852.git.steadmon@google.com","subject":"Re: [PATCH v3 1/5] config: split out config_parse_options","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-10-23T17:52:17Z","receivedAt":"2023-10-23T17:52:23Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Josh Steadmon <steadmon@google.com> writes:\n> From: Glen Choo <chooglen@google.com>\n> \n> \"struct config_options\" is a disjoint set of options used by the config\n> parser (e.g. event listeners) and options used by config_with_options()\n> (e.g. to handle includes, choose which config files to parse).\n\nCan this sentence be reworded? In particular, \"disjoint\" is a word\nnormally applied to two or more sets (meaning that they have no elements\nin common), but here it is used for only one.\n\nEverything else looks good, and the reasoning (some functions only use\na subset of the fields, and this subset is easily explained conceptually\nas those related to parsing) makes sense.\n \n"},{"id":"483694","messageId":"20231023180555.986240-1-jonathantanmy@google.com","threadId":"60017","inReplyTo":"8a1463c223497fca2fd3f11a54db5d7e52d1d08a.1695330852.git.steadmon@google.com","subject":"Re: [PATCH v3 2/5] config: split do_event() into start and flush operations","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-10-23T18:05:55Z","receivedAt":"2023-10-23T18:05:59Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Josh Steadmon <steadmon@google.com> writes:\n> +static void start_event(struct config_source *cs, enum config_event_t type,\n> +\t\t       struct parse_event_data *data)\n> +{\n> +\tdata->previous_type = type;\n> +\tdata->previous_offset = get_corrected_offset(cs, type);\n> +}\n\nIt's a pity that get_corrected_offset() has to be called twice (once\nhere and once below) but I think that's the best we can do given how the\ncode is laid out (and I can't think of a better code layout either).\n\n> +static int flush_event(struct config_source *cs, enum config_event_t type,\n> +\t\t       struct parse_event_data *data)\n\nOne thing confusing here is that the \"type\" is not what's being flushed,\nbut used to change details about how we flush. Technically all we need\nis is_whitespace_type and is_eof_type, but that's clumsier to code. I\nthink the best we can do is add some documentation to this function,\nmaybe 'Flush the event started by a prior start_event(), if one exists.\nThe type of the event being flushed is not \"type\" but the type that was\npassed to the prior start_event(); \"type\" here may merely change how the\nflush is performed' or something like that.\n\n> +{\n> +\tif (!data->opts || !data->opts->event_fn)\n> +\t\treturn 0;\n> +\n> +\tif (type == CONFIG_EVENT_WHITESPACE &&\n> +\t    data->previous_type == type)\n> +\t\treturn 0;\n>  \n>  \tif (data->previous_type != CONFIG_EVENT_EOF &&\n>  \t    data->opts->event_fn(data->previous_type, data->previous_offset,\n> -\t\t\t\t offset, cs, data->opts->event_fn_data) < 0)\n> +\t\t\t\t get_corrected_offset(cs, type), cs,\n> +\t\t\t\t data->opts->event_fn_data) < 0)\n>  \t\treturn -1;\n\nAnother confusing point here is how EOF is used both to mean\n\"start_event() was never called\" and a true EOF. I think for now it's\nbest to just document this where we define CONFIG_EVENT_EOF.\n"},{"id":"483698","messageId":"20231023184137.994212-1-jonathantanmy@google.com","threadId":"60017","inReplyTo":"a888045c04d27864edf5751ea8641fdba596779c.1695330852.git.steadmon@google.com","subject":"Re: [PATCH v3 3/5] config: report config parse errors using cb","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-10-23T18:41:37Z","receivedAt":"2023-10-23T18:41:41Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Josh Steadmon <steadmon@google.com> writes:\n> From: Glen Choo <chooglen@google.com>\n> \n> In a subsequent commit, config parsing will become its own library, and\n> it's likely that the caller will want flexibility in handling errors\n> (instead of being limited to the error handling we have in-tree).\n> \n> Move the Git-specific error handling into a config_parser_event_fn_t\n> that responds to config errors, and make git_parse_source() always\n> return -1 (careful inspection shows that it was always returning -1\n> already). This makes CONFIG_ERROR_SILENT obsolete since that is\n> equivalent to not specifying an error event listener. Also, remove\n> CONFIG_ERROR_UNSET and the config_source 'default', since all callers\n> are now expected to specify the error handling they want.\n\nI think this has to be better explained. So:\n\n- There is already a config_parser_event_fn_t that can be configured\nby a user to receive emitted config events. This callback can return\nnegative to halt further config parsing.\n\n- Currently, it is git_parse_source() that detects when an error\noccurs, and it emits a CONFIG_EVENT_ERROR and either dies, prints\nan error, or swallows the error depending on error_action; no\nmatter what error_action is, it halts config parsing, as one would\nexpect. This commit moves the die/print/swallow handling to a\nconfig_parser_event_fn_t that will see the CONFIG_EVENT_ERROR and die/\nprint/swallow.\n\n- This new config_parser_event_fn_t does not need to swallow, since\nthat's the same as not passing in a callback. So it just needs to die/\nprint.\n\n> @@ -1039,6 +1042,29 @@ static int do_event(struct config_source *cs, enum config_event_t type,\n>  \treturn 0;\n>  }\n>  \n> +static int do_event_and_flush(struct config_source *cs,\n> +\t\t\t      enum config_event_t type,\n> +\t\t\t      struct parse_event_data *data)\n> +{\n> +\tint maybe_ret;\n> +\n> +\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n> +\t\treturn maybe_ret;\n> +\n> +\tstart_event(cs, type, data);\n> +\n> +\tif ((maybe_ret = flush_event(cs, type, data)) < 1)\n> +\t\treturn maybe_ret;\n> +\n> +\t/*\n> +\t * Not actually EOF, but this indicates we don't have a valid event\n> +\t * to flush next time around.\n> +\t */\n> +\tdata->previous_type = CONFIG_EVENT_EOF;\n> +\n> +\treturn 0;\n> +}\n\nA lot of this function only makes sense if the type is ERROR, so maybe\nrename this as flush_and_emit_error() (and don't take in a type). As\nit is, right now there is some confusion about how you can flush (I'm\nreferring to the second flush) with the same type as what you passed\nto start_event().\n\nAlso, I don't think we should set data->previous_type here. Instead\nthere should be a comment saying that if you're emitting ERROR, you\nshould halt config parsing. The return value here is useless too (it\nsignals whether we should halt config parsing, but the caller should\nalways halt, so we don't need to return anything).\n\n"},{"id":"483699","messageId":"ZTa/fvfuPN+JJx89@nand.local","threadId":"60017","inReplyTo":"20231023175217.984090-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 1/5] config: split out config_parse_options","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2023-10-23T18:46:22Z","receivedAt":"2023-10-23T18:46:26Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Mon, Oct 23, 2023 at 10:52:17AM -0700, Jonathan Tan wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> > From: Glen Choo <chooglen@google.com>\n> >\n> > \"struct config_options\" is a disjoint set of options used by the config\n> > parser (e.g. event listeners) and options used by config_with_options()\n> > (e.g. to handle includes, choose which config files to parse).\n>\n> Can this sentence be reworded? In particular, \"disjoint\" is a word\n> normally applied to two or more sets (meaning that they have no elements\n> in common), but here it is used for only one.\n\nThe pedant in me agrees with you. I do think that the sentence reads a\nlittle awkwardly. Perhaps instead:\n\n    \"struct config_options\" has members which serve two distinct\n    purposes. There are a set of members used by the configuration parse\n    (e.g. event listeners). There is also a set used by\n    config_with_options() (e.g to handle includes, choose which config\n    files to parse).\n\n> Everything else looks good, and the reasoning (some functions only use\n> a subset of the fields, and this subset is easily explained conceptually\n> as those related to parsing) makes sense.\n\nYup.\n\nThanks,\nTaylor\n"},{"id":"483702","messageId":"20231023185208.996434-1-jonathantanmy@google.com","threadId":"60017","inReplyTo":"49d4b649919e5661daa2113c2f5d674c5cd8dd0e.1695330852.git.steadmon@google.com","subject":"Re: [PATCH v3 4/5] config.c: accept config_parse_options in git_config_from_stdin","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-10-23T18:52:08Z","receivedAt":"2023-10-23T18:52:13Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Josh Steadmon <steadmon@google.com> writes:\n> diff --git a/config.c b/config.c\n> index 0c4f1a2874..50188f469a 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -2063,12 +2063,11 @@ static int do_config_from_file(config_fn_t fn,\n>  }\n>  \n>  static int git_config_from_stdin(config_fn_t fn, void *data,\n> -\t\t\t\t enum config_scope scope)\n> +\t\t\t\t enum config_scope scope,\n> +\t\t\t\t const struct config_parse_options *config_opts)\n>  {\n> -\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n> -\n>  \treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n> -\t\t\t\t   data, scope, &config_opts);\n> +\t\t\t\t   data, scope, config_opts);\n>  }\n>  \n>  int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n> @@ -2303,7 +2302,8 @@ int config_with_options(config_fn_t fn, void *data,\n>  \t * regular lookup sequence.\n>  \t */\n>  \tif (config_source && config_source->use_stdin) {\n> -\t\tret = git_config_from_stdin(fn, data, config_source->scope);\n> +\t\tret = git_config_from_stdin(fn, data, config_source->scope,\n> +\t\t\t\t\t    &opts->parse_options);\n>  \t} else if (config_source && config_source->file) {\n>  \t\tret = git_config_from_file_with_options(fn, config_source->file,\n>  \t\t\t\t\t\t\tdata, config_source->scope,\n\nDoes this change the behavior of stdin config parsing from \"die\" to\n\"silent\" (since there is no event emitting callback)? The only user of\nstdin parsing seems to be builtin/config.c, so maybe a corresponding\nchange needs to be made there.\n\n"},{"id":"483703","messageId":"20231023185307.996813-1-jonathantanmy@google.com","threadId":"60017","inReplyTo":"e59ca992d0566f793a07d78f3e65219b51239492.1695330852.git.steadmon@google.com","subject":"Re: [PATCH v3 5/5] config-parse: split library out of config.[c|h]","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-10-23T18:53:07Z","receivedAt":"2023-10-23T18:53:11Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Josh Steadmon <steadmon@google.com> writes:\n> From: Glen Choo <chooglen@google.com>\n> \n> The config parsing machinery (besides \"include\" directives) is usable by\n> programs other than Git - it works with any file written in Git config\n> syntax (IOW it doesn't rely on 'core' Git features like a repository),\n> and as of the series ending at 6e8e7981eb (config: pass source to\n> config_parser_event_fn_t, 2023-06-28), it no longer relies on global\n> state. Thus, we can and should start turning it into a library other\n> programs can use.\n\nChecking this with --color-moved looks good, but we'll need to take\nanother look once my comments about the earlier patches have been\naddressed.\n \n"},{"id":"483713","messageId":"ZTbJqzWDyqkhc6L9@nand.local","threadId":"60017","inReplyTo":"a888045c04d27864edf5751ea8641fdba596779c.1695330852.git.steadmon@google.com","subject":"Re: [PATCH v3 3/5] config: report config parse errors using cb","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2023-10-23T19:29:47Z","receivedAt":"2023-10-23T19:29:50Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Thu, Sep 21, 2023 at 02:17:22PM -0700, Josh Steadmon wrote:\n> diff --git a/bundle-uri.c b/bundle-uri.c\n> index f93ca6a486..856bffdcad 100644\n> --- a/bundle-uri.c\n> +++ b/bundle-uri.c\n> @@ -237,9 +237,7 @@ int bundle_uri_parse_config_format(const char *uri,\n>  \t\t\t\t   struct bundle_list *list)\n>  {\n>  \tint result;\n> -\tstruct config_parse_options opts = {\n> -\t\t.error_action = CONFIG_ERROR_ERROR,\n> -\t};\n> +\tstruct config_parse_options opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);\n\nI'm nit-picking, but I find this parameterized initializer macro to be a\nlittle unusual w.r.t our usual conventions.\n\nIn terms of \"usual conventions,\" I'm thinking about STRING_LIST_INIT_DUP\nversus STRING_LIST_INIT_NODUP (as opposed to something like\nSTRING_LIST_INIT(DUP) or STRING_LIST_INIT(NODUP)).\n\nSince there are only two possible values (the ones corresponding to\nerror() and die()) I wonder if something like CP_OPTS_INIT_ERROR and\nCP_OPTS_INIT_DIE might be more appropriate. If you don't like either of\nthose, I'd suggest making the initializer a function instead of a\nparameterized macro.\n\n>  \tif (!list->baseURI) {\n>  \t\tstruct strbuf baseURI = STRBUF_INIT;\n> diff --git a/config.c b/config.c\n> index ff138500a2..0c4f1a2874 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -55,7 +55,6 @@ struct config_source {\n>  \tenum config_origin_type origin_type;\n>  \tconst char *name;\n>  \tconst char *path;\n> -\tenum config_error_action default_error_action;\n>  \tint linenr;\n>  \tint eof;\n>  \tsize_t total_len;\n> @@ -185,13 +184,15 @@ static int handle_path_include(const struct key_value_info *kvi,\n>  \t}\n>\n>  \tif (!access_or_die(path, R_OK, 0)) {\n> +\t\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n> +\n>  \t\tif (++inc->depth > MAX_INCLUDE_DEPTH)\n>  \t\t\tdie(_(include_depth_advice), MAX_INCLUDE_DEPTH, path,\n>  \t\t\t    !kvi ? \"<unknown>\" :\n>  \t\t\t    kvi->filename ? kvi->filename :\n>  \t\t\t    \"the command line\");\n>  \t\tret = git_config_from_file_with_options(git_config_include, path, inc,\n> -\t\t\t\t\t\t\tkvi->scope, NULL);\n> +\t\t\t\t\t\t\tkvi->scope, &config_opts);\n\n...OK, so using the CONFIG_ERROR_DIE variant seems like the right choice\nhere because git_config_from_file_with_options() calls\ndo_config_from_file() which sets its default_error_action as\nCONFIG_ERROR_DIE.\n\n>  static uintmax_t get_unit_factor(const char *end)\n> @@ -2023,7 +2052,6 @@ static int do_config_from_file(config_fn_t fn,\n>  \ttop.origin_type = origin_type;\n>  \ttop.name = name;\n>  \ttop.path = path;\n> -\ttop.default_error_action = CONFIG_ERROR_DIE;\n>  \ttop.do_fgetc = config_file_fgetc;\n>  \ttop.do_ungetc = config_file_ungetc;\n>  \ttop.do_ftell = config_file_ftell;\n> @@ -2037,8 +2065,10 @@ static int do_config_from_file(config_fn_t fn,\n>  static int git_config_from_stdin(config_fn_t fn, void *data,\n>  \t\t\t\t enum config_scope scope)\n>  {\n> +\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n> +\n>  \treturn do_config_from_file(fn, CONFIG_ORIGIN_STDIN, \"\", NULL, stdin,\n> -\t\t\t\t   data, scope, NULL);\n> +\t\t\t\t   data, scope, &config_opts);\n\nSame here.\n\n>  int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n> @@ -2061,8 +2091,10 @@ int git_config_from_file_with_options(config_fn_t fn, const char *filename,\n>\n>  int git_config_from_file(config_fn_t fn, const char *filename, void *data)\n>  {\n> +\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_DIE);\n> +\n>  \treturn git_config_from_file_with_options(fn, filename, data,\n> -\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, NULL);\n> +\t\t\t\t\t\t CONFIG_SCOPE_UNKNOWN, &config_opts);\n>  }\n\nAnd here.\n\n> @@ -2098,6 +2129,7 @@ int git_config_from_blob_oid(config_fn_t fn,\n>  \tchar *buf;\n>  \tunsigned long size;\n>  \tint ret;\n> +\tstruct config_parse_options config_opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);\n>\n>  \tbuf = repo_read_object_file(repo, oid, &type, &size);\n>  \tif (!buf)\n> @@ -2108,7 +2140,7 @@ int git_config_from_blob_oid(config_fn_t fn,\n>  \t}\n>\n>  \tret = git_config_from_mem(fn, CONFIG_ORIGIN_BLOB, name, buf, size,\n> -\t\t\t\t  data, scope, NULL);\n> +\t\t\t\t  data, scope, &config_opts);\n>  \tfree(buf);\n\nThis one uses git_config_from_mem(), which sets the default error action\nto \"CONFIG_ERROR_ERROR\", so this transformation looks correct.\n\nThanks,\nTaylor\n"},{"id":"483714","messageId":"ZTbK3QTJYXxYj/M6@nand.local","threadId":"60017","inReplyTo":"xmqq34y9jho2.fsf@gitster.g","subject":"Re: [PATCH v3 0/5] config-parse: create config parsing library","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2023-10-23T19:34:53Z","receivedAt":"2023-10-23T19:34:56Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Oct 17, 2023 at 10:13:49AM -0700, Junio C Hamano wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n>\n> > Open questions:\n> > - How do folks feel about the do_event() refactor in patches 2 & 3?\n>\n> I gave a quick re-read and found that the code after patch 2 made it\n> easier to see how config.c::do_event() does its thing (even though\n> the patch text of that exact step was somehow a bit hard to follow).\n>\n> However, the helper added by patch 3, do_event_and_flush(), that\n> duplicates exactly what do_event() does, is hard to reason about, at\n> least for me.  It returns early without setting .previous_type to\n> EOF and the value returned from the helper signals if that is the\n> case (the two early return points both return what flush_event()\n> gave us), but the only caller of the helper does not even inspect\n> the return value, unlike all the callers of do_event(), which also\n> looks a bit fishy.\n\nI had similar thoughts while reviewing.\n\nBut I am not sure that I agree that this series is moving us in the\nright direction necessarily. Or at least I am not convinced that\nshipping the intermediate state is worth doing before we have callers\nthat could drop '#include \"config.h\"' for just the parser.\n\nThis feels like churn that does not yield a tangible pay-off, at least\nin the sense that the refactoring and code movement delivers us\nsomething that we can substantively use today.\n\nI dunno.\n\nThanks,\nTaylor\n"},{"id":"483717","messageId":"xmqqv8axm73r.fsf@gitster.g","threadId":"60017","inReplyTo":"ZTbJqzWDyqkhc6L9@nand.local","subject":"Re: [PATCH v3 3/5] config: report config parse errors using cb","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-10-23T20:11:52Z","receivedAt":"2023-10-23T20:11:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Taylor Blau <me@ttaylorr.com> writes:\n\n>> +\tstruct config_parse_options opts = CP_OPTS_INIT(CONFIG_ERROR_ERROR);\n>\n> I'm nit-picking, but I find this parameterized initializer macro to be a\n> little unusual w.r.t our usual conventions.\n>\n> In terms of \"usual conventions,\" I'm thinking about STRING_LIST_INIT_DUP\n> versus STRING_LIST_INIT_NODUP (as opposed to something like\n> STRING_LIST_INIT(DUP) or STRING_LIST_INIT(NODUP)).\n\nFWIW, I have always felt that the way STRING_LIST_INIT* was done was\nquite ugly.  The new pattern does look superiour, as long as (1) it\ndoes not involve voodoo like token pasting, and (2) the parameters\npassed does not grow.  The latter is especially important as there\nis no equivalent to designated initializers in C preprocessor macros.\n\nThanks.\n"},{"id":"483718","messageId":"xmqqr0llm71g.fsf@gitster.g","threadId":"60017","inReplyTo":"ZTbK3QTJYXxYj/M6@nand.local","subject":"Re: [PATCH v3 0/5] config-parse: create config parsing library","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-10-23T20:13:15Z","receivedAt":"2023-10-23T20:13:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Taylor Blau <me@ttaylorr.com> writes:\n\n> This feels like churn that does not yield a tangible pay-off, at least\n> in the sense that the refactoring and code movement delivers us\n> something that we can substantively use today.\n>\n> I dunno.\n\nThat matches something I felt but was too polite to say aloud ;-)\n"},{"id":"483827","messageId":"20231024225005.1191555-1-jonathantanmy@google.com","threadId":"60017","inReplyTo":"ZTbK3QTJYXxYj/M6@nand.local","subject":"Re: [PATCH v3 0/5] config-parse: create config parsing library","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2023-10-24T22:50:05Z","receivedAt":"2023-10-24T22:50:09Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Taylor Blau <me@ttaylorr.com> writes:\n> But I am not sure that I agree that this series is moving us in the\n> right direction necessarily. Or at least I am not convinced that\n> shipping the intermediate state is worth doing before we have callers\n> that could drop '#include \"config.h\"' for just the parser.\n> \n> This feels like churn that does not yield a tangible pay-off, at least\n> in the sense that the refactoring and code movement delivers us\n> something that we can substantively use today.\n> \n> I dunno.\n> \n> Thanks,\n> Taylor\n\nThanks for calling this out. We do want our changes to be good for both\nthe libification and the non-libification cases as much as possible. As\nit is, I do agree that since we won't have callers that can use the new\nparser header (I think the likeliest cause of having such a caller is\nif we have a \"interpret-config\" command, like \"interpret-trailers\"), we\nprobably shouldn't merge this (at least, the last 2 patches).\n\nI think patches 1-3 are still usable (they make some internals of config\nparsing less confusing) but I'm also OK if we hold off on them until\nwe find a compelling use case that motivates refactoring on the config\nparser.\n"},{"id":"483867","messageId":"ZTluW0Ha2W4NYaJd@google.com","threadId":"60017","inReplyTo":"20231024225005.1191555-1-jonathantanmy@google.com","subject":"Re: [PATCH v3 0/5] config-parse: create config parsing library","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2023-10-25T19:37:51Z","receivedAt":"2023-10-25T19:38:00Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2023.10.24 15:50, Jonathan Tan wrote:\n> Taylor Blau <me@ttaylorr.com> writes:\n> > But I am not sure that I agree that this series is moving us in the\n> > right direction necessarily. Or at least I am not convinced that\n> > shipping the intermediate state is worth doing before we have callers\n> > that could drop '#include \"config.h\"' for just the parser.\n> > \n> > This feels like churn that does not yield a tangible pay-off, at least\n> > in the sense that the refactoring and code movement delivers us\n> > something that we can substantively use today.\n> > \n> > I dunno.\n> > \n> > Thanks,\n> > Taylor\n> \n> Thanks for calling this out. We do want our changes to be good for both\n> the libification and the non-libification cases as much as possible. As\n> it is, I do agree that since we won't have callers that can use the new\n> parser header (I think the likeliest cause of having such a caller is\n> if we have a \"interpret-config\" command, like \"interpret-trailers\"), we\n> probably shouldn't merge this (at least, the last 2 patches).\n> \n> I think patches 1-3 are still usable (they make some internals of config\n> parsing less confusing) but I'm also OK if we hold off on them until\n> we find a compelling use case that motivates refactoring on the config\n> parser.\n\nThanks everyone for the revived discussion here. I think I agree, this\nseries is not going in the right direction. Additionally, our internal\nuse case for this change has evaporated, so let's just drop the series.\nWe can pick it up again later if interest returns.\n"},{"id":"483982","messageId":"xmqqjzr8p67g.fsf@gitster.g","threadId":"60017","inReplyTo":"ZTluW0Ha2W4NYaJd@google.com","subject":"Re: [PATCH v3 0/5] config-parse: create config parsing library","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2023-10-27T13:04:19Z","receivedAt":"2023-10-27T13:04:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> Thanks everyone for the revived discussion here. I think I agree, this\n> series is not going in the right direction. Additionally, our internal\n> use case for this change has evaporated, so let's just drop the series.\n> We can pick it up again later if interest returns.\n\nOK.  Let's scrap it for now.\n\nThe \"internal use case\" behind a proposed feature changing so\nquickly is a bit worrying.  What is good for this project should\nideally be good for everybody, not only for satisfying a particular\n$CORP needs of the day.  But I think the idea of giving enhanced\nvisibility into stakeholder companies directions and priorities\nEmily (I think?)  floated during the contributors' summit may help\nreduce such a risk, hopefully.\n\nThanks.\n"}]}