{"thread":{"id":"54704","subject":"[PATCH v2 0/2] config: allow specifying config entries via envvar pairs","startedAt":"2020-11-24T10:51:06Z","lastAt":"2021-05-19T11:39:32Z","messageCount":116,"participants":["Patrick Steinhardt","Junio C Hamano","Ævar Arnfjörð Bjarmason","Jeff King","brian m. carlson","Phillip Wood","Simon Ruderich"],"isPatch":true,"patchVersion":2,"patchTotal":2},"messages":[{"id":"410654","messageId":"cover.1606214397.git.ps@pks.im","threadId":"54704","inReplyTo":null,"subject":"[PATCH v2 0/2] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-11-24T10:50:46Z","receivedAt":"2020-11-24T10:51:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the second version of my patch series which aims to implement a\nway to pass config entries via the environment while avoiding any\nrequirements to perform shell quoting on the user's side.\n\nThere's been quite some feedback on the first version, which I tried to\ninclude in this version. Changes include:\n\n    - I reworked how git detects which variables it should process.\n      Instead of iterating from GIT_CONFIG_KEY_0 to $n until we find the\n      first gap, this now uses a third environment variable\n      GIT_CONFIG_COUNT which specifies show many environment config\n      pairs should be processed. I've added this variable to the local\n      environment variables, so that it's properly unset when moving\n      between repos and printed by `git rev-parse --local-env-vars`.\n\n    - Missing GIT_CONFIG_VALUE_$n keys for a given key are now treated\n      as an error. The same is true for any environment value which\n      should exist based on the value of GIT_CONFIG_COUNT.\n\n    - I've changed priorities. The envvars are treated as command-level\n      and as such override all values configured in files. But any\n      explicit `git -c key=value` will now override these envvars.\n\n    - I've improved test coverage to also nail down priorities.\n\nPatrick\n\nPatrick Steinhardt (2):\n  config: extract function to parse config pairs\n  config: allow specifying config entries via envvar pairs\n\n Documentation/git-config.txt |   9 +++\n cache.h                      |   1 +\n config.c                     |  96 +++++++++++++++++++++++++-------\n environment.c                |   1 +\n t/t1300-config.sh            | 105 ++++++++++++++++++++++++++++++++++-\n 5 files changed, 190 insertions(+), 22 deletions(-)\n\n-- \n2.29.2\n\n"},{"id":"410655","messageId":"fa54f13a917cf2843fb7902f79d38cf55699fa07.1606214397.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1606214397.git.ps@pks.im","subject":"[PATCH v2 1/2] config: extract function to parse config pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-11-24T10:50:50Z","receivedAt":"2020-11-24T10:51:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `git_config_parse_parameter` is responsible for parsing a\n`foo.bar=baz`-formatted configuration key, sanitizing the key and then\nprocessing it via the given callback function. Given that we're about to\nadd a second user which is going to process keys in such which already\nhas keys and values separated, this commit extracts a function\n`config_parse_pair` which only does the sanitization and processing\npart.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.c | 24 +++++++++++++++++-------\n 1 file changed, 17 insertions(+), 7 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 2bdff4457b..3281b1374e 100644\n--- a/config.c\n+++ b/config.c\n@@ -437,11 +437,26 @@ int git_config_key_is_valid(const char *key)\n \treturn !git_config_parse_key_1(key, NULL, NULL, 1);\n }\n \n+static int config_parse_pair(const char *key, const char *value,\n+\t\t\t  config_fn_t fn, void *data)\n+{\n+\tchar *canonical_name;\n+\tint ret;\n+\n+\tif (!strlen(key))\n+\t\treturn error(_(\"empty config key\"));\n+\tif (git_config_parse_key(key, &canonical_name, NULL))\n+\t\treturn -1;\n+\n+\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n+\tfree(canonical_name);\n+\treturn ret;\n+}\n+\n int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n \tconst char *value;\n-\tchar *canonical_name;\n \tstruct strbuf **pair;\n \tint ret;\n \n@@ -462,12 +477,7 @@ int git_config_parse_parameter(const char *text,\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \t}\n \n-\tif (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {\n-\t\tret = -1;\n-\t} else {\n-\t\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n-\t\tfree(canonical_name);\n-\t}\n+\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n \tstrbuf_list_free(pair);\n \treturn ret;\n }\n-- \n2.29.2\n\n"},{"id":"410656","messageId":"97740ada840a1e2f151003e695de9f2efa5a7e62.1606214397.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1606214397.git.ps@pks.im","subject":"[PATCH v2 2/2] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-11-24T10:50:55Z","receivedAt":"2020-11-24T10:51:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While we currently have the `GIT_CONFIG_PARAMETERS` environment variable\nwhich can be used to pass runtime configuration data to git processes,\nit's an internal implementation detail and not supposed to be used by\nend users.\n\nNext to being for internal use only, this way of passing config entries\nhas a major downside: the config keys need to be parsed as they contain\nboth key and value in a single variable. As such, it is left to the user\nto escape any potentially harmful characters in the value, which is\nquite hard to do if values are controlled by a third party.\n\nThis commit thus adds a new way of adding config entries via the\nenvironment which gets rid of this shortcoming. If the user passes the\n`GIT_CONFIG_COUNT=$n` environment variable, Git will parse environment\nvariable pairs `GIT_CONFIG_KEY_$i` and `GIT_CONFIG_VALUE_$i` for each\n`i` in `[0,n)`.\n\nWhile the same can be achieved with `git -c <name>=<value>`, one may\nwish to not do so for potentially sensitive information. E.g. if one\nwants to set `http.extraHeader` to contain an authentication token,\ndoing so via `-c` would trivially leak those credentials via e.g. ps(1),\nwhich typically also shows command arguments.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-config.txt |   9 +++\n cache.h                      |   1 +\n config.c                     |  72 +++++++++++++++++++-----\n environment.c                |   1 +\n t/t1300-config.sh            | 105 ++++++++++++++++++++++++++++++++++-\n 5 files changed, 173 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/git-config.txt b/Documentation/git-config.txt\nindex 7573160f21..84ae9b51a3 100644\n--- a/Documentation/git-config.txt\n+++ b/Documentation/git-config.txt\n@@ -335,6 +335,15 @@ GIT_CONFIG_NOSYSTEM::\n \tWhether to skip reading settings from the system-wide\n \t$(prefix)/etc/gitconfig file. See linkgit:git[1] for details.\n \n+GIT_CONFIG_COUNT,GIT_CONFIG_KEY_<n>,GIT_CONFIG_VALUE_<n>::\n+\tIf GIT_CONFIG_COUNT is set to a positive number, all environment pairs\n+\tGIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> up to that number will be\n+\tadded to the process's runtime configuration. The config pairs are\n+\tzero-indexed. Any missing key or value is treated as an error. An empty\n+\tGIT_CONFIG_COUNT is treated the same as GIT_CONFIG_COUNT=0, namely no\n+\tpairs are processed. Config entries set this way have command scope,\n+\tbut will be overridden by any explicit options passed via `git -c`.\n+\n See also <<FILES>>.\n \n \ndiff --git a/cache.h b/cache.h\nindex c0072d43b1..8a36146337 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -472,6 +472,7 @@ static inline enum object_type object_type(unsigned int mode)\n #define TEMPLATE_DIR_ENVIRONMENT \"GIT_TEMPLATE_DIR\"\n #define CONFIG_ENVIRONMENT \"GIT_CONFIG\"\n #define CONFIG_DATA_ENVIRONMENT \"GIT_CONFIG_PARAMETERS\"\n+#define CONFIG_COUNT_ENVIRONMENT \"GIT_CONFIG_COUNT\"\n #define EXEC_PATH_ENVIRONMENT \"GIT_EXEC_PATH\"\n #define CEILING_DIRECTORIES_ENVIRONMENT \"GIT_CEILING_DIRECTORIES\"\n #define NO_REPLACE_OBJECTS_ENVIRONMENT \"GIT_NO_REPLACE_OBJECTS\"\ndiff --git a/config.c b/config.c\nindex 3281b1374e..5da1ae16c9 100644\n--- a/config.c\n+++ b/config.c\n@@ -484,38 +484,82 @@ int git_config_parse_parameter(const char *text,\n \n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n-\tconst char *env = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tconst char *env;\n+\tstruct strbuf envvar = STRBUF_INIT;\n \tint ret = 0;\n-\tchar *envw;\n+\tchar *envw = NULL;\n \tconst char **argv = NULL;\n-\tint nr = 0, alloc = 0;\n \tint i;\n \tstruct config_source source;\n \n-\tif (!env)\n-\t\treturn 0;\n-\n \tmemset(&source, 0, sizeof(source));\n \tsource.prev = cf;\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n \tcf = &source;\n \n-\t/* sq_dequote will write over it */\n-\tenvw = xstrdup(env);\n+\tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n+\tif (env) {\n+\t\tunsigned long count;\n+\t\tchar *endp;\n \n-\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n-\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n-\t\tgoto out;\n+\t\tcount = strtoul(env, &endp, 10);\n+\t\tif (*endp) {\n+\t\t\tret = error(_(\"bogus count in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (count > INT_MAX) {\n+\t\t\tret = error(_(\"too many entries in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (i = 0; i < count; i++) {\n+\t\t\tconst char *key, *value;\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_KEY_%d\", i);\n+\t\t\tkey = getenv(envvar.buf);\n+\t\t\tif (!key) {\n+\t\t\t\tret = error(_(\"missing config key %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_VALUE_%d\", i);\n+\t\t\tvalue = getenv(envvar.buf);\n+\t\t\tif (!value) {\n+\t\t\t\tret = error(_(\"missing config value %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n \t}\n \n-\tfor (i = 0; i < nr; i++) {\n-\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n-\t\t\tret = -1;\n+\tenv = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tif (env) {\n+\t\tint nr = 0, alloc = 0;\n+\n+\t\t/* sq_dequote will write over it */\n+\t\tenvw = xstrdup(env);\n+\n+\t\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n+\t\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n \t\t\tgoto out;\n \t\t}\n+\n+\t\tfor (i = 0; i < nr; i++) {\n+\t\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n \t}\n \n out:\n+\tstrbuf_release(&envvar);\n \tfree(argv);\n \tfree(envw);\n \tcf = source.prev;\ndiff --git a/environment.c b/environment.c\nindex bb518c61cd..e94eca92f3 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -116,6 +116,7 @@ const char * const local_repo_env[] = {\n \tALTERNATE_DB_ENVIRONMENT,\n \tCONFIG_ENVIRONMENT,\n \tCONFIG_DATA_ENVIRONMENT,\n+\tCONFIG_COUNT_ENVIRONMENT,\n \tDB_ENVIRONMENT,\n \tGIT_DIR_ENVIRONMENT,\n \tGIT_WORK_TREE_ENVIRONMENT,\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 825d9a184f..8c90cca79d 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1316,6 +1316,107 @@ test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n \t\tgit config --get-regexp \"env.*\"\n '\n \n+test_expect_success 'git config handles environment config pairs' '\n+\tGIT_CONFIG_COUNT=2 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"foo\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"bar\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one foo\n+\tpair.two bar\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs without count' '\n+\ttest_must_fail env GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one 2>error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one\n+'\n+\n+test_expect_success 'git config ignores pairs exceeding count' '\n+\tGIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"value\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with empty count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT= GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config fails with invalid count' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=10a git config --list 2>error &&\n+\ttest_i18ngrep \"bogus count\" error &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=9999999999999999 git config --list 2>error &&\n+\ttest_i18ngrep \"too many entries\" error\n+'\n+\n+test_expect_success 'git config fails with missing config key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config key\" error\n+'\n+\n+test_expect_success 'git config fails with missing config value' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=\"pair.one\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config value\" error\n+'\n+\n+test_expect_success 'git config fails with invalid config pair key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0= GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=missing-section GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list\n+'\n+\n+test_expect_success 'environment overrides config file' '\n+\ttest_when_finished \"rm -f .git/config\" &&\n+\tcat >.git/config <<-EOF &&\n+\t[pair]\n+\tone = value\n+\tEOF\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=override \\\n+\t\tgit config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'command line overrides environment config' '\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n+\t\tgit -c pair.one=override config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n@@ -1661,9 +1762,11 @@ test_expect_success '--show-origin with --list' '\n \tfile:.git/config\tuser.override=local\n \tfile:.git/config\tinclude.path=../include/relative.include\n \tfile:.git/../include/relative.include\tuser.relative=include\n+\tcommand line:\tuser.environ=true\n \tcommand line:\tuser.cmdline=true\n \tEOF\n-\tgit -c user.cmdline=true config --list --show-origin >output &&\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=user.environ GIT_CONFIG_VALUE_0=true\\\n+\t\tgit -c user.cmdline=true config --list --show-origin >output &&\n \ttest_cmp expect output\n '\n \n-- \n2.29.2\n\n"},{"id":"410761","messageId":"xmqqtutef6kb.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"97740ada840a1e2f151003e695de9f2efa5a7e62.1606214397.git.ps@pks.im","subject":"Re: [PATCH v2 2/2] config: allow specifying config entries via envvar pairs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-25T03:39:48Z","receivedAt":"2020-11-25T03:39:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> +GIT_CONFIG_COUNT,GIT_CONFIG_KEY_<n>,GIT_CONFIG_VALUE_<n>::\n\nI think we write a header with multiple/related items like this\ninstead:\n\n    GIT_CONFIG_COUNT::\n    GIT_CONFIG_KEY_<n>::\n    GIT_CONFIG_VALUE_<n>::\n\nSee how -f/--file is marked up in an earlier part of the same file.\n\n> +\tIf GIT_CONFIG_COUNT is set to a positive number, all environment pairs\n> +\tGIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> up to that number will be\n> +\tadded to the process's runtime configuration. The config pairs are\n> +\tzero-indexed. Any missing key or value is treated as an error. An empty\n> +\tGIT_CONFIG_COUNT is treated the same as GIT_CONFIG_COUNT=0, namely no\n> +\tpairs are processed. Config entries set this way have command scope,\n> +\tbut will be overridden by any explicit options passed via `git -c`.\n> +\n>  See also <<FILES>>.\n\nDoesn't this <<FILES>> refer to GIT_CONFIG and GIT_CONFIG_NOSYSTEM\nthat are described earlier?  It certainly looks out of place to see\nit after the KEY/VALUE thing.\n\n> +\t\tfor (i = 0; i < count; i++) {\n> +\t\t\tconst char *key, *value;\n> +\n> +\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_KEY_%d\", i);\n> +\t\t\tkey = getenv(envvar.buf);\n> +\t\t\tif (!key) {\n> +\t\t\t\tret = error(_(\"missing config key %s\"), envvar.buf);\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n> +\t\t\tstrbuf_reset(&envvar);\n> +\n> +\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_VALUE_%d\", i);\n> +\t\t\tvalue = getenv(envvar.buf);\n> +\t\t\tif (!value) {\n> +\t\t\t\tret = error(_(\"missing config value %s\"), envvar.buf);\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n> +\t\t\tstrbuf_reset(&envvar);\n\nDidn't we got bitten by number of times that the string returned by\ngetenv() are not necessarily nonvolatile depending on platforms?  I\nthink the result of getenv() would need to be xstrdup'ed.\n\ncf. 6776a84d (diff: ensure correct lifetime of external_diff_cmd,\n2019-01-11)\n\n> +\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n> +\t\t\t\tret = -1;\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n> +\t\t}\n>  \t}\n>  \n> -\tfor (i = 0; i < nr; i++) {\n> -\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n> -\t\t\tret = -1;\n> +\tenv = getenv(CONFIG_DATA_ENVIRONMENT);\n\n> +\tif (env) {\n> +\t\tint nr = 0, alloc = 0;\n> +\n> +\t\t/* sq_dequote will write over it */\n> +\t\tenvw = xstrdup(env);\n> +\n> +\t\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n> +\t\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n>  \t\t\tgoto out;\n>  \t\t}\n> +\n> +\t\tfor (i = 0; i < nr; i++) {\n> +\t\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n> +\t\t\t\tret = -1;\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n> +\t\t}\n>  \t}\n>  \n>  out:\n> +\tstrbuf_release(&envvar);\n>  \tfree(argv);\n>  \tfree(envw);\n>  \tcf = source.prev;\n\nWith re-indentation this patch does, it is a bit hard to see the\ncorrespondence between common lines in preimage and postimage, but I\nthink the patch adds the support of the new style environments\nbefore the existing support of the GIT_CONFIG_DATA, but when there\nis no compelling reason not to, new code should be added near the\nbottom, not before the existing code, in the function.\n\nOtherwise, this part of the patch looks OK to me.\n\nThanks.\n"},{"id":"410765","messageId":"X74CigYS7AUtMo9Q@tanuki","threadId":"54704","inReplyTo":"xmqqtutef6kb.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2 2/2] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-11-25T07:06:50Z","receivedAt":"2020-11-25T07:06:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Nov 24, 2020 at 07:39:48PM -0800, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > +GIT_CONFIG_COUNT,GIT_CONFIG_KEY_<n>,GIT_CONFIG_VALUE_<n>::\n> \n> I think we write a header with multiple/related items like this\n> instead:\n> \n>     GIT_CONFIG_COUNT::\n>     GIT_CONFIG_KEY_<n>::\n>     GIT_CONFIG_VALUE_<n>::\n> \n> See how -f/--file is marked up in an earlier part of the same file.\n\nAh, thanks. I wondered how to format these but didn't spot other\nexamples.\n\n> > +\tIf GIT_CONFIG_COUNT is set to a positive number, all environment pairs\n> > +\tGIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> up to that number will be\n> > +\tadded to the process's runtime configuration. The config pairs are\n> > +\tzero-indexed. Any missing key or value is treated as an error. An empty\n> > +\tGIT_CONFIG_COUNT is treated the same as GIT_CONFIG_COUNT=0, namely no\n> > +\tpairs are processed. Config entries set this way have command scope,\n> > +\tbut will be overridden by any explicit options passed via `git -c`.\n> > +\n> >  See also <<FILES>>.\n> \n> Doesn't this <<FILES>> refer to GIT_CONFIG and GIT_CONFIG_NOSYSTEM\n> that are described earlier?  It certainly looks out of place to see\n> it after the KEY/VALUE thing.\n\nRight, my fault.\n\n> > +\t\tfor (i = 0; i < count; i++) {\n> > +\t\t\tconst char *key, *value;\n> > +\n> > +\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_KEY_%d\", i);\n> > +\t\t\tkey = getenv(envvar.buf);\n> > +\t\t\tif (!key) {\n> > +\t\t\t\tret = error(_(\"missing config key %s\"), envvar.buf);\n> > +\t\t\t\tgoto out;\n> > +\t\t\t}\n> > +\t\t\tstrbuf_reset(&envvar);\n> > +\n> > +\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_VALUE_%d\", i);\n> > +\t\t\tvalue = getenv(envvar.buf);\n> > +\t\t\tif (!value) {\n> > +\t\t\t\tret = error(_(\"missing config value %s\"), envvar.buf);\n> > +\t\t\t\tgoto out;\n> > +\t\t\t}\n> > +\t\t\tstrbuf_reset(&envvar);\n> \n> Didn't we got bitten by number of times that the string returned by\n> getenv() are not necessarily nonvolatile depending on platforms?  I\n> think the result of getenv() would need to be xstrdup'ed.\n> \n> cf. 6776a84d (diff: ensure correct lifetime of external_diff_cmd,\n> 2019-01-11)\n\nWe did, but do we have to in this case? There is no interleaving calls\nto getenv(3P), so we don't depend on at least $n getenv(3P) calls\nsucceeding without clobbering old values. It's true that it could be\nthat any other caller in the callchain clobbers the value, but as far as\nI can see none does.\n\nAnyway, I'm not opposed to changing this if you think it to be\nnecessary.\n\n> > +\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n> > +\t\t\t\tret = -1;\n> > +\t\t\t\tgoto out;\n> > +\t\t\t}\n> > +\t\t}\n> >  \t}\n> >  \n> > -\tfor (i = 0; i < nr; i++) {\n> > -\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n> > -\t\t\tret = -1;\n> > +\tenv = getenv(CONFIG_DATA_ENVIRONMENT);\n> \n> > +\tif (env) {\n> > +\t\tint nr = 0, alloc = 0;\n> > +\n> > +\t\t/* sq_dequote will write over it */\n> > +\t\tenvw = xstrdup(env);\n> > +\n> > +\t\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n> > +\t\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n> >  \t\t\tgoto out;\n> >  \t\t}\n> > +\n> > +\t\tfor (i = 0; i < nr; i++) {\n> > +\t\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n> > +\t\t\t\tret = -1;\n> > +\t\t\t\tgoto out;\n> > +\t\t\t}\n> > +\t\t}\n> >  \t}\n> >  \n> >  out:\n> > +\tstrbuf_release(&envvar);\n> >  \tfree(argv);\n> >  \tfree(envw);\n> >  \tcf = source.prev;\n> \n> With re-indentation this patch does, it is a bit hard to see the\n> correspondence between common lines in preimage and postimage, but I\n> think the patch adds the support of the new style environments\n> before the existing support of the GIT_CONFIG_DATA, but when there\n> is no compelling reason not to, new code should be added near the\n> bottom, not before the existing code, in the function.\n> \n> Otherwise, this part of the patch looks OK to me.\n> \n> Thanks.\n\nIt is required as this is what sets precedence of GIT_CONFIG_PARAMETERS\nand thus `git -c` over GIT_CONFIG_COUNT. It's easy enough to split this\ninto two patches though, with a first refactoring which does the\nindentation and a second one which adds the new code.\n\nPatrick\n"},{"id":"410766","messageId":"xmqqpn41g9xt.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"X74CigYS7AUtMo9Q@tanuki","subject":"Re: [PATCH v2 2/2] config: allow specifying config entries via envvar pairs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-25T07:41:34Z","receivedAt":"2020-11-25T07:41:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> > +\t\tfor (i = 0; i < count; i++) {\n>> > +\t\t\tconst char *key, *value;\n>> > +\n>> > +\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_KEY_%d\", i);\n>> > +\t\t\tkey = getenv(envvar.buf);\n>> > +\t\t\tif (!key) {\n>> > +\t\t\t\tret = error(_(\"missing config key %s\"), envvar.buf);\n>> > +\t\t\t\tgoto out;\n>> > +\t\t\t}\n>> > +\t\t\tstrbuf_reset(&envvar);\n>> > +\n>> > +\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_VALUE_%d\", i);\n>> > +\t\t\tvalue = getenv(envvar.buf);\n>> > +\t\t\tif (!value) {\n>> > +\t\t\t\tret = error(_(\"missing config value %s\"), envvar.buf);\n>> > +\t\t\t\tgoto out;\n>> > +\t\t\t}\n>> > +\t\t\tstrbuf_reset(&envvar);\n>> \n>> Didn't we got bitten by number of times that the string returned by\n>> getenv() are not necessarily nonvolatile depending on platforms?  I\n>> think the result of getenv() would need to be xstrdup'ed.\n>> \n>> cf. 6776a84d (diff: ensure correct lifetime of external_diff_cmd,\n>> 2019-01-11)\n>\n> We did, but do we have to in this case? There is no interleaving calls\n> to getenv(3P), so we don't depend on at least $n getenv(3P) calls\n> succeeding without clobbering old values. It's true that it could be\n> that any other caller in the callchain clobbers the value, but as far as\n> I can see none does.\n\nDoesn't the code expect \"key\" will stay valid even after another\ncall to getenv() grabs \"value\"?\n\n> It is required as this is what sets precedence of GIT_CONFIG_PARAMETERS\n> and thus `git -c` over GIT_CONFIG_COUNT.\n\nOK, that is what the \"will be overridden by any explicit options\"\nwas about.  Perhaps that deserves an in-code comment, something like\n\n\t/*\n\t * process GIT_CONFIG_KEY_N/GIT_CONFIG_VALUE_N pairs\n\t * first, to be overridden by GIT_CONFIG_PARAMETERS\n\t * inherited from parent Git processes' \"git -c var=val\"\n\t * later\n\t */\n\nbefore we check GIT_CONFIG_COUNT and loop over the new style\nenvironment variables.\n\nThanks.\n\n"},{"id":"410768","messageId":"X74OblVAeE7PVJOP@tanuki","threadId":"54704","inReplyTo":"xmqqpn41g9xt.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2 2/2] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-11-25T07:57:34Z","receivedAt":"2020-11-25T07:57:12Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Nov 24, 2020 at 11:41:34PM -0800, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> >> > +\t\tfor (i = 0; i < count; i++) {\n> >> > +\t\t\tconst char *key, *value;\n> >> > +\n> >> > +\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_KEY_%d\", i);\n> >> > +\t\t\tkey = getenv(envvar.buf);\n> >> > +\t\t\tif (!key) {\n> >> > +\t\t\t\tret = error(_(\"missing config key %s\"), envvar.buf);\n> >> > +\t\t\t\tgoto out;\n> >> > +\t\t\t}\n> >> > +\t\t\tstrbuf_reset(&envvar);\n> >> > +\n> >> > +\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_VALUE_%d\", i);\n> >> > +\t\t\tvalue = getenv(envvar.buf);\n> >> > +\t\t\tif (!value) {\n> >> > +\t\t\t\tret = error(_(\"missing config value %s\"), envvar.buf);\n> >> > +\t\t\t\tgoto out;\n> >> > +\t\t\t}\n> >> > +\t\t\tstrbuf_reset(&envvar);\n> >> \n> >> Didn't we got bitten by number of times that the string returned by\n> >> getenv() are not necessarily nonvolatile depending on platforms?  I\n> >> think the result of getenv() would need to be xstrdup'ed.\n> >> \n> >> cf. 6776a84d (diff: ensure correct lifetime of external_diff_cmd,\n> >> 2019-01-11)\n> >\n> > We did, but do we have to in this case? There is no interleaving calls\n> > to getenv(3P), so we don't depend on at least $n getenv(3P) calls\n> > succeeding without clobbering old values. It's true that it could be\n> > that any other caller in the callchain clobbers the value, but as far as\n> > I can see none does.\n> \n> Doesn't the code expect \"key\" will stay valid even after another\n> call to getenv() grabs \"value\"?\n\nOh, right. No idea what I was thinking there.\n\n> > It is required as this is what sets precedence of GIT_CONFIG_PARAMETERS\n> > and thus `git -c` over GIT_CONFIG_COUNT.\n> \n> OK, that is what the \"will be overridden by any explicit options\"\n> was about.  Perhaps that deserves an in-code comment, something like\n> \n> \t/*\n> \t * process GIT_CONFIG_KEY_N/GIT_CONFIG_VALUE_N pairs\n> \t * first, to be overridden by GIT_CONFIG_PARAMETERS\n> \t * inherited from parent Git processes' \"git -c var=val\"\n> \t * later\n> \t */\n> \n> before we check GIT_CONFIG_COUNT and loop over the new style\n> environment variables.\n> \n> Thanks.\n\nWill do, thanks!\n\nPatrick\n"},{"id":"410772","messageId":"875z5tq0v9.fsf@evledraar.gmail.com","threadId":"54704","inReplyTo":"97740ada840a1e2f151003e695de9f2efa5a7e62.1606214397.git.ps@pks.im","subject":"Re: [PATCH v2 2/2] config: allow specifying config entries via envvar pairs","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2020-11-25T08:47:22Z","receivedAt":"2020-11-25T08:47:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Nov 24 2020, Patrick Steinhardt wrote:\n\n> +test_expect_success 'command line overrides environment config' '\n> +\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n> +\t\tgit -c pair.one=override config pair.one >actual &&\n> +\tcat >expect <<-EOF &&\n> +\toverride\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n\nMaybe a test to see which one of this new-style key-value thing\nv.s. GIT_CONFIG_PARAMETERS wins? Helps if/when we ever refactor this to\nat least see the behavior of the purely internal thing changed.\n"},{"id":"410774","messageId":"87360xq08k.fsf@evledraar.gmail.com","threadId":"54704","inReplyTo":"97740ada840a1e2f151003e695de9f2efa5a7e62.1606214397.git.ps@pks.im","subject":"Re: [PATCH v2 2/2] config: allow specifying config entries via envvar pairs","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2020-11-25T09:00:59Z","receivedAt":"2020-11-25T09:01:19Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Nov 24 2020, Patrick Steinhardt wrote:\n\n...some more feedback.\n\n> +GIT_CONFIG_COUNT,GIT_CONFIG_KEY_<n>,GIT_CONFIG_VALUE_<n>::\n> +\tIf GIT_CONFIG_COUNT is set to a positive number, all environment pairs\n> +\tGIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> up to that number will be\n> +\tadded to the process's runtime configuration. The config pairs are\n> +\tzero-indexed. Any missing key or value is treated as an error. An empty\n> +\tGIT_CONFIG_COUNT is treated the same as GIT_CONFIG_COUNT=0, namely no\n> +\tpairs are processed. Config entries set this way have command scope,\n> +\tbut will be overridden by any explicit options passed via `git -c`.\n\nPerhaps work in some/all of some version of these:\n\n - There's also a GIT_CONFIG_PARAMETERS variable, which is considered\n   internal to Git itself. Users are expected to set these.\n\n   --> I.e. even if we're not going to support some format for\n   --> GIT_CONFIG_PARAMETERS document what it is.\n\n - This is analogous to the pre-receive `GIT_PUSH_OPTION_*` variables\n   (see linkgit:githooks[5]), but unlike those the `-c` option to\n   linkgit:git(1) does not set `GIT_CONFIG_*`.\n\n - Saying \"command scope\" here I think is wrong/misleading. If I didn't\n   know how this worked I'd expect the first git process to see it to\n   delete it from the env, so e.g. the \"fetch\" command would see it, but\n   not the \"gc\" it spawned (different commands). Maybe just say \"the\n   scope of these is as with other GIT_* environment variables, they'll\n   be inherited by subprocesses\".\n\n> diff --git a/cache.h b/cache.h\n> index c0072d43b1..8a36146337 100644\n> --- a/cache.h\n> +++ b/cache.h\n> @@ -472,6 +472,7 @@ static inline enum object_type object_type(unsigned int mode)\n>  #define TEMPLATE_DIR_ENVIRONMENT \"GIT_TEMPLATE_DIR\"\n>  #define CONFIG_ENVIRONMENT \"GIT_CONFIG\"\n>  #define CONFIG_DATA_ENVIRONMENT \"GIT_CONFIG_PARAMETERS\"\n> +#define CONFIG_COUNT_ENVIRONMENT \"GIT_CONFIG_COUNT\"\n\nI was wondering if this shouldn't be \"GIT_CONFIG_KEY_COUNT\" to be\nconsistent with the push options environment, but on a closer look we\nhave:\n\n - GIT_CONFIG_COUNT\n - GIT_CONFIG_KEY_N\n - GIT_CONFIG_VALUE_N\n - GIT_PUSH_OPTION_COUNT\n - GIT_PUSH_OPTION_N\n\nSo I guess that makes sense & is consistent since we'd like to split the\nkey-value here to save the user the effort of figuring out which \"=\"\nthey should split on.\n\n> -\tif (!env)\n> -\t\treturn 0;\n> -\n\nRe the indent question to make the diff more readable question Junio\nhad: could set some \"do we have this or that\" variables here to not\nreindent the existing code, but maybe not worth the effort...\n\n> -\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n> -\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n> -\t\tgoto out;\n> +\t\tcount = strtoul(env, &endp, 10);\n> +\t\tif (*endp) {\n> +\t\t\tret = error(_(\"bogus count in %s\"), CONFIG_COUNT_ENVIRONMENT);\n> +\t\t\tgoto out;\n> +\t\t}\n> +\t\tif (count > INT_MAX) {\n> +\t\t\tret = error(_(\"too many entries in %s\"), CONFIG_COUNT_ENVIRONMENT);\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\tfor (i = 0; i < count; i++) {\n> +\t\t\tconst char *key, *value;\n> +\n> +\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_KEY_%d\", i);\n> +\t\t\tkey = getenv(envvar.buf);\n> +\t\t\tif (!key) {\n> +\t\t\t\tret = error(_(\"missing config key %s\"), envvar.buf);\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n> +\t\t\tstrbuf_reset(&envvar);\n> +\n> +\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_VALUE_%d\", i);\n> +\t\t\tvalue = getenv(envvar.buf);\n> +\t\t\tif (!value) {\n> +\t\t\t\tret = error(_(\"missing config value %s\"), envvar.buf);\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n> +\t\t\tstrbuf_reset(&envvar);\n> +\n> +\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n> +\t\t\t\tret = -1;\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n> +\t\t}\n>  \t}\n>  \n> -\tfor (i = 0; i < nr; i++) {\n> -\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n> -\t\t\tret = -1;\n> +\tenv = getenv(CONFIG_DATA_ENVIRONMENT);\n> +\tif (env) {\n> +\t\tint nr = 0, alloc = 0;\n> +\n> +\t\t/* sq_dequote will write over it */\n> +\t\tenvw = xstrdup(env);\n> +\n> +\t\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n> +\t\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n>  \t\t\tgoto out;\n>  \t\t}\n> +\n> +\t\tfor (i = 0; i < nr; i++) {\n> +\t\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n> +\t\t\t\tret = -1;\n> +\t\t\t\tgoto out;\n> +\t\t\t}\n> +\t\t}\n>  \t}\n>  \n>  out:\n> +\tstrbuf_release(&envvar);\n>  \tfree(argv);\n>  \tfree(envw);\n>  \tcf = source.prev;\n> diff --git a/environment.c b/environment.c\n> index bb518c61cd..e94eca92f3 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -116,6 +116,7 @@ const char * const local_repo_env[] = {\n>  \tALTERNATE_DB_ENVIRONMENT,\n>  \tCONFIG_ENVIRONMENT,\n>  \tCONFIG_DATA_ENVIRONMENT,\n> +\tCONFIG_COUNT_ENVIRONMENT,\n>  \tDB_ENVIRONMENT,\n>  \tGIT_DIR_ENVIRONMENT,\n>  \tGIT_WORK_TREE_ENVIRONMENT,\n> diff --git a/t/t1300-config.sh b/t/t1300-config.sh\n> index 825d9a184f..8c90cca79d 100755\n> --- a/t/t1300-config.sh\n> +++ b/t/t1300-config.sh\n> @@ -1316,6 +1316,107 @@ test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n>  \t\tgit config --get-regexp \"env.*\"\n>  '\n>  \n> +test_expect_success 'git config handles environment config pairs' '\n\nI was wondering if the patch would keep the current\nGIT_CONFIG_PARAMETERS or replace it entirely with the new facility.\n\nOn the one hand it would make sense to just replace\nGIT_CONFIG_PARAMETERS, we could make this code loop over the new values.\n\nOn the other hand, and this is an edge case I hadn't considered before,\nany change to the semantics of GIT_CONFIG_PARAMETERS means that e.g. a\nfetch->gc spawning would break in the face of a concurrent OS update to\n/usr/bin/git, since \"fetch\" and \"gc\" might be of differing versions\n"},{"id":"410778","messageId":"X740yqoYIhrqsNRE@coredump.intra.peff.net","threadId":"54704","inReplyTo":"cover.1606214397.git.ps@pks.im","subject":"Re: [PATCH v2 0/2] config: allow specifying config entries via envvar pairs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-25T10:41:14Z","receivedAt":"2020-11-25T10:41:17Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Nov 24, 2020 at 11:50:46AM +0100, Patrick Steinhardt wrote:\n\n>     - I've changed priorities. The envvars are treated as command-level\n>       and as such override all values configured in files. But any\n>       explicit `git -c key=value` will now override these envvars.\n\nThat ordering makes sense. Those get passed through the environment,\ntoo, but at some point there is a process where your new ones are in the\nenvironment and the \"-c\" ones are on the command-line.\n\nI do still think that a \"--config-env\" option solves your problem in a\nmuch simpler way (especially in terms of interface we expose to users\nthat we'll be locked into forever). I sketched out the solution below if\nit's of interest (and I'd be happy to polish it up, or hand it off to\nyou if so). But if you're unconvinced, I'll stop mentioning it.\n\ndiff --git a/config.c b/config.c\nindex 8f324ed3a6..d8cf6a5d6b 100644\n--- a/config.c\n+++ b/config.c\n@@ -345,6 +345,27 @@ void git_config_push_parameter(const char *text)\n \tstrbuf_release(&env);\n }\n \n+void git_config_push_env(const char *spec)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *env_name;\n+\tconst char *env_value;\n+\n+\tenv_name = strchr(spec, '=');\n+\tif (!env_name)\n+\t\treturn; /* die or warn? */\n+\tenv_name++;\n+\n+\tenv_value = getenv(env_name);\n+\tif (!env_value)\n+\t\treturn; /* die or warn? */\n+\n+\tstrbuf_add(&buf, spec, env_name - spec);\n+\tstrbuf_addstr(&buf, env_value);\n+\tgit_config_push_parameter(buf.buf);\n+\tstrbuf_release(&buf);\n+}\n+\n static inline int iskeychar(int c)\n {\n \treturn isalnum(c) || c == '-';\ndiff --git a/config.h b/config.h\nindex 91cdfbfb41..d05651c96c 100644\n--- a/config.h\n+++ b/config.h\n@@ -138,6 +138,7 @@ int git_config_from_mem(config_fn_t fn,\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     const struct object_id *oid, void *data);\n void git_config_push_parameter(const char *text);\n+void git_config_push_env(const char *spec);\n int git_config_from_parameters(config_fn_t fn, void *data);\n void read_early_config(config_fn_t cb, void *data);\n void read_very_early_config(config_fn_t cb, void *data);\ndiff --git a/git.c b/git.c\nindex 4b7bd77b80..342f2fb0c9 100644\n--- a/git.c\n+++ b/git.c\n@@ -254,6 +254,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tgit_config_push_parameter((*argv)[1]);\n \t\t\t(*argv)++;\n \t\t\t(*argc)--;\n+\t\t} else if (skip_prefix(cmd, \"--config-env=\", &cmd)) {\n+\t\t\tgit_config_push_env(cmd);\n \t\t} else if (!strcmp(cmd, \"--literal-pathspecs\")) {\n \t\t\tsetenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, \"1\", 1);\n \t\t\tif (envchanged)\n"},{"id":"410780","messageId":"X75ZNv/8X5Z7Yfci@ncase","threadId":"54704","inReplyTo":"X740yqoYIhrqsNRE@coredump.intra.peff.net","subject":"Re: [PATCH v2 0/2] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-11-25T13:16:38Z","receivedAt":"2020-11-25T13:17:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Nov 25, 2020 at 05:41:14AM -0500, Jeff King wrote:\n> On Tue, Nov 24, 2020 at 11:50:46AM +0100, Patrick Steinhardt wrote:\n> \n> >     - I've changed priorities. The envvars are treated as command-level\n> >       and as such override all values configured in files. But any\n> >       explicit `git -c key=value` will now override these envvars.\n> \n> That ordering makes sense. Those get passed through the environment,\n> too, but at some point there is a process where your new ones are in the\n> environment and the \"-c\" ones are on the command-line.\n> \n> I do still think that a \"--config-env\" option solves your problem in a\n> much simpler way (especially in terms of interface we expose to users\n> that we'll be locked into forever). I sketched out the solution below if\n> it's of interest (and I'd be happy to polish it up, or hand it off to\n> you if so). But if you're unconvinced, I'll stop mentioning it.\n\nThe thing I like more about using envvars only is that you only need to\nmodify a single part, while with `--config-env` there's two moving\nparts. E.g. assume you have a script and want certain configuration to\napply to all git commands in that script. It's trivial in the envvar\ncase, while for `--config-env` you'll also have to modify each single\ngit call. You could get around that by using a wrapper, but it's still a\ntad more involved. A second thing I briefly wondered about is the\nmaximum command line length, which may be easier to hit in case you want\nto pass a lot of config entries.\n\nNone of these complaints apply to my original usecase, where\n`--config-env` would work equally well. But I do think that for\nscripting use, which is going to be most of all cases where my patch\nseries is useful, GIT_CONFIG_COUNT is easier to use.\n\nThere probably are good arguments for `--config-env`, for example that\nit's easier to spot when executing a git command. I stil lean towards my\ncurrent implementation, but I'm obviously biased. So if there is\nconsensus that we should use `--config-env` instead, I'm not opposed.\n\nPatrick\n"},{"id":"410797","messageId":"xmqqzh35dxmn.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"87360xq08k.fsf@evledraar.gmail.com","subject":"Re: [PATCH v2 2/2] config: allow specifying config entries via envvar pairs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-25T19:50:24Z","receivedAt":"2020-11-25T19:50:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> On Tue, Nov 24 2020, Patrick Steinhardt wrote:\n>\n> ...some more feedback.\n>\n>> +GIT_CONFIG_COUNT,GIT_CONFIG_KEY_<n>,GIT_CONFIG_VALUE_<n>::\n>> +\tIf GIT_CONFIG_COUNT is set to a positive number, all environment pairs\n>> +\tGIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> up to that number will be\n>> +\tadded to the process's runtime configuration. The config pairs are\n>> +\tzero-indexed. Any missing key or value is treated as an error. An empty\n>> +\tGIT_CONFIG_COUNT is treated the same as GIT_CONFIG_COUNT=0, namely no\n>> +\tpairs are processed. Config entries set this way have command scope,\n>> +\tbut will be overridden by any explicit options passed via `git -c`.\n>\n> Perhaps work in some/all of some version of these:\n>\n>  - There's also a GIT_CONFIG_PARAMETERS variable, which is considered\n>    internal to Git itself. Users are expected to set these.\n\n\"are\", or \"are not\"?  I think it is the latter, and if so I agree\nthat it is a good thing to say here or somewhere nearby.\n\n>    --> I.e. even if we're not going to support some format for\n>    --> GIT_CONFIG_PARAMETERS document what it is.\n\nMy preference is to keep it an implementation detail, especially if\nwe were to be adding this new thing as a documented feature, so\ndocumenting it beyond its existence and nature is counterproductive.\n\n>  - This is analogous to the pre-receive `GIT_PUSH_OPTION_*` variables\n>    (see linkgit:githooks[5]), but unlike those the `-c` option to\n>    linkgit:git(1) does not set `GIT_CONFIG_*`.\n\nI am slightly negative about this.  It would be an irrelevant noise\nto readers who are interested in environment variables that affect\nhow \"git config\" works (which is what this section is about).  Also\nfor those who want to learn about GIT_PUSH_OPTION variable, I do not\nthink they would look for it in \"git config\" documentation and check\nits ENVIRONMENT section.  It would be much more likely for them to\nlook for them in the documentation for receive-pack or push (and then\nredirected to githooks doc).\n\n>  - Saying \"command scope\" here I think is wrong/misleading. If I didn't\n>    know how this worked I'd expect the first git process to see it to\n>    delete it from the env, so e.g. the \"fetch\" command would see it, but\n>    not the \"gc\" it spawned (different commands). Maybe just say \"the\n>    scope of these is as with other GIT_* environment variables, they'll\n>    be inherited by subprocesses\".\n\nOK.\n\n> Re the indent question to make the diff more readable question Junio\n> had: could set some \"do we have this or that\" variables here to not\n> reindent the existing code, but maybe not worth the effort...\n\nI was leaving a clue for those who want to futz with \"diff\"\nalgorithm that this change can be a good test case for their\nimprovement.\n\nI didn't mean that as a suggestion to help \"diff\" produce a better\nresult by twisting code.  We should not tweak our code to please\n\"git show\" output.  Tweaking code to please \"cat/less $file\" output\nis very much welcome, though.\n\nThanks.\n"},{"id":"410798","messageId":"xmqqv9dtdvui.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"X740yqoYIhrqsNRE@coredump.intra.peff.net","subject":"Re: [PATCH v2 0/2] config: allow specifying config entries via envvar pairs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-11-25T20:28:53Z","receivedAt":"2020-11-25T20:29:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> I do still think that a \"--config-env\" option solves your problem in a\n> much simpler way (especially in terms of interface we expose to users\n> that we'll be locked into forever).\n\nAs a mechanism to allow a custom configuration for a single\ninvocation of a command, I tend to agree.  For a mechansim to affect\nmultiple commands in a sequence (read: scripts), I am not so sure.\n\nThe simplicity of the implementation we see below is also very\nattractive.\n\n> I sketched out the solution below if\n> it's of interest (and I'd be happy to polish it up, or hand it off to\n> you if so). But if you're unconvinced, I'll stop mentioning it.\n>\n> diff --git a/config.c b/config.c\n> index 8f324ed3a6..d8cf6a5d6b 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -345,6 +345,27 @@ void git_config_push_parameter(const char *text)\n>  \tstrbuf_release(&env);\n>  }\n>  \n> +void git_config_push_env(const char *spec)\n> +{\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\tconst char *env_name;\n> +\tconst char *env_value;\n> +\n> +\tenv_name = strchr(spec, '=');\n> +\tif (!env_name)\n> +\t\treturn; /* die or warn? */\n> +\tenv_name++;\n> +\n> +\tenv_value = getenv(env_name);\n> +\tif (!env_value)\n> +\t\treturn; /* die or warn? */\n> +\n> +\tstrbuf_add(&buf, spec, env_name - spec);\n> +\tstrbuf_addstr(&buf, env_value);\n> +\tgit_config_push_parameter(buf.buf);\n> +\tstrbuf_release(&buf);\n> +}\n> +\n>  static inline int iskeychar(int c)\n>  {\n>  \treturn isalnum(c) || c == '-';\n> diff --git a/config.h b/config.h\n> index 91cdfbfb41..d05651c96c 100644\n> --- a/config.h\n> +++ b/config.h\n> @@ -138,6 +138,7 @@ int git_config_from_mem(config_fn_t fn,\n>  int git_config_from_blob_oid(config_fn_t fn, const char *name,\n>  \t\t\t     const struct object_id *oid, void *data);\n>  void git_config_push_parameter(const char *text);\n> +void git_config_push_env(const char *spec);\n>  int git_config_from_parameters(config_fn_t fn, void *data);\n>  void read_early_config(config_fn_t cb, void *data);\n>  void read_very_early_config(config_fn_t cb, void *data);\n> diff --git a/git.c b/git.c\n> index 4b7bd77b80..342f2fb0c9 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -254,6 +254,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n>  \t\t\tgit_config_push_parameter((*argv)[1]);\n>  \t\t\t(*argv)++;\n>  \t\t\t(*argc)--;\n> +\t\t} else if (skip_prefix(cmd, \"--config-env=\", &cmd)) {\n> +\t\t\tgit_config_push_env(cmd);\n>  \t\t} else if (!strcmp(cmd, \"--literal-pathspecs\")) {\n>  \t\t\tsetenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, \"1\", 1);\n>  \t\t\tif (envchanged)\n"},{"id":"410822","messageId":"20201125224737.GK389879@camp.crustytoothpaste.net","threadId":"54704","inReplyTo":"X740yqoYIhrqsNRE@coredump.intra.peff.net","subject":"Re: [PATCH v2 0/2] config: allow specifying config entries via envvar pairs","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-11-25T22:47:37Z","receivedAt":"2020-11-25T22:48:30Z","isPatch":true,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2020-11-25 at 10:41:14, Jeff King wrote:\n> On Tue, Nov 24, 2020 at 11:50:46AM +0100, Patrick Steinhardt wrote:\n> \n> >     - I've changed priorities. The envvars are treated as command-level\n> >       and as such override all values configured in files. But any\n> >       explicit `git -c key=value` will now override these envvars.\n> \n> That ordering makes sense. Those get passed through the environment,\n> too, but at some point there is a process where your new ones are in the\n> environment and the \"-c\" ones are on the command-line.\n\nYeah, I agree this would be the right way to go.\n\n> I do still think that a \"--config-env\" option solves your problem in a\n> much simpler way (especially in terms of interface we expose to users\n> that we'll be locked into forever). I sketched out the solution below if\n> it's of interest (and I'd be happy to polish it up, or hand it off to\n> you if so). But if you're unconvinced, I'll stop mentioning it.\n\nI do rather prefer this approach over the multiple key-value pairs.  I\nthink the use case of scripts could probably be easily solved with an\nadditional environment variable like so:\n\n  args=\"--config-env abc.def=GHI --config-env jkl.mno=PQR\"\n\nThis isn't necessarily super elegant, but I like it more than needing\nto handle many key-value pairs.\n\nBut while I do have a moderately strong preference, I'm not going to\nargue for blocking the series if you still want to go this way.\n-- \nbrian m. carlson (he/him or they/them)\nHouston, Texas, US\n"},{"id":"410841","messageId":"X774nvXcRrP64SZ2@coredump.intra.peff.net","threadId":"54704","inReplyTo":"X75ZNv/8X5Z7Yfci@ncase","subject":"Re: [PATCH v2 0/2] config: allow specifying config entries via envvar pairs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-11-26T00:36:46Z","receivedAt":"2020-11-26T00:37:04Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Nov 25, 2020 at 02:16:38PM +0100, Patrick Steinhardt wrote:\n\n> > I do still think that a \"--config-env\" option solves your problem in a\n> > much simpler way (especially in terms of interface we expose to users\n> > that we'll be locked into forever). I sketched out the solution below if\n> > it's of interest (and I'd be happy to polish it up, or hand it off to\n> > you if so). But if you're unconvinced, I'll stop mentioning it.\n> \n> The thing I like more about using envvars only is that you only need to\n> modify a single part, while with `--config-env` there's two moving\n> parts. E.g. assume you have a script and want certain configuration to\n> apply to all git commands in that script. It's trivial in the envvar\n> case, while for `--config-env` you'll also have to modify each single\n> git call. You could get around that by using a wrapper, but it's still a\n> tad more involved. A second thing I briefly wondered about is the\n> maximum command line length, which may be easier to hit in case you want\n> to pass a lot of config entries.\n\nYeah, that's true. I haven't typically run across this myself because\nusually such a script ends up invoked by git itself. I.e., it is\ngit-foo, and then I do:\n\n  git -c some.var=value foo\n\nwhich puts everything in the environment, but it's done by Git itself,\nso the exact environment format remains opaque.\n\n-Peff\n"},{"id":"410865","messageId":"X79Lz4z8NX5PCjp+@ncase","threadId":"54704","inReplyTo":"20201125224737.GK389879@camp.crustytoothpaste.net","subject":"Re: [PATCH v2 0/2] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-11-26T06:31:43Z","receivedAt":"2020-11-26T06:32:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Nov 25, 2020 at 10:47:37PM +0000, brian m. carlson wrote:\n> On 2020-11-25 at 10:41:14, Jeff King wrote:\n> > On Tue, Nov 24, 2020 at 11:50:46AM +0100, Patrick Steinhardt wrote:\n> > I do still think that a \"--config-env\" option solves your problem in a\n> > much simpler way (especially in terms of interface we expose to users\n> > that we'll be locked into forever). I sketched out the solution below if\n> > it's of interest (and I'd be happy to polish it up, or hand it off to\n> > you if so). But if you're unconvinced, I'll stop mentioning it.\n> \n> I do rather prefer this approach over the multiple key-value pairs.  I\n> think the use case of scripts could probably be easily solved with an\n> additional environment variable like so:\n> \n>   args=\"--config-env abc.def=GHI --config-env jkl.mno=PQR\"\n> \n> This isn't necessarily super elegant, but I like it more than needing\n> to handle many key-value pairs.\n> \n> But while I do have a moderately strong preference, I'm not going to\n> argue for blocking the series if you still want to go this way.\n\nIn the end, it probably boils down to taste. Both work to solve the\nproblem at hand while there are tradeoffs for other usecases for both.\n\nUltimately, those two ways are not mutually exclusive and we could even\nimplement both. So I might as well include Peffs patch in this series,\neven though I'm not sure whether adding two new ways of doing things at\nthe same time would be welcome.\n\nPatrick\n"},{"id":"411041","messageId":"X8YRTPPfkRqo23ll@ncase","threadId":"54704","inReplyTo":"X740yqoYIhrqsNRE@coredump.intra.peff.net","subject":"Re: [PATCH v2 0/2] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-01T09:47:56Z","receivedAt":"2020-12-01T09:49:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Nov 25, 2020 at 05:41:14AM -0500, Jeff King wrote:\n> On Tue, Nov 24, 2020 at 11:50:46AM +0100, Patrick Steinhardt wrote:\n> \n> >     - I've changed priorities. The envvars are treated as command-level\n> >       and as such override all values configured in files. But any\n> >       explicit `git -c key=value` will now override these envvars.\n> \n> That ordering makes sense. Those get passed through the environment,\n> too, but at some point there is a process where your new ones are in the\n> environment and the \"-c\" ones are on the command-line.\n> \n> I do still think that a \"--config-env\" option solves your problem in a\n> much simpler way (especially in terms of interface we expose to users\n> that we'll be locked into forever). I sketched out the solution below if\n> it's of interest (and I'd be happy to polish it up, or hand it off to\n> you if so). But if you're unconvinced, I'll stop mentioning it.\n> \n> diff --git a/config.c b/config.c\n> index 8f324ed3a6..d8cf6a5d6b 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -345,6 +345,27 @@ void git_config_push_parameter(const char *text)\n>  \tstrbuf_release(&env);\n>  }\n>  \n> +void git_config_push_env(const char *spec)\n> +{\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\tconst char *env_name;\n> +\tconst char *env_value;\n> +\n> +\tenv_name = strchr(spec, '=');\n> +\tif (!env_name)\n> +\t\treturn; /* die or warn? */\n> +\tenv_name++;\n> +\n> +\tenv_value = getenv(env_name);\n> +\tif (!env_value)\n> +\t\treturn; /* die or warn? */\n> +\n> +\tstrbuf_add(&buf, spec, env_name - spec);\n> +\tstrbuf_addstr(&buf, env_value);\n> +\tgit_config_push_parameter(buf.buf);\n> +\tstrbuf_release(&buf);\n> +}\n\nI realize that you say it's yet unpolished, but doesn't this have\nparsing issues? The first strchr(3P) probably needs to be a strrchr(3P)\nto correctly parse `includeIf./home/foo/=repo.path=MY_PATH_ENV`. But\nwe'd also have to handle shell quoting for the user, don't we?\n\nAnyway, I'd be happy to adopt is as part of the series if we care\nenough. For now I'll send out the current state I have though.\n\nPatrick\n\n>  static inline int iskeychar(int c)\n>  {\n>  \treturn isalnum(c) || c == '-';\n> diff --git a/config.h b/config.h\n> index 91cdfbfb41..d05651c96c 100644\n> --- a/config.h\n> +++ b/config.h\n> @@ -138,6 +138,7 @@ int git_config_from_mem(config_fn_t fn,\n>  int git_config_from_blob_oid(config_fn_t fn, const char *name,\n>  \t\t\t     const struct object_id *oid, void *data);\n>  void git_config_push_parameter(const char *text);\n> +void git_config_push_env(const char *spec);\n>  int git_config_from_parameters(config_fn_t fn, void *data);\n>  void read_early_config(config_fn_t cb, void *data);\n>  void read_very_early_config(config_fn_t cb, void *data);\n> diff --git a/git.c b/git.c\n> index 4b7bd77b80..342f2fb0c9 100644\n> --- a/git.c\n> +++ b/git.c\n> @@ -254,6 +254,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n>  \t\t\tgit_config_push_parameter((*argv)[1]);\n>  \t\t\t(*argv)++;\n>  \t\t\t(*argc)--;\n> +\t\t} else if (skip_prefix(cmd, \"--config-env=\", &cmd)) {\n> +\t\t\tgit_config_push_env(cmd);\n>  \t\t} else if (!strcmp(cmd, \"--literal-pathspecs\")) {\n>  \t\t\tsetenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, \"1\", 1);\n>  \t\t\tif (envchanged)\n"},{"id":"411044","messageId":"cover.1606816110.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1606214397.git.ps@pks.im","subject":"[PATCH v3 0/4] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-01T09:55:52Z","receivedAt":"2020-12-01T09:56:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the third version of my patch series which aims to implement a\nway to pass config entries via the enviroment while avoiding any\nrequirements to perform shell quoting on the user's side.\n\nMajor changes include:\n\n- Another test case to test interaction of GIT_CONFIG_PARAMETERS.\n\n- I've exposed `getenv_safe` and now use that to retrieve envvars to\n  avoid platform-specific lifetime issues of returned envvar values.\n\n- I've split out a patch which performs reindentation of preexisting\n  code to make the actual code change easier to review.\n\nStill missing is the `--config-env` way of doing things. I'd be happy to\nlive with both ways of doing things and adopt it as part of this series,\nbut I wasn't sure whether this would be welcome or not. Too many ways to\ndo the same thing may be confusing in the end, even though their target\naudience is probably different.\n\nPatrick\n\nPatrick Steinhardt (4):\n  environment: make `getenv_safe()` non-static\n  config: extract function to parse config pairs\n  config: refactor parsing of GIT_CONFIG_PARAMETERS\n  config: allow specifying config entries via envvar pairs\n\n Documentation/git-config.txt |  12 ++++\n cache.h                      |   1 +\n config.c                     |  99 +++++++++++++++++++++++-------\n environment.c                |   8 +--\n environment.h                |  12 ++++\n t/t1300-config.sh            | 115 ++++++++++++++++++++++++++++++++++-\n 6 files changed, 220 insertions(+), 27 deletions(-)\n create mode 100644 environment.h\n\n-- \n2.29.2\n\n"},{"id":"411045","messageId":"87653893b7c80cde158dfb39acb54aa91d4e54f4.1606816110.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1606816110.git.ps@pks.im","subject":"[PATCH v3 1/4] environment: make `getenv_safe()` non-static","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-01T09:55:56Z","receivedAt":"2020-12-01T09:56:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `getenv_safe()` helper function helps to safely retrieve multiple\nenvironment values without the need to depend on platform-specific\nbehaviour for the return value's lifetime. We'll make use of this\nfunction in a following patch, so let's make it available by making it\nnon-static and adding a declaration.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n environment.c |  7 ++-----\n environment.h | 12 ++++++++++++\n 2 files changed, 14 insertions(+), 5 deletions(-)\n create mode 100644 environment.h\n\ndiff --git a/environment.c b/environment.c\nindex bb518c61cd..2234af462c 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -9,6 +9,7 @@\n  */\n #include \"cache.h\"\n #include \"branch.h\"\n+#include \"environment.h\"\n #include \"repository.h\"\n #include \"config.h\"\n #include \"refs.h\"\n@@ -152,11 +153,7 @@ static char *expand_namespace(const char *raw_namespace)\n \treturn strbuf_detach(&buf, NULL);\n }\n \n-/*\n- * Wrapper of getenv() that returns a strdup value. This value is kept\n- * in argv to be freed later.\n- */\n-static const char *getenv_safe(struct strvec *argv, const char *name)\n+const char *getenv_safe(struct strvec *argv, const char *name)\n {\n \tconst char *value = getenv(name);\n \ndiff --git a/environment.h b/environment.h\nnew file mode 100644\nindex 0000000000..d438b5c8f3\n--- /dev/null\n+++ b/environment.h\n@@ -0,0 +1,12 @@\n+#ifndef ENVIRONMENT_H\n+#define ENVIRONMENT_H\n+\n+#include \"strvec.h\"\n+\n+/*\n+ * Wrapper of getenv() that returns a strdup value. This value is kept\n+ * in argv to be freed later.\n+ */\n+const char *getenv_safe(struct strvec *argv, const char *name);\n+\n+#endif\n-- \n2.29.2\n\n"},{"id":"411046","messageId":"357084c9dc545331f2b8d2e25bb1d2b104c8c4f4.1606816110.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1606816110.git.ps@pks.im","subject":"[PATCH v3 3/4] config: refactor parsing of GIT_CONFIG_PARAMETERS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-01T09:56:05Z","receivedAt":"2020-12-01T09:57:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're about to introduce a new way of passing parameters via environment\nvariables to git, which will require us to change the way we parse\nconfig entries from parameters. Currently, `git_config_from_parameters`\nis written in a way which makes it rather hard to extend.\n\nRefactor the function to make it ready for the new logic as a\npreparatory step in order to avoid reindenting code and adding new logic\nin the same step, which would be much harder to reason about. This\nrefactoring is not expected to change any behaviour.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.c | 31 ++++++++++++++++---------------\n 1 file changed, 16 insertions(+), 15 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 4ae3711d3d..ba67706854 100644\n--- a/config.c\n+++ b/config.c\n@@ -484,35 +484,36 @@ int git_config_parse_parameter(const char *text,\n \n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n-\tconst char *env = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tconst char *env;\n \tint ret = 0;\n-\tchar *envw;\n+\tchar *envw = NULL;\n \tconst char **argv = NULL;\n-\tint nr = 0, alloc = 0;\n \tint i;\n \tstruct config_source source;\n \n-\tif (!env)\n-\t\treturn 0;\n-\n \tmemset(&source, 0, sizeof(source));\n \tsource.prev = cf;\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n \tcf = &source;\n \n-\t/* sq_dequote will write over it */\n-\tenvw = xstrdup(env);\n+\tenv = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tif (env) {\n+\t\tint nr = 0, alloc = 0;\n \n-\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n-\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n-\t\tgoto out;\n-\t}\n+\t\t/* sq_dequote will write over it */\n+\t\tenvw = xstrdup(env);\n \n-\tfor (i = 0; i < nr; i++) {\n-\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n-\t\t\tret = -1;\n+\t\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n+\t\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n \t\t\tgoto out;\n \t\t}\n+\n+\t\tfor (i = 0; i < nr; i++) {\n+\t\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n \t}\n \n out:\n-- \n2.29.2\n\n"},{"id":"411047","messageId":"808c311925b650339b8a1494554df0b1a0bfa30f.1606816110.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1606816110.git.ps@pks.im","subject":"[PATCH v3 2/4] config: extract function to parse config pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-01T09:56:00Z","receivedAt":"2020-12-01T09:57:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `git_config_parse_parameter` is responsible for parsing a\n`foo.bar=baz`-formatted configuration key, sanitizing the key and then\nprocessing it via the given callback function. Given that we're about to\nadd a second user which is going to process keys in such which already\nhas keys and values separated, this commit extracts a function\n`config_parse_pair` which only does the sanitization and processing\npart.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.c | 24 +++++++++++++++++-------\n 1 file changed, 17 insertions(+), 7 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 8f324ed3a6..4ae3711d3d 100644\n--- a/config.c\n+++ b/config.c\n@@ -437,11 +437,26 @@ int git_config_key_is_valid(const char *key)\n \treturn !git_config_parse_key_1(key, NULL, NULL, 1);\n }\n \n+static int config_parse_pair(const char *key, const char *value,\n+\t\t\t  config_fn_t fn, void *data)\n+{\n+\tchar *canonical_name;\n+\tint ret;\n+\n+\tif (!strlen(key))\n+\t\treturn error(_(\"empty config key\"));\n+\tif (git_config_parse_key(key, &canonical_name, NULL))\n+\t\treturn -1;\n+\n+\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n+\tfree(canonical_name);\n+\treturn ret;\n+}\n+\n int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n \tconst char *value;\n-\tchar *canonical_name;\n \tstruct strbuf **pair;\n \tint ret;\n \n@@ -462,12 +477,7 @@ int git_config_parse_parameter(const char *text,\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \t}\n \n-\tif (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {\n-\t\tret = -1;\n-\t} else {\n-\t\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n-\t\tfree(canonical_name);\n-\t}\n+\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n \tstrbuf_list_free(pair);\n \treturn ret;\n }\n-- \n2.29.2\n\n"},{"id":"411048","messageId":"4aadaeba993a746341d8fdf4028611826c0963e8.1606816110.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1606816110.git.ps@pks.im","subject":"[PATCH v3 4/4] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-01T09:56:10Z","receivedAt":"2020-12-01T09:57:42Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While we currently have the `GIT_CONFIG_PARAMETERS` environment variable\nwhich can be used to pass runtime configuration data to git processes,\nit's an internal implementation detail and not supposed to be used by\nend users.\n\nNext to being for internal use only, this way of passing config entries\nhas a major downside: the config keys need to be parsed as they contain\nboth key and value in a single variable. As such, it is left to the user\nto escape any potentially harmful characters in the value, which is\nquite hard to do if values are controlled by a third party.\n\nThis commit thus adds a new way of adding config entries via the\nenvironment which gets rid of this shortcoming. If the user passes the\n`GIT_CONFIG_COUNT=$n` environment variable, Git will parse environment\nvariable pairs `GIT_CONFIG_KEY_$i` and `GIT_CONFIG_VALUE_$i` for each\n`i` in `[0,n)`.\n\nWhile the same can be achieved with `git -c <name>=<value>`, one may\nwish to not do so for potentially sensitive information. E.g. if one\nwants to set `http.extraHeader` to contain an authentication token,\ndoing so via `-c` would trivially leak those credentials via e.g. ps(1),\nwhich typically also shows command arguments.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-config.txt |  12 ++++\n cache.h                      |   1 +\n config.c                     |  46 ++++++++++++++\n environment.c                |   1 +\n t/t1300-config.sh            | 115 ++++++++++++++++++++++++++++++++++-\n 5 files changed, 174 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-config.txt b/Documentation/git-config.txt\nindex 7573160f21..073fb5229a 100644\n--- a/Documentation/git-config.txt\n+++ b/Documentation/git-config.txt\n@@ -337,6 +337,18 @@ GIT_CONFIG_NOSYSTEM::\n \n See also <<FILES>>.\n \n+GIT_CONFIG_COUNT::\n+GIT_CONFIG_KEY_<n>::\n+GIT_CONFIG_VALUE_<n>::\n+\tIf GIT_CONFIG_COUNT is set to a positive number, all environment pairs\n+\tGIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> up to that number will be\n+\tadded to the process's runtime configuration. The config pairs are\n+\tzero-indexed. Any missing key or value is treated as an error. An empty\n+\tGIT_CONFIG_COUNT is treated the same as GIT_CONFIG_COUNT=0, namely no\n+\tpairs are processed. These environment variables will override values\n+\tin configuration files, but will be overridden by any explicit options\n+\tpassed via `git -c`.\n+\n \n [[EXAMPLES]]\n EXAMPLES\ndiff --git a/cache.h b/cache.h\nindex c0072d43b1..8a36146337 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -472,6 +472,7 @@ static inline enum object_type object_type(unsigned int mode)\n #define TEMPLATE_DIR_ENVIRONMENT \"GIT_TEMPLATE_DIR\"\n #define CONFIG_ENVIRONMENT \"GIT_CONFIG\"\n #define CONFIG_DATA_ENVIRONMENT \"GIT_CONFIG_PARAMETERS\"\n+#define CONFIG_COUNT_ENVIRONMENT \"GIT_CONFIG_COUNT\"\n #define EXEC_PATH_ENVIRONMENT \"GIT_EXEC_PATH\"\n #define CEILING_DIRECTORIES_ENVIRONMENT \"GIT_CEILING_DIRECTORIES\"\n #define NO_REPLACE_OBJECTS_ENVIRONMENT \"GIT_NO_REPLACE_OBJECTS\"\ndiff --git a/config.c b/config.c\nindex ba67706854..0b4d0b45d1 100644\n--- a/config.c\n+++ b/config.c\n@@ -8,6 +8,7 @@\n #include \"cache.h\"\n #include \"branch.h\"\n #include \"config.h\"\n+#include \"environment.h\"\n #include \"repository.h\"\n #include \"lockfile.h\"\n #include \"exec-cmd.h\"\n@@ -485,6 +486,8 @@ int git_config_parse_parameter(const char *text,\n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n \tconst char *env;\n+\tstruct strbuf envvar = STRBUF_INIT;\n+\tstruct strvec to_free = STRVEC_INIT;\n \tint ret = 0;\n \tchar *envw = NULL;\n \tconst char **argv = NULL;\n@@ -496,6 +499,47 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n \tcf = &source;\n \n+\tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n+\tif (env) {\n+\t\tunsigned long count;\n+\t\tchar *endp;\n+\n+\t\tcount = strtoul(env, &endp, 10);\n+\t\tif (*endp) {\n+\t\t\tret = error(_(\"bogus count in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (count > INT_MAX) {\n+\t\t\tret = error(_(\"too many entries in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (i = 0; i < count; i++) {\n+\t\t\tconst char *key, *value;\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_KEY_%d\", i);\n+\t\t\tkey = getenv_safe(&to_free, envvar.buf);\n+\t\t\tif (!key) {\n+\t\t\t\tret = error(_(\"missing config key %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_VALUE_%d\", i);\n+\t\t\tvalue = getenv_safe(&to_free, envvar.buf);\n+\t\t\tif (!value) {\n+\t\t\t\tret = error(_(\"missing config value %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \tenv = getenv(CONFIG_DATA_ENVIRONMENT);\n \tif (env) {\n \t\tint nr = 0, alloc = 0;\n@@ -517,6 +561,8 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \t}\n \n out:\n+\tstrbuf_release(&envvar);\n+\tstrvec_clear(&to_free);\n \tfree(argv);\n \tfree(envw);\n \tcf = source.prev;\ndiff --git a/environment.c b/environment.c\nindex 2234af462c..2f27008424 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -117,6 +117,7 @@ const char * const local_repo_env[] = {\n \tALTERNATE_DB_ENVIRONMENT,\n \tCONFIG_ENVIRONMENT,\n \tCONFIG_DATA_ENVIRONMENT,\n+\tCONFIG_COUNT_ENVIRONMENT,\n \tDB_ENVIRONMENT,\n \tGIT_DIR_ENVIRONMENT,\n \tGIT_WORK_TREE_ENVIRONMENT,\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 825d9a184f..756536067b 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1316,6 +1316,117 @@ test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n \t\tgit config --get-regexp \"env.*\"\n '\n \n+test_expect_success 'git config handles environment config pairs' '\n+\tGIT_CONFIG_COUNT=2 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"foo\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"bar\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one foo\n+\tpair.two bar\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs without count' '\n+\ttest_must_fail env GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one 2>error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one\n+'\n+\n+test_expect_success 'git config ignores pairs exceeding count' '\n+\tGIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"value\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with empty count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT= GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config fails with invalid count' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=10a git config --list 2>error &&\n+\ttest_i18ngrep \"bogus count\" error &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=9999999999999999 git config --list 2>error &&\n+\ttest_i18ngrep \"too many entries\" error\n+'\n+\n+test_expect_success 'git config fails with missing config key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config key\" error\n+'\n+\n+test_expect_success 'git config fails with missing config value' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=\"pair.one\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config value\" error\n+'\n+\n+test_expect_success 'git config fails with invalid config pair key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0= GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=missing-section GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list\n+'\n+\n+test_expect_success 'environment overrides config file' '\n+\ttest_when_finished \"rm -f .git/config\" &&\n+\tcat >.git/config <<-EOF &&\n+\t[pair]\n+\tone = value\n+\tEOF\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=override \\\n+\t\tgit config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'GIT_CONFIG_PARAMETERS overrides environment config' '\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n+\t\tGIT_CONFIG_PARAMETERS=\"${SQ}pair.one=override${SQ}\" \\\n+\t\tgit config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'command line overrides environment config' '\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n+\t\tgit -c pair.one=override config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n@@ -1661,9 +1772,11 @@ test_expect_success '--show-origin with --list' '\n \tfile:.git/config\tuser.override=local\n \tfile:.git/config\tinclude.path=../include/relative.include\n \tfile:.git/../include/relative.include\tuser.relative=include\n+\tcommand line:\tuser.environ=true\n \tcommand line:\tuser.cmdline=true\n \tEOF\n-\tgit -c user.cmdline=true config --list --show-origin >output &&\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=user.environ GIT_CONFIG_VALUE_0=true\\\n+\t\tgit -c user.cmdline=true config --list --show-origin >output &&\n \ttest_cmp expect output\n '\n \n-- \n2.29.2\n\n"},{"id":"411057","messageId":"X8YpRyOcPTOztZ8r@coredump.intra.peff.net","threadId":"54704","inReplyTo":"X8YRTPPfkRqo23ll@ncase","subject":"Re: [PATCH v2 0/2] config: allow specifying config entries via envvar pairs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-01T11:30:15Z","receivedAt":"2020-12-01T11:30:59Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Dec 01, 2020 at 10:47:56AM +0100, Patrick Steinhardt wrote:\n\n> > +void git_config_push_env(const char *spec)\n> > +{\n> > +\tstruct strbuf buf = STRBUF_INIT;\n> > +\tconst char *env_name;\n> > +\tconst char *env_value;\n> > +\n> > +\tenv_name = strchr(spec, '=');\n> > +\tif (!env_name)\n> > +\t\treturn; /* die or warn? */\n> > +\tenv_name++;\n> > +\n> > +\tenv_value = getenv(env_name);\n> > +\tif (!env_value)\n> > +\t\treturn; /* die or warn? */\n> > +\n> > +\tstrbuf_add(&buf, spec, env_name - spec);\n> > +\tstrbuf_addstr(&buf, env_value);\n> > +\tgit_config_push_parameter(buf.buf);\n> > +\tstrbuf_release(&buf);\n> > +}\n> \n> I realize that you say it's yet unpolished, but doesn't this have\n> parsing issues? The first strchr(3P) probably needs to be a strrchr(3P)\n> to correctly parse `includeIf./home/foo/=repo.path=MY_PATH_ENV`.\n\nWithout further changes to $GIT_CONFIG_PARAMETERS, there'd be little\npoint. The value we put in there has the same parsing issue when read\nout of the environment (which we resolve by disallowing \"=\" in the\nsubsection, just as here).\n\nI don't think it's actually that big of a deal in practice (it _could_\nbe an injection source, but it seems somewhat implausible that somebody\nis generating complex config keys based on untrusted input). But if we\ncare, then we could pretty easily change the reading side to separately\nquote the key/value in this case:\n\n  'foo.subsection=with=equals.bar'='value=with=equals'\n\nAnd then doing strrchr() would make sense, with the explicitly\ndocumented rule that the environment variable name cannot contain an\nequals sign. (Doing a raw \"git -c\" wouldn't work unless we introduce\nanother option that lets you specify the key and value separately; that\nmight be worthwhile, too).\n\n> But we'd also have to handle shell quoting for the user, don't we?\n\nI'm not sure exactly what you mean here. We wouldn't typically see any\nshell quoting from the user, since the shell would dequote it and give\nus a NUL-terminated argv. Or if you meant we'd have to adjust the shell\nquoting in $GIT_CONFIG_PARAMETERS, then see above.\n\n-Peff\n"},{"id":"411839","messageId":"cover.1607514692.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1606214397.git.ps@pks.im","subject":"[PATCH v4 0/6] config: allow specifying config entries via env","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-09T11:52:16Z","receivedAt":"2020-12-09T11:53:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the fourth version of my patch series which aims to implement a\nway to pass config entries via the environment while avoiding any\nrequirements to perform shell quoting on the user's side.\n\nGiven that the What's Cooking report notes that my third version is\nabout to be dropped dropped because the `--config-env` way of doing\nthings is preferred, I've now adopted that approach. I've taken the\npatch which Peff posted originally (with one change strchr->strrchr) and\nadded documentation and tests to it.\n\nThis patch series still includes my old proposal as it would actually be\na better fit for our usecase at GitLab I have in mind, which is to put\nall configuration which applies to all git commands into the commands\ninstead of using a config file for this. I have structured the series in\nsuch a way though that those patches come last -- so if you continue to\nthink this approach shouldn't make it in, please feel free to drop\npatches 3-6.\n\nPatrick\n\nPatrick Steinhardt (6):\n  git: add `--super-prefix` to usage string\n  config: add new way to pass config via `--config-env`\n  environment: make `getenv_safe()` non-static\n  config: extract function to parse config pairs\n  config: refactor parsing of GIT_CONFIG_PARAMETERS\n  config: allow specifying config entries via envvar pairs\n\n Documentation/git-config.txt |  12 +++\n Documentation/git.txt        |  11 ++-\n cache.h                      |   1 +\n config.c                     | 120 +++++++++++++++++++++-----\n config.h                     |   1 +\n environment.c                |   8 +-\n environment.h                |  12 +++\n git.c                        |   3 +\n t/t1300-config.sh            | 160 ++++++++++++++++++++++++++++++++++-\n 9 files changed, 300 insertions(+), 28 deletions(-)\n create mode 100644 environment.h\n\n-- \n2.29.2\n\n"},{"id":"411840","messageId":"20ed9aff3be43e2c86d45c1a0255ea287ed1198d.1607514692.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1607514692.git.ps@pks.im","subject":"[PATCH v4 1/6] git: add `--super-prefix` to usage string","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-09T11:52:21Z","receivedAt":"2020-12-09T11:53:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When the `--super-prefix` option was implmented in 74866d7579 (git: make\nsuper-prefix option, 2016-10-07), its existence was only documented in\nthe manpage but not in the command's own usage string. Given that the\ncommit message didn't mention that this was done intentionally and given\nthat it's documented in the manpage, this seems like an oversight.\n\nAdd it to the usage string to fix the inconsistency.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n git.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/git.c b/git.c\nindex a00a0a4d94..5a8ff12f87 100644\n--- a/git.c\n+++ b/git.c\n@@ -29,6 +29,7 @@ const char git_usage_string[] =\n \t   \"           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\\n\"\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\\n\"\n \t   \"           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\\n\"\n+\t   \"           [--super-prefix=<path>]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n-- \n2.29.2\n\n"},{"id":"411841","messageId":"e6b110c3e3a0ec27802c4315c2431fa24f404551.1607514692.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1607514692.git.ps@pks.im","subject":"[PATCH v4 3/6] environment: make `getenv_safe()` non-static","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-09T11:52:30Z","receivedAt":"2020-12-09T11:53:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `getenv_safe()` helper function helps to safely retrieve multiple\nenvironment values without the need to depend on platform-specific\nbehaviour for the return value's lifetime. We'll make use of this\nfunction in a following patch, so let's make it available by making it\nnon-static and adding a declaration.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n environment.c |  7 ++-----\n environment.h | 12 ++++++++++++\n 2 files changed, 14 insertions(+), 5 deletions(-)\n create mode 100644 environment.h\n\ndiff --git a/environment.c b/environment.c\nindex bb518c61cd..2234af462c 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -9,6 +9,7 @@\n  */\n #include \"cache.h\"\n #include \"branch.h\"\n+#include \"environment.h\"\n #include \"repository.h\"\n #include \"config.h\"\n #include \"refs.h\"\n@@ -152,11 +153,7 @@ static char *expand_namespace(const char *raw_namespace)\n \treturn strbuf_detach(&buf, NULL);\n }\n \n-/*\n- * Wrapper of getenv() that returns a strdup value. This value is kept\n- * in argv to be freed later.\n- */\n-static const char *getenv_safe(struct strvec *argv, const char *name)\n+const char *getenv_safe(struct strvec *argv, const char *name)\n {\n \tconst char *value = getenv(name);\n \ndiff --git a/environment.h b/environment.h\nnew file mode 100644\nindex 0000000000..d438b5c8f3\n--- /dev/null\n+++ b/environment.h\n@@ -0,0 +1,12 @@\n+#ifndef ENVIRONMENT_H\n+#define ENVIRONMENT_H\n+\n+#include \"strvec.h\"\n+\n+/*\n+ * Wrapper of getenv() that returns a strdup value. This value is kept\n+ * in argv to be freed later.\n+ */\n+const char *getenv_safe(struct strvec *argv, const char *name);\n+\n+#endif\n-- \n2.29.2\n\n"},{"id":"411842","messageId":"766ffe31a6f14c55d1b58a8f53edbb7f731b1b24.1607514692.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1607514692.git.ps@pks.im","subject":"[PATCH v4 2/6] config: add new way to pass config via `--config-env`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-09T11:52:26Z","receivedAt":"2020-12-09T11:53:54Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While it's already possible to pass runtime configuration via `git -c\n<key>=<value>`, it may be undesirable to use when the value contains\nsensitive information. E.g. if one wants to set `http.extraHeader` to\ncontain an authentication token, doing so via `-c` would trivially leak\nthose credentials via e.g. ps(1), which typically also shows command\narguments.\n\nTo enable this usecase without leaking credentials, this commit\nintroduces a new switch `--config-env=<key>=<envvar>`. Instead of\ndirectly passing a value for the given key, it instead allows the user\nto specify the name of an environment variable. The value of that\nvariable will then be used as value of the key.\n\nCo-authored-by: Jeff King <peff@peff.net>\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git.txt | 11 ++++++++++-\n config.c              | 21 ++++++++++++++++++++\n config.h              |  1 +\n git.c                 |  4 +++-\n t/t1300-config.sh     | 45 +++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 80 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex c463b937a8..9061c54792 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n     [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n     [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]\n     [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n-    [--super-prefix=<path>]\n+    [--super-prefix=<path>] [--config-env <name>=<envvar>]\n     <command> [<args>]\n \n DESCRIPTION\n@@ -80,6 +80,15 @@ config file). Including the equals but with an empty value (like `git -c\n foo.bar= ...`) sets `foo.bar` to the empty string which `git config\n --type=bool` will convert to `false`.\n \n+--config-env=<name>=<envvar>::\n+\tPass a configuration parameter to the command. The <envvar>\n+\tgiven will be replaced with the contents of the environment\n+\tvariable of that name. In contrast to `-c`, an envvar must\n+\talways be given and exist in the environment. Passing an\n+\tenvironment variable with empty value will set <name> to the\n+\tempty string which `git config --type=bool` will convert to\n+\t`false`.\n+\n --exec-path[=<path>]::\n \tPath to wherever your core Git programs are installed.\n \tThis can also be controlled by setting the GIT_EXEC_PATH\ndiff --git a/config.c b/config.c\nindex 1137bd73af..cde3511110 100644\n--- a/config.c\n+++ b/config.c\n@@ -345,6 +345,27 @@ void git_config_push_parameter(const char *text)\n \tstrbuf_release(&env);\n }\n \n+void git_config_push_env(const char *spec)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *env_name;\n+\tconst char *env_value;\n+\n+\tenv_name = strrchr(spec, '=');\n+\tif (!env_name)\n+\t\tdie(\"invalid config format: %s\", spec);\n+\tenv_name++;\n+\n+\tenv_value = getenv(env_name);\n+\tif (!env_value)\n+\t\tdie(\"config variable missing for '%s'\", env_name);\n+\n+\tstrbuf_add(&buf, spec, env_name - spec);\n+\tstrbuf_addstr(&buf, env_value);\n+\tgit_config_push_parameter(buf.buf);\n+\tstrbuf_release(&buf);\n+}\n+\n static inline int iskeychar(int c)\n {\n \treturn isalnum(c) || c == '-';\ndiff --git a/config.h b/config.h\nindex c1449bb790..19a9adbaa9 100644\n--- a/config.h\n+++ b/config.h\n@@ -138,6 +138,7 @@ int git_config_from_mem(config_fn_t fn,\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     const struct object_id *oid, void *data);\n void git_config_push_parameter(const char *text);\n+void git_config_push_env(const char *spec);\n int git_config_from_parameters(config_fn_t fn, void *data);\n void read_early_config(config_fn_t cb, void *data);\n void read_very_early_config(config_fn_t cb, void *data);\ndiff --git a/git.c b/git.c\nindex 5a8ff12f87..b5f63d346b 100644\n--- a/git.c\n+++ b/git.c\n@@ -29,7 +29,7 @@ const char git_usage_string[] =\n \t   \"           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\\n\"\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\\n\"\n \t   \"           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\\n\"\n-\t   \"           [--super-prefix=<path>]\\n\"\n+\t   \"           [--super-prefix=<path>] [--config-env=<name>=<envvar>]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n@@ -255,6 +255,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tgit_config_push_parameter((*argv)[1]);\n \t\t\t(*argv)++;\n \t\t\t(*argc)--;\n+\t\t} else if (skip_prefix(cmd, \"--config-env=\", &cmd)) {\n+\t\t\tgit_config_push_env(cmd);\n \t\t} else if (!strcmp(cmd, \"--literal-pathspecs\")) {\n \t\t\tsetenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, \"1\", 1);\n \t\t\tif (envchanged)\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 97a04c6cc2..46a94814d5 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1316,6 +1316,51 @@ test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n \t\tgit config --get-regexp \"env.*\"\n '\n \n+test_expect_success 'git --config-env=key=envvar support' '\n+\tcat >expect <<-\\EOF &&\n+\tvalue\n+\tvalue\n+\tfalse\n+\tEOF\n+\t{\n+\t\tenv ENVVAR=value git --config-env=core.name=ENVVAR config core.name &&\n+\t\tenv ENVVAR=value git --config-env=foo.CamelCase=ENVVAR config foo.camelcase &&\n+\t\tenv ENVVAR= git --config-env=foo.flag=ENVVAR config --bool foo.flag\n+\t} >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git --config-env fails with invalid parameters' '\n+\ttest_must_fail git --config-env=foo.flag config --bool foo.flag 2>error &&\n+\ttest_i18ngrep \"invalid config format\" error &&\n+\ttest_must_fail git --config-env=foo.flag=NONEXISTENT config --bool foo.flag 2>error &&\n+\ttest_i18ngrep \"config variable missing\" error\n+'\n+\n+test_expect_success 'git -c and --config-env work together' '\n+\tcat >expect <<-\\EOF &&\n+\tbar.cmd cmd-value\n+\tbar.env env-value\n+\tEOF\n+\tenv ENVVAR=env-value git \\\n+\t\t-c bar.cmd=cmd-value \\\n+\t\t--config-env=bar.env=ENVVAR \\\n+\t\tconfig --get-regexp \"^bar.*\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git -c and --config-env override each other' '\n+\tcat >expect <<-\\EOF &&\n+\tenv\n+\tcmd\n+\tEOF\n+\t{\n+\t\tenv ENVVAR=env git -c bar.bar=cmd --config-env=bar.bar=ENVVAR config bar.bar &&\n+\t\tenv ENVVAR=env git --config-env=bar.bar=ENVVAR -c bar.bar=cmd config bar.bar\n+\t} >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n-- \n2.29.2\n\n"},{"id":"411843","messageId":"63fb8ad99742d748dc00306be6d2e05bd0ed583d.1607514692.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1607514692.git.ps@pks.im","subject":"[PATCH v4 4/6] config: extract function to parse config pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-09T11:52:34Z","receivedAt":"2020-12-09T11:53:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `git_config_parse_parameter` is responsible for parsing a\n`foo.bar=baz`-formatted configuration key, sanitizing the key and then\nprocessing it via the given callback function. Given that we're about to\nadd a second user which is going to process keys in such which already\nhas keys and values separated, this commit extracts a function\n`config_parse_pair` which only does the sanitization and processing\npart.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.c | 24 +++++++++++++++++-------\n 1 file changed, 17 insertions(+), 7 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex cde3511110..151980e5c9 100644\n--- a/config.c\n+++ b/config.c\n@@ -458,11 +458,26 @@ int git_config_key_is_valid(const char *key)\n \treturn !git_config_parse_key_1(key, NULL, NULL, 1);\n }\n \n+static int config_parse_pair(const char *key, const char *value,\n+\t\t\t  config_fn_t fn, void *data)\n+{\n+\tchar *canonical_name;\n+\tint ret;\n+\n+\tif (!strlen(key))\n+\t\treturn error(_(\"empty config key\"));\n+\tif (git_config_parse_key(key, &canonical_name, NULL))\n+\t\treturn -1;\n+\n+\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n+\tfree(canonical_name);\n+\treturn ret;\n+}\n+\n int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n \tconst char *value;\n-\tchar *canonical_name;\n \tstruct strbuf **pair;\n \tint ret;\n \n@@ -483,12 +498,7 @@ int git_config_parse_parameter(const char *text,\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \t}\n \n-\tif (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {\n-\t\tret = -1;\n-\t} else {\n-\t\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n-\t\tfree(canonical_name);\n-\t}\n+\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n \tstrbuf_list_free(pair);\n \treturn ret;\n }\n-- \n2.29.2\n\n"},{"id":"411844","messageId":"659e20697fa29d996f54015852b3314c37d432e5.1607514692.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1607514692.git.ps@pks.im","subject":"[PATCH v4 6/6] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-09T11:52:43Z","receivedAt":"2020-12-09T11:54:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While we currently have the `GIT_CONFIG_PARAMETERS` environment variable\nwhich can be used to pass runtime configuration data to git processes,\nit's an internal implementation detail and not supposed to be used by\nend users.\n\nNext to being for internal use only, this way of passing config entries\nhas a major downside: the config keys need to be parsed as they contain\nboth key and value in a single variable. As such, it is left to the user\nto escape any potentially harmful characters in the value, which is\nquite hard to do if values are controlled by a third party.\n\nThis commit thus adds a new way of adding config entries via the\nenvironment which gets rid of this shortcoming. If the user passes the\n`GIT_CONFIG_COUNT=$n` environment variable, Git will parse environment\nvariable pairs `GIT_CONFIG_KEY_$i` and `GIT_CONFIG_VALUE_$i` for each\n`i` in `[0,n)`.\n\nWhile the same can be achieved with `git -c <name>=<value>`, one may\nwish to not do so for potentially sensitive information. E.g. if one\nwants to set `http.extraHeader` to contain an authentication token,\ndoing so via `-c` would trivially leak those credentials via e.g. ps(1),\nwhich typically also shows command arguments.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-config.txt |  12 ++++\n cache.h                      |   1 +\n config.c                     |  46 ++++++++++++++\n environment.c                |   1 +\n t/t1300-config.sh            | 115 ++++++++++++++++++++++++++++++++++-\n 5 files changed, 174 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-config.txt b/Documentation/git-config.txt\nindex 0e9351d3cb..54c994aea1 100644\n--- a/Documentation/git-config.txt\n+++ b/Documentation/git-config.txt\n@@ -346,6 +346,18 @@ GIT_CONFIG_NOSYSTEM::\n \n See also <<FILES>>.\n \n+GIT_CONFIG_COUNT::\n+GIT_CONFIG_KEY_<n>::\n+GIT_CONFIG_VALUE_<n>::\n+\tIf GIT_CONFIG_COUNT is set to a positive number, all environment pairs\n+\tGIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> up to that number will be\n+\tadded to the process's runtime configuration. The config pairs are\n+\tzero-indexed. Any missing key or value is treated as an error. An empty\n+\tGIT_CONFIG_COUNT is treated the same as GIT_CONFIG_COUNT=0, namely no\n+\tpairs are processed. These environment variables will override values\n+\tin configuration files, but will be overridden by any explicit options\n+\tpassed via `git -c`.\n+\n \n [[EXAMPLES]]\n EXAMPLES\ndiff --git a/cache.h b/cache.h\nindex 8d279bc110..294841fca7 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -472,6 +472,7 @@ static inline enum object_type object_type(unsigned int mode)\n #define TEMPLATE_DIR_ENVIRONMENT \"GIT_TEMPLATE_DIR\"\n #define CONFIG_ENVIRONMENT \"GIT_CONFIG\"\n #define CONFIG_DATA_ENVIRONMENT \"GIT_CONFIG_PARAMETERS\"\n+#define CONFIG_COUNT_ENVIRONMENT \"GIT_CONFIG_COUNT\"\n #define EXEC_PATH_ENVIRONMENT \"GIT_EXEC_PATH\"\n #define CEILING_DIRECTORIES_ENVIRONMENT \"GIT_CEILING_DIRECTORIES\"\n #define NO_REPLACE_OBJECTS_ENVIRONMENT \"GIT_NO_REPLACE_OBJECTS\"\ndiff --git a/config.c b/config.c\nindex 8162f3cec8..779487bc2d 100644\n--- a/config.c\n+++ b/config.c\n@@ -8,6 +8,7 @@\n #include \"cache.h\"\n #include \"branch.h\"\n #include \"config.h\"\n+#include \"environment.h\"\n #include \"repository.h\"\n #include \"lockfile.h\"\n #include \"exec-cmd.h\"\n@@ -506,6 +507,8 @@ int git_config_parse_parameter(const char *text,\n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n \tconst char *env;\n+\tstruct strbuf envvar = STRBUF_INIT;\n+\tstruct strvec to_free = STRVEC_INIT;\n \tint ret = 0;\n \tchar *envw = NULL;\n \tconst char **argv = NULL;\n@@ -517,6 +520,47 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n \tcf = &source;\n \n+\tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n+\tif (env) {\n+\t\tunsigned long count;\n+\t\tchar *endp;\n+\n+\t\tcount = strtoul(env, &endp, 10);\n+\t\tif (*endp) {\n+\t\t\tret = error(_(\"bogus count in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (count > INT_MAX) {\n+\t\t\tret = error(_(\"too many entries in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (i = 0; i < count; i++) {\n+\t\t\tconst char *key, *value;\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_KEY_%d\", i);\n+\t\t\tkey = getenv_safe(&to_free, envvar.buf);\n+\t\t\tif (!key) {\n+\t\t\t\tret = error(_(\"missing config key %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_VALUE_%d\", i);\n+\t\t\tvalue = getenv_safe(&to_free, envvar.buf);\n+\t\t\tif (!value) {\n+\t\t\t\tret = error(_(\"missing config value %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \tenv = getenv(CONFIG_DATA_ENVIRONMENT);\n \tif (env) {\n \t\tint nr = 0, alloc = 0;\n@@ -538,6 +582,8 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \t}\n \n out:\n+\tstrbuf_release(&envvar);\n+\tstrvec_clear(&to_free);\n \tfree(argv);\n \tfree(envw);\n \tcf = source.prev;\ndiff --git a/environment.c b/environment.c\nindex 2234af462c..2f27008424 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -117,6 +117,7 @@ const char * const local_repo_env[] = {\n \tALTERNATE_DB_ENVIRONMENT,\n \tCONFIG_ENVIRONMENT,\n \tCONFIG_DATA_ENVIRONMENT,\n+\tCONFIG_COUNT_ENVIRONMENT,\n \tDB_ENVIRONMENT,\n \tGIT_DIR_ENVIRONMENT,\n \tGIT_WORK_TREE_ENVIRONMENT,\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 46a94814d5..f157cd217e 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1361,6 +1361,117 @@ test_expect_success 'git -c and --config-env override each other' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'git config handles environment config pairs' '\n+\tGIT_CONFIG_COUNT=2 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"foo\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"bar\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one foo\n+\tpair.two bar\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs without count' '\n+\ttest_must_fail env GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one 2>error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one\n+'\n+\n+test_expect_success 'git config ignores pairs exceeding count' '\n+\tGIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"value\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with empty count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT= GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config fails with invalid count' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=10a git config --list 2>error &&\n+\ttest_i18ngrep \"bogus count\" error &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=9999999999999999 git config --list 2>error &&\n+\ttest_i18ngrep \"too many entries\" error\n+'\n+\n+test_expect_success 'git config fails with missing config key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config key\" error\n+'\n+\n+test_expect_success 'git config fails with missing config value' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=\"pair.one\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config value\" error\n+'\n+\n+test_expect_success 'git config fails with invalid config pair key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0= GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=missing-section GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list\n+'\n+\n+test_expect_success 'environment overrides config file' '\n+\ttest_when_finished \"rm -f .git/config\" &&\n+\tcat >.git/config <<-EOF &&\n+\t[pair]\n+\tone = value\n+\tEOF\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=override \\\n+\t\tgit config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'GIT_CONFIG_PARAMETERS overrides environment config' '\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n+\t\tGIT_CONFIG_PARAMETERS=\"${SQ}pair.one=override${SQ}\" \\\n+\t\tgit config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'command line overrides environment config' '\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n+\t\tgit -c pair.one=override config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n@@ -1706,9 +1817,11 @@ test_expect_success '--show-origin with --list' '\n \tfile:.git/config\tuser.override=local\n \tfile:.git/config\tinclude.path=../include/relative.include\n \tfile:.git/../include/relative.include\tuser.relative=include\n+\tcommand line:\tuser.environ=true\n \tcommand line:\tuser.cmdline=true\n \tEOF\n-\tgit -c user.cmdline=true config --list --show-origin >output &&\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=user.environ GIT_CONFIG_VALUE_0=true\\\n+\t\tgit -c user.cmdline=true config --list --show-origin >output &&\n \ttest_cmp expect output\n '\n \n-- \n2.29.2\n\n"},{"id":"411845","messageId":"1afda0a536bb431a4acc8ca312c2daf5ff26e5ef.1607514692.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1607514692.git.ps@pks.im","subject":"[PATCH v4 5/6] config: refactor parsing of GIT_CONFIG_PARAMETERS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-09T11:52:38Z","receivedAt":"2020-12-09T11:54:41Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're about to introduce a new way of passing parameters via environment\nvariables to git, which will require us to change the way we parse\nconfig entries from parameters. Currently, `git_config_from_parameters`\nis written in a way which makes it rather hard to extend.\n\nRefactor the function to make it ready for the new logic as a\npreparatory step in order to avoid reindenting code and adding new logic\nin the same step, which would be much harder to reason about. This\nrefactoring is not expected to change any behaviour.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.c | 31 ++++++++++++++++---------------\n 1 file changed, 16 insertions(+), 15 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 151980e5c9..8162f3cec8 100644\n--- a/config.c\n+++ b/config.c\n@@ -505,35 +505,36 @@ int git_config_parse_parameter(const char *text,\n \n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n-\tconst char *env = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tconst char *env;\n \tint ret = 0;\n-\tchar *envw;\n+\tchar *envw = NULL;\n \tconst char **argv = NULL;\n-\tint nr = 0, alloc = 0;\n \tint i;\n \tstruct config_source source;\n \n-\tif (!env)\n-\t\treturn 0;\n-\n \tmemset(&source, 0, sizeof(source));\n \tsource.prev = cf;\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n \tcf = &source;\n \n-\t/* sq_dequote will write over it */\n-\tenvw = xstrdup(env);\n+\tenv = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tif (env) {\n+\t\tint nr = 0, alloc = 0;\n \n-\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n-\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n-\t\tgoto out;\n-\t}\n+\t\t/* sq_dequote will write over it */\n+\t\tenvw = xstrdup(env);\n \n-\tfor (i = 0; i < nr; i++) {\n-\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n-\t\t\tret = -1;\n+\t\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n+\t\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n \t\t\tgoto out;\n \t\t}\n+\n+\t\tfor (i = 0; i < nr; i++) {\n+\t\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n \t}\n \n out:\n-- \n2.29.2\n\n"},{"id":"411846","messageId":"875z5bxgwu.fsf@evledraar.gmail.com","threadId":"54704","inReplyTo":"63fb8ad99742d748dc00306be6d2e05bd0ed583d.1607514692.git.ps@pks.im","subject":"Re: [PATCH v4 4/6] config: extract function to parse config pairs","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2020-12-09T13:12:01Z","receivedAt":"2020-12-09T13:12:47Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Dec 09 2020, Patrick Steinhardt wrote:\n\n> +static int config_parse_pair(const char *key, const char *value,\n> +\t\t\t  config_fn_t fn, void *data)\n> +{\n> +\tchar *canonical_name;\n> +\tint ret;\n> +\n> +\tif (!strlen(key))\n> +\t\treturn error(_(\"empty config key\"));\n\nWe just did this check (just before the context of the second hunk in\nthis patch) before calling this function:\n\n    if (!pair[0]->len) {\n        strbuf_list_free(pair);\n        return error(_(\"bogus config parameter: %s\"), text);\n\nI think just removing this is best, for such a closely coupled static\nfunction we can just rely on the sanity of the caller, but it should at\nleast be:\n\n    if (!strlen(key))\n        BUG(\"clear that it's unreachable, and a translator won't bother with it, like _()...\");\n\nAside: It's more C-idiom-y in this case to write:\n\n    if (!*key)\n\n\n> +\tif (git_config_parse_key(key, &canonical_name, NULL))\n> +\t\treturn -1;\n> +\n> +\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n> +\tfree(canonical_name);\n> +\treturn ret;\n> +}\n> +\n>  int git_config_parse_parameter(const char *text,\n>  \t\t\t       config_fn_t fn, void *data)\n>  {\n>  \tconst char *value;\n> -\tchar *canonical_name;\n>  \tstruct strbuf **pair;\n>  \tint ret;\n>  \n> @@ -483,12 +498,7 @@ int git_config_parse_parameter(const char *text,\n>  \t\treturn error(_(\"bogus config parameter: %s\"), text);\n>  \t}\n>  \n> -\tif (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {\n> -\t\tret = -1;\n> -\t} else {\n> -\t\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n> -\t\tfree(canonical_name);\n> -\t}\n> +\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n>  \tstrbuf_list_free(pair);\n>  \treturn ret;\n>  }\n\n"},{"id":"411869","messageId":"871rfzxctq.fsf@evledraar.gmail.com","threadId":"54704","inReplyTo":"766ffe31a6f14c55d1b58a8f53edbb7f731b1b24.1607514692.git.ps@pks.im","subject":"Re: [PATCH v4 2/6] config: add new way to pass config via `--config-env`","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2020-12-09T14:40:17Z","receivedAt":"2020-12-09T14:41:01Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Dec 09 2020, Patrick Steinhardt wrote:\n\n> While it's already possible to pass runtime configuration via `git -c\n> <key>=<value>`, it may be undesirable to use when the value contains\n> sensitive information. E.g. if one wants to set `http.extraHeader` to\n> contain an authentication token, doing so via `-c` would trivially leak\n> those credentials via e.g. ps(1), which typically also shows command\n> arguments.\n>\n> To enable this usecase without leaking credentials, this commit\n> introduces a new switch `--config-env=<key>=<envvar>`. Instead of\n> directly passing a value for the given key, it instead allows the user\n> to specify the name of an environment variable. The value of that\n> variable will then be used as value of the key.\n> [...]\n> +--config-env=<name>=<envvar>::\n> +\tPass a configuration parameter to the command. The <envvar>\n> +\tgiven will be replaced with the contents of the environment\n> +\tvariable of that name. In contrast to `-c`, an envvar must\n> +\talways be given and exist in the environment. Passing an\n> +\tenvironment variable with empty value will set <name> to the\n> +\tempty string which `git config --type=bool` will convert to\n> +\t`false`.\n\nOkey, because \"-c foo.bar\" (true) \"-c foo.bar=\" is the empty string, but\nthat doesn't make sene with \"--config-env\". Also the whole part about\n--type=bool is just confusing, because it's referring to `-c`'s magic\nbehavior when it comes to `bool` which we don't have here.\n\nI think it's also worth describing what this is for & what the\nlimitations are. Maybe:\n\n    --config-env=<name>=<envvar>\n\n        Like `-c <name>=<var>` except the value is the name of an\n        environment variable from which to retrieve the value. Unlike\n        `-c` there is no shortcut for directly setting the value to an\n        empty string, instead the environment variable itself must be\n        set to the empty strin. Errors if the `<envvar>` does not exist\n        in the environment.\n\n        This is useful for cases where you want to pass transitory\n        configuration options to git, but are doing so on OS's where\n        other processes might be able to read your cmdline\n        (e.g. `/proc/self/cmdline`), but not your environ\n        (e.g. `/proc/self/environ`). That behavior is the default on\n        Linux, but may not be on your system.\n\n\tNote that this might add security for variables such as\n\t`http.extraHeader` where the sensitive information is part of\n\tthe value, but not e.g. `url.<base.insteadOf` where the\n\tsensitive information can be part of the key.\n\n> +void git_config_push_env(const char *spec)\n> +{\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\tconst char *env_name;\n> +\tconst char *env_value;\n> +\n> +\tenv_name = strrchr(spec, '=');\n> +\tif (!env_name)\n> +\t\tdie(\"invalid config format: %s\", spec);\n> +\tenv_name++;\n\nNot something new, and maybe not something for this series, but I wish\n-c and --config-env would document this limitation that we support \"=\"\nin keys in config, but not via those parameters.\n"},{"id":"411872","messageId":"87y2i7vvz4.fsf@evledraar.gmail.com","threadId":"54704","inReplyTo":"cover.1607514692.git.ps@pks.im","subject":"Re: [PATCH v4 0/6] config: allow specifying config entries via env","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2020-12-09T15:29:35Z","receivedAt":"2020-12-09T15:30:20Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Dec 09 2020, Patrick Steinhardt wrote:\n\n> this is the fourth version of my patch series which aims to implement a\n> way to pass config entries via the environment while avoiding any\n> requirements to perform shell quoting on the user's side.\n>\n> Given that the What's Cooking report notes that my third version is\n> about to be dropped dropped because the `--config-env` way of doing\n> things is preferred, I've now adopted that approach. I've taken the\n> patch which Peff posted originally (with one change strchr->strrchr) and\n> added documentation and tests to it.\n>\n> This patch series still includes my old proposal as it would actually be\n> a better fit for our usecase at GitLab I have in mind, which is to put\n> all configuration which applies to all git commands into the commands\n> instead of using a config file for this. I have structured the series in\n> such a way though that those patches come last -- so if you continue to\n> think this approach shouldn't make it in, please feel free to drop\n> patches 3-6.\n\nTo add even more to your headaches (sorry!) I hadn't really fully looked\nat that --config-env proposal.\n\nAs noted in my per-patch reply in [1] it will still expose the key part\nof the key=value, and in at least one place (url.<base>.insteadOf) the\nkey is where we'll pass the user/password on the command-line still with\nthat.\n\nI'd much prefer either your 6/6 over --config-env for that reason & that\n--config-env makes it impossible to pass a key with \"=\" in. For \"-c\" I\ndon't think that's much of an issue, but e.g. with\n\"url.<base>.insteadOf\" needing to take arbitrary passwords + us\nimplicitly/explicitly advertising this as a \"here's how you can pass the\npassword\" feature not being able to have \"=\" is more painful.\n\nI mildly prefer Jeff's suggestion of just getting GIT_CONFIG_PARAMETERS\nto the point where we could document it [2][3] to both of those, but\nthat's mostly an asthetic concern of dealing with N values. It won't\nmatter for the security aspect (but I think you (but haven't tested)\nthat you still can't pass a \"=\", but your 6/6 does allow that).\n\nI still can't quite shake the bad spidey-sense feeling that any of these\nare bad in some way we haven't thought of, just from the perspective\nthat no other tool I can think of that accepts a password has this\nmechanism for passing in a user/password or other sensitive data.\n\nE.g. openssh explicitly has refused to add anything of the sort (a\n--password parameter, but maybe they didn't consider\n--password=ENV_VAR). E.g. curl has a mode where you can have a password\non the command-line, but they then make you use -netrc-file to grab it\nfrom a file. From searching around I see concerns about shell histories\nbeing part of the security model, maybe that's why it's not a common\npattern.\n\nSo I still wonder if some version of what I tried with /dev/fd/321 in\n[4] would be best, i.e. something that combines transitory+no extra\ncommand invocation+not adding things to shell history. We support that\npattern in general, just not in fetch.c/remote.c for no particular good\nreason AFAICT.\n\nI do that that whatever we go for this series would be much better if\nthe commit messages / added docs explained why we're doing particular\nthings, and to users why they'd use one method but not the other.\n\nE.g. IIRC this whole series is because it's a hassle to invoke\ncore.askpass in some stateful program where you'd like to just provide a\ntransitory password. I think some brief cross-linking or explanation\nsomewhere of these various ways to pass sensitive values around would be\nrelly helpful.\n\n1. https://lore.kernel.org/git/871rfzxctq.fsf@evledraar.gmail.com/\n2. https://lore.kernel.org/git/20201117023454.GA34754@coredump.intra.peff.net/\n3. https://lore.kernel.org/git/20201118015907.GD650959@coredump.intra.peff.net/\n4. https://lore.kernel.org/git/87k0upflk4.fsf@evledraar.gmail.com/\n\n"},{"id":"411880","messageId":"X9D23LQv34A5Q5DC@coredump.intra.peff.net","threadId":"54704","inReplyTo":"766ffe31a6f14c55d1b58a8f53edbb7f731b1b24.1607514692.git.ps@pks.im","subject":"Re: [PATCH v4 2/6] config: add new way to pass config via `--config-env`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-09T16:10:04Z","receivedAt":"2020-12-09T16:11:12Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Dec 09, 2020 at 12:52:26PM +0100, Patrick Steinhardt wrote:\n\n> Co-authored-by: Jeff King <peff@peff.net>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n\nIn case we want it, this is also:\n\n  Signed-off-by: Jeff King <peff@peff.net>\n\n> +--config-env=<name>=<envvar>::\n> +\tPass a configuration parameter to the command. The <envvar>\n> +\tgiven will be replaced with the contents of the environment\n> +\tvariable of that name. In contrast to `-c`, an envvar must\n> +\talways be given and exist in the environment. Passing an\n> +\tenvironment variable with empty value will set <name> to the\n> +\tempty string which `git config --type=bool` will convert to\n> +\t`false`.\n\nI agree with Ævar that we probably should keep an empty variable as the\nempty string. I think some options use an empty string to clear a list\n(e.g., push.pushOption), and I'm not sure how they'd react to a bool\ninstead. It would be nice to also have a way to do the implicit-bool\nthing, but I don't think it's strictly necessary (it's always correct to\nput the string \"true\" into the variable instead).\n\nI think we should also document that <envvar> can't contain an \"=\" sign.\nOf course using strrchr() here doesn't help much with just this patch,\nbecause we flatten the string before stuffing it into\n$GIT_CONFIG_PARAMETERS, so the reading side would mis-parse it.\n\nBut here's a fix for that. I built it on top of your whole series, since\nyou touched some of the related functions, but it could easily be\nrebased onto just this part.\n\n  [1/3]: quote: make sq_dequote_step() a public function\n  [2/3]: config: parse more robust format in GIT_CONFIG_PARAMETERS\n  [3/3]: config: store \"git -c\" variables using more robust format\n\n config.c          | 118 +++++++++++++++++++++++++++++++++++++---------\n quote.c           |  15 ++++--\n quote.h           |  18 ++++++-\n t/t1300-config.sh |  60 +++++++++++++++++++++++\n 4 files changed, 183 insertions(+), 28 deletions(-)\n\n-Peff\n"},{"id":"411881","messageId":"X9D3cO+l2cYA9cB0@coredump.intra.peff.net","threadId":"54704","inReplyTo":"X9D23LQv34A5Q5DC@coredump.intra.peff.net","subject":"[PATCH 1/3] quote: make sq_dequote_step() a public function","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-09T16:12:32Z","receivedAt":"2020-12-09T16:13:14Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"We provide a function for dequoting an entire string, as well as one for\nhandling a space-separated list of quoted strings. But there's no way\nfor a caller to parse a string like 'foo'='bar', even though it is easy\nto generate one using sq_quote_buf() or similar.\n\nLet's make the single-step function available to callers outside of\nquote.c. Note that we do need to adjust its implementation slightly: it\ninsists on seeing whitespace between items, and we'd like to be more\nflexible than that. Since it only has a single caller, we can move that\ncheck (and slurping up any extra whitespace) into that caller.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n quote.c | 15 ++++++++++-----\n quote.h | 18 ++++++++++++++++--\n 2 files changed, 26 insertions(+), 7 deletions(-)\n\ndiff --git a/quote.c b/quote.c\nindex 69f4ca45da..8a3a5e39eb 100644\n--- a/quote.c\n+++ b/quote.c\n@@ -116,7 +116,7 @@ void sq_append_quote_argv_pretty(struct strbuf *dst, const char **argv)\n \t}\n }\n \n-static char *sq_dequote_step(char *arg, char **next)\n+char *sq_dequote_step(char *arg, char **next)\n {\n \tchar *dst = arg;\n \tchar *src = arg;\n@@ -153,11 +153,8 @@ static char *sq_dequote_step(char *arg, char **next)\n \t\t\t}\n \t\t/* Fallthrough */\n \t\tdefault:\n-\t\t\tif (!next || !isspace(*src))\n+\t\t\tif (!next)\n \t\t\t\treturn NULL;\n-\t\t\tdo {\n-\t\t\t\tc = *++src;\n-\t\t\t} while (isspace(c));\n \t\t\t*dst = 0;\n \t\t\t*next = src;\n \t\t\treturn arg;\n@@ -182,6 +179,14 @@ static int sq_dequote_to_argv_internal(char *arg,\n \t\tchar *dequoted = sq_dequote_step(next, &next);\n \t\tif (!dequoted)\n \t\t\treturn -1;\n+\t\tif (next) {\n+\t\t\tchar c;\n+\t\t\tif (!isspace(*next))\n+\t\t\t\treturn -1;\n+\t\t\tdo {\n+\t\t\t\tc = *++next;\n+\t\t\t} while (isspace(c));\n+\t\t}\n \t\tif (argv) {\n \t\t\tALLOC_GROW(*argv, *nr + 1, *alloc);\n \t\t\t(*argv)[(*nr)++] = dequoted;\ndiff --git a/quote.h b/quote.h\nindex 4b72a583cf..768cc6338e 100644\n--- a/quote.h\n+++ b/quote.h\n@@ -42,12 +42,26 @@ void sq_quote_buf_pretty(struct strbuf *, const char *src);\n void sq_quote_argv_pretty(struct strbuf *, const char **argv);\n void sq_append_quote_argv_pretty(struct strbuf *dst, const char **argv);\n \n-/* This unwraps what sq_quote() produces in place, but returns\n+/*\n+ * This unwraps what sq_quote() produces in place, but returns\n  * NULL if the input does not look like what sq_quote would have\n- * produced.\n+ * produced (the full string must be a single quoted item).\n  */\n char *sq_dequote(char *);\n \n+/*\n+ * Like sq_dequote(), but dequote a single item, and leave \"next\" pointing to\n+ * the next character. E.g., in the string:\n+ *\n+ *   'one' 'two' 'three'\n+ *\n+ * after the first call, the return value would be the unquoted string \"one\",\n+ * with \"next\" pointing to the space between \"one\" and \"two\"). The caller is\n+ * responsible for advancing the pointer to the start of the next item before\n+ * calling sq_dequote_step() again.\n+ */\n+char *sq_dequote_step(char *src, char **next);\n+\n /*\n  * Same as the above, but can be used to unwrap many arguments in the\n  * same string separated by space. Like sq_quote, it works in place,\n-- \n2.29.2.1019.g5c4255ecd5\n\n"},{"id":"411882","messageId":"X9D4irohzwFCdx+t@coredump.intra.peff.net","threadId":"54704","inReplyTo":"X9D23LQv34A5Q5DC@coredump.intra.peff.net","subject":"[PATCH 2/3] config: parse more robust format in GIT_CONFIG_PARAMETERS","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-09T16:17:14Z","receivedAt":"2020-12-09T16:17:57Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"When we stuff config options into GIT_CONFIG_PARAMETERS, we shell-quote\neach one as a single unit, like:\n\n  'section.one=value1' 'section.two=value2'\n\nOn the reading side, we de-quote to get the individual strings, and then\nparse them by splitting on the first \"=\" we find. This format is\nambiguous, because an \"=\" may appear in a subsection. So the config\nrepresented in a file by both:\n\n  [section \"subsection=with=equals\"]\n  key = value\n\nand:\n\n  [section]\n  subsection = with=equals.key=value\n\nends up in this flattened format like:\n\n  'section.subsection=with=equals.key=value'\n\nand we can't tell which was desired. We have traditionally resolved this\nby taking the first \"=\" we see starting from the left, meaning that we\nallowed arbitrary content in the value, but not in the subsection.\n\nLet's make our environment format a bit more robust by separately\nquoting the key and value. That turns those examples into:\n\n  'section.subsection=with=equals.key'='value'\n\nand:\n\n  'section.subsection'='with=equals.key=value'\n\nrespectively, and we can tell the difference between them. We can detect\nwhich format is in use for any given element of the list based on the\npresence of the unquoted \"=\". That means we can continue to allow the\nold format to work to support any callers which manually used the old\nformat, and we can even intermingle the two formats. The old format\nwasn't documented, and nobody was supposed to be using it. But it's\nlikely that such callers exist in the wild, so it's nice if we can avoid\nbreaking them. Likewise, it may be possible to trigger an older version\nof \"git -c\" that runs a script that calls into a newer version of \"git\n-c\"; that new version would see the intermingled format.\n\nThis does create one complication, which is that the obvious format in\nthe new scheme for\n\n  [section]\n  some-bool\n\nis:\n\n  'section.some-bool'\n\nwith no equals. We'd mistake that for an old-style variable. And it even\nhas the same meaning in the old style, but:\n\n  [section \"with=equals\"]\n  some-bool\n\ndoes not. It would be:\n\n  'section.with=equals=some-bool'\n\nwhich we'd take to mean:\n\n  [section]\n  with = equals=some-bool\n\nin the old, ambiguous style. Likewise, we can't use:\n\n  'section.some-bool'=''\n\nbecause that's ambiguous with an actual empty string. Instead, we'll\nagain use the shell-quoting to give us a hint, and use:\n\n  'section.some-bool'=\n\nto show that we have no value.\n\nNote that this commit just expands the reading side. We'll start writing\nthe new format via \"git -c\" in a future patch. In the meantime, the\nexisting \"git -c\" tests will make sure we didn't break reading the old\nformat. But we'll also add some explicit coverage of the two formats to\nmake sure we continue to handle the old one after we move the writing\nside over.\n\nAnd one final note: since we're now using the shell-quoting as a\nsemantically meaningful hint, this closes the door to us ever allowing\narbitrary shell quoting, like:\n\n  'a'shell'would'be'ok'with'this'.key=value\n\nBut we have never supported that (only what sq_quote() would produce),\nand we are probably better off keeping things simple, robust, and\nbackwards-compatible, than trying to make it easier for humans. We'll\ncontinue not to advertise the format of the variable to users, and\ninstead keep \"git -c\" as the recommended mechanism for setting config\n(even if we are trying to be kind not to break users who may be relying\non the current undocumented format).\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n config.c          | 66 +++++++++++++++++++++++++++++++++++++----------\n t/t1300-config.sh | 52 +++++++++++++++++++++++++++++++++++++\n 2 files changed, 104 insertions(+), 14 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 779487bc2d..fb160c33d2 100644\n--- a/config.c\n+++ b/config.c\n@@ -504,6 +504,57 @@ int git_config_parse_parameter(const char *text,\n \treturn ret;\n }\n \n+static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n+{\n+\tchar *cur = env;\n+\twhile (cur && *cur) {\n+\t\tconst char *key = sq_dequote_step(cur, &cur);\n+\t\tif (!key)\n+\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\n+\t\tif (!cur || isspace(*cur)) {\n+\t\t\t/* old-style 'key=value' */\n+\t\t\tif (git_config_parse_parameter(key, fn, data) < 0)\n+\t\t\t\treturn -1;\n+\t\t}\n+\t\telse if (*cur == '=') {\n+\t\t\t/* new-style 'key'='value' */\n+\t\t\tconst char *value;\n+\n+\t\t\tcur++;\n+\t\t\tif (*cur == '\\'') {\n+\t\t\t\t/* quoted value */\n+\t\t\t\tvalue = sq_dequote_step(cur, &cur);\n+\t\t\t\tif (!value || (cur && !isspace(*cur))) {\n+\t\t\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t\t\t}\n+\t\t\t} else if (!*cur || isspace(*cur)) {\n+\t\t\t\t/* implicit bool: 'key'= */\n+\t\t\t\tvalue = NULL;\n+\t\t\t} else {\n+\t\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t\t}\n+\n+\t\t\tif (config_parse_pair(key, value, fn, data) < 0)\n+\t\t\t\treturn -1;\n+\t\t}\n+\t\telse {\n+\t\t\t/* unknown format */\n+\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t}\n+\n+\t\tif (cur) {\n+\t\t\twhile (isspace(*cur))\n+\t\t\t\tcur++;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n \tconst char *env;\n@@ -563,22 +614,9 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \n \tenv = getenv(CONFIG_DATA_ENVIRONMENT);\n \tif (env) {\n-\t\tint nr = 0, alloc = 0;\n-\n \t\t/* sq_dequote will write over it */\n \t\tenvw = xstrdup(env);\n-\n-\t\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n-\t\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n-\t\t\tgoto out;\n-\t\t}\n-\n-\t\tfor (i = 0; i < nr; i++) {\n-\t\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n-\t\t\t\tret = -1;\n-\t\t\t\tgoto out;\n-\t\t\t}\n-\t\t}\n+\t\tret = parse_config_env_list(envw, fn, data);\n \t}\n \n out:\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex f157cd217e..bd602e7720 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1294,6 +1294,58 @@ test_expect_success 'git -c is not confused by empty environment' '\n \tGIT_CONFIG_PARAMETERS=\"\" git -c x.one=1 config --list\n '\n \n+test_expect_success 'GIT_CONFIG_PARAMETERS handles old-style entries' '\n+\tv=\"${SQ}key.one=foo${SQ}\" &&\n+\tv=\"$v  ${SQ}key.two=bar${SQ}\" &&\n+\tv=\"$v ${SQ}key.ambiguous=section.whatever=value${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.one foo\n+\tkey.two bar\n+\tkey.ambiguous section.whatever=value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'GIT_CONFIG_PARAMETERS handles new-style entries' '\n+\tv=\"${SQ}key.one${SQ}=${SQ}foo${SQ}\" &&\n+\tv=\"$v  ${SQ}key.two${SQ}=${SQ}bar${SQ}\" &&\n+\tv=\"$v ${SQ}key.ambiguous=section.whatever${SQ}=${SQ}value${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.one foo\n+\tkey.two bar\n+\tkey.ambiguous=section.whatever value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'old and new-style entries can mix' '\n+\tv=\"${SQ}key.oldone=oldfoo${SQ}\" &&\n+\tv=\"$v ${SQ}key.newone${SQ}=${SQ}newfoo${SQ}\" &&\n+\tv=\"$v ${SQ}key.oldtwo=oldbar${SQ}\" &&\n+\tv=\"$v ${SQ}key.newtwo${SQ}=${SQ}newbar${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.oldone oldfoo\n+\tkey.newone newfoo\n+\tkey.oldtwo oldbar\n+\tkey.newtwo newbar\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'old and new bools with ambiguous subsection' '\n+\tv=\"${SQ}key.with=equals.oldbool${SQ}\" &&\n+\tv=\"$v ${SQ}key.with=equals.newbool${SQ}=\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.with equals.oldbool\n+\tkey.with=equals.newbool\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n \tcat >expect <<-\\EOF &&\n \tenv.one one\n-- \n2.29.2.1019.g5c4255ecd5\n\n"},{"id":"411884","messageId":"X9D5SnXca2rGnJFl@coredump.intra.peff.net","threadId":"54704","inReplyTo":"X9D23LQv34A5Q5DC@coredump.intra.peff.net","subject":"[PATCH 3/3] config: store \"git -c\" variables using more robust format","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-09T16:20:26Z","receivedAt":"2020-12-09T16:21:36Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"The previous commit added a new format for $GIT_CONFIG_PARAMETERS which\nis able to robustly handle subsections with \"=\" in them. Let's start\nwriting the new format. Unfortunately, this does much less than you'd\nhope, because \"git -c\" itself has the same ambiguity problem! But it's\nstill worth doing:\n\n  - we've now pushed the problem from the inter-process communication\n    into the \"-c\" command-line parser. This would free us up to later\n    add an unambiguous format there (e.g., separate arguments like \"git\n    --config key value\", etc).\n\n  - for --config-env, the parser already disallows \"=\" in the\n    environment variable name. So:\n\n      git --config-env section.with=equals.key=ENVVAR\n\n    will robustly set section.with=equals.key to the contents of\n    $ENVVAR.\n\nThe new test shows the improvement for --config-env.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\nOne other side effect I just noticed is that we're very aggressive about\ntrimming leading and trailing whitespace in the old-style format, but\nthe new one will store values verbatim. IMHO that's better overall, but\nwe might consider a preparatory patch to remove that trimming\nexplicitly.\n\n config.c          | 52 ++++++++++++++++++++++++++++++++++++++++-------\n t/t1300-config.sh |  8 ++++++++\n 2 files changed, 53 insertions(+), 7 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex fb160c33d2..04029e45dc 100644\n--- a/config.c\n+++ b/config.c\n@@ -333,38 +333,76 @@ int git_config_include(const char *var, const char *value, void *data)\n \treturn ret;\n }\n \n-void git_config_push_parameter(const char *text)\n+static void git_config_push_split_parameter(const char *key, const char *value)\n {\n \tstruct strbuf env = STRBUF_INIT;\n \tconst char *old = getenv(CONFIG_DATA_ENVIRONMENT);\n \tif (old && *old) {\n \t\tstrbuf_addstr(&env, old);\n \t\tstrbuf_addch(&env, ' ');\n \t}\n-\tsq_quote_buf(&env, text);\n+\tsq_quote_buf(&env, key);\n+\tstrbuf_addch(&env, '=');\n+\tif (value)\n+\t\tsq_quote_buf(&env, value);\n \tsetenv(CONFIG_DATA_ENVIRONMENT, env.buf, 1);\n \tstrbuf_release(&env);\n }\n \n+void git_config_push_parameter(const char *text)\n+{\n+\tconst char *value;\n+\n+\t/*\n+\t * When we see:\n+\t *\n+\t *   section.subsection=with=equals.key=value\n+\t *\n+\t * we cannot tell if it means:\n+\t *\n+\t *   [section \"subsection=with=equals\"]\n+\t *   key = value\n+\t *\n+\t * or:\n+\t *\n+\t *   [section]\n+\t *   subsection = with=equals.key=value\n+\t *\n+\t * We parse left-to-right for the first \"=\", meaning we'll prefer to\n+\t * keep the value intact over the subsection. This is historical, but\n+\t * also sensible since values are more likely to contain odd or\n+\t * untrusted input than a section name.\n+\t *\n+\t * A missing equals is explicitly allowed (as a bool-only entry).\n+\t */\n+\tvalue = strchr(text, '=');\n+\tif (value) {\n+\t\tchar *key = xmemdupz(text, value - text);\n+\t\tgit_config_push_split_parameter(key, value + 1);\n+\t\tfree(key);\n+\t} else {\n+\t\tgit_config_push_split_parameter(text, NULL);\n+\t}\n+}\n+\n void git_config_push_env(const char *spec)\n {\n-\tstruct strbuf buf = STRBUF_INIT;\n+\tchar *key;\n \tconst char *env_name;\n \tconst char *env_value;\n \n \tenv_name = strrchr(spec, '=');\n \tif (!env_name)\n \t\tdie(\"invalid config format: %s\", spec);\n+\tkey = xmemdupz(spec, env_name - spec);\n \tenv_name++;\n \n \tenv_value = getenv(env_name);\n \tif (!env_value)\n \t\tdie(\"config variable missing for '%s'\", env_name);\n \n-\tstrbuf_add(&buf, spec, env_name - spec);\n-\tstrbuf_addstr(&buf, env_value);\n-\tgit_config_push_parameter(buf.buf);\n-\tstrbuf_release(&buf);\n+\tgit_config_push_split_parameter(key, env_value);\n+\tfree(key);\n }\n \n static inline int iskeychar(int c)\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex bd602e7720..e06961767f 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1413,6 +1413,14 @@ test_expect_success 'git -c and --config-env override each other' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--config-env handles keys with equals' '\n+\techo value=with=equals >expect &&\n+\tENVVAR=value=with=equals git \\\n+\t\t--config-env=section.subsection=with=equals.key=ENVVAR \\\n+\t\tconfig section.subsection=with=equals.key >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config handles environment config pairs' '\n \tGIT_CONFIG_COUNT=2 \\\n \t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"foo\" \\\n-- \n2.29.2.1019.g5c4255ecd5\n"},{"id":"411885","messageId":"X9D6IyPchkGkYgeB@coredump.intra.peff.net","threadId":"54704","inReplyTo":"871rfzxctq.fsf@evledraar.gmail.com","subject":"Re: [PATCH v4 2/6] config: add new way to pass config via `--config-env`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-09T16:24:03Z","receivedAt":"2020-12-09T16:25:03Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Dec 09, 2020 at 03:40:17PM +0100, Ævar Arnfjörð Bjarmason wrote:\n\n> > +--config-env=<name>=<envvar>::\n> > +\tPass a configuration parameter to the command. The <envvar>\n> > +\tgiven will be replaced with the contents of the environment\n> > +\tvariable of that name. In contrast to `-c`, an envvar must\n> > +\talways be given and exist in the environment. Passing an\n> > +\tenvironment variable with empty value will set <name> to the\n> > +\tempty string which `git config --type=bool` will convert to\n> > +\t`false`.\n> \n> Okey, because \"-c foo.bar\" (true) \"-c foo.bar=\" is the empty string, but\n> that doesn't make sene with \"--config-env\". Also the whole part about\n> --type=bool is just confusing, because it's referring to `-c`'s magic\n> behavior when it comes to `bool` which we don't have here.\n\nYeah, I agree.\n\n> I think it's also worth describing what this is for & what the\n> limitations are. Maybe:\n\nAgreed, and the text you gave looks reasonable. Another reason to use it\nis that it will (if we add the patches I just sent on top) avoid the\nkey/value ambiguity with equals in the section name.\n\n> Not something new, and maybe not something for this series, but I wish\n> -c and --config-env would document this limitation that we support \"=\"\n> in keys in config, but not via those parameters.\n\nYeah. If we add in my patches, then the limitation is gone here (but we\nshould mention the limitation on the environment variable name).\n\nI stopped short of adding a variant of \"-c\" that avoids the ambiguity.\nI'm certainly not opposed to one if somebody wants to do it, but I think\ndocumenting the current limitation makes sense in the meantime (and we\nshould do it in this series while we're thinking about it).\n\n-Peff\n"},{"id":"411886","messageId":"X9D8sTX3envTCi75@coredump.intra.peff.net","threadId":"54704","inReplyTo":"X9D5SnXca2rGnJFl@coredump.intra.peff.net","subject":"Re: [PATCH 3/3] config: store \"git -c\" variables using more robust format","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-09T16:34:57Z","receivedAt":"2020-12-09T16:35:54Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Dec 09, 2020 at 11:20:26AM -0500, Jeff King wrote:\n\n> One other side effect I just noticed is that we're very aggressive about\n> trimming leading and trailing whitespace in the old-style format, but\n> the new one will store values verbatim. IMHO that's better overall, but\n> we might consider a preparatory patch to remove that trimming\n> explicitly.\n\nActually, it looks like we just trim either side of the key. Which\nis...weird. We've never generated any, and I wouldn't expect people to\nwrite:\n\n  git -c '  some.key = value'\n\nAnd even if they did, then \"value\" would have extra whitespace. So I\ndon't think this is really changing anything important, though I'm still\ntempted to do something like the patch below to clean up the reading\nside (and as a bonus, it gets rid of a strbuf_split call, which is a\nterrible and awkward interface).\n\ndiff --git a/config.c b/config.c\nindex 04029e45dc..ede33cf3d0 100644\n--- a/config.c\n+++ b/config.c\n@@ -516,29 +516,21 @@ static int config_parse_pair(const char *key, const char *value,\n int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n-\tconst char *value;\n-\tstruct strbuf **pair;\n+\tchar *to_free = NULL;\n+\tconst char *key, *value;\n \tint ret;\n \n-\tpair = strbuf_split_str(text, '=', 2);\n-\tif (!pair[0])\n-\t\treturn error(_(\"bogus config parameter: %s\"), text);\n-\n-\tif (pair[0]->len && pair[0]->buf[pair[0]->len - 1] == '=') {\n-\t\tstrbuf_setlen(pair[0], pair[0]->len - 1);\n-\t\tvalue = pair[1] ? pair[1]->buf : \"\";\n+\tvalue = strchr(text, '=');\n+\tif (value) {\n+\t\tkey = to_free = xmemdupz(text, value - text);\n+\t\tvalue++;\n \t} else {\n-\t\tvalue = NULL;\n+\t\tkey = text;\n \t}\n \n-\tstrbuf_trim(pair[0]);\n-\tif (!pair[0]->len) {\n-\t\tstrbuf_list_free(pair);\n-\t\treturn error(_(\"bogus config parameter: %s\"), text);\n-\t}\n+\tret = config_parse_pair(key, value, fn, data);\n \n-\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n-\tstrbuf_list_free(pair);\n+\tfree(to_free);\n \treturn ret;\n }\n \n"},{"id":"411947","messageId":"xmqqy2i6zg1j.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"X9D23LQv34A5Q5DC@coredump.intra.peff.net","subject":"Re: [PATCH v4 2/6] config: add new way to pass config via `--config-env`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-10T00:00:08Z","receivedAt":"2020-12-10T00:00:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Wed, Dec 09, 2020 at 12:52:26PM +0100, Patrick Steinhardt wrote:\n>\n>> Co-authored-by: Jeff King <peff@peff.net>\n>> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n>\n> In case we want it, this is also:\n>\n>   Signed-off-by: Jeff King <peff@peff.net>\n>\n>> +--config-env=<name>=<envvar>::\n>> +\tPass a configuration parameter to the command. The <envvar>\n>> +\tgiven will be replaced with the contents of the environment\n>> +\tvariable of that name. In contrast to `-c`, an envvar must\n>> +\talways be given and exist in the environment. Passing an\n>> +\tenvironment variable with empty value will set <name> to the\n>> +\tempty string which `git config --type=bool` will convert to\n>> +\t`false`.\n>\n> I agree with Ævar that we probably should keep an empty variable as the\n> empty string. I think some options use an empty string to clear a list\n> (e.g., push.pushOption), and I'm not sure how they'd react to a bool\n> instead. It would be nice to also have a way to do the implicit-bool\n> thing, but I don't think it's strictly necessary (it's always correct to\n> put the string \"true\" into the variable instead).\n>\n> I think we should also document that <envvar> can't contain an \"=\" sign.\n> Of course using strrchr() here doesn't help much with just this patch,\n> because we flatten the string before stuffing it into\n> $GIT_CONFIG_PARAMETERS, so the reading side would mis-parse it.\n>\n> But here's a fix for that. I built it on top of your whole series, since\n> you touched some of the related functions, but it could easily be\n> rebased onto just this part.\n\nHmph, so \n\n (1) Patrick's 1 & 2 are about adding --config-env,\n\n (2) These three patches can come on top to make it more robust to\n     pass key=value with GIT_CONFIG_PARAMETERS (including what is\n     added via the --config-env=name=envvar), and\n\n (3) The remainder of Patrick's 6-patch series is to further add the\n     pairs of environment variables to pass keys and values?\n\nI am still not sure if we want the last part, but whether we take\n(1) or (3) or neither or both, (2) sounds like a good thing to do.\nAnd (2) would not shine without (1).  In the traditional use of -c,\nwe do not know which = from the end user separates key and value,\nbut when (1) places a = to separate the <name> and the value in the\nenvironment variable, we know where that = is and can quote\naccordingly.\n\nBut these three patches are done on top of (1) and (3), at least for\nnow.\n\nThe above is my understanding of the state of these patches.  Am I\ngetting it right?\n\nThanks.\n\n>   [1/3]: quote: make sq_dequote_step() a public function\n>   [2/3]: config: parse more robust format in GIT_CONFIG_PARAMETERS\n>   [3/3]: config: store \"git -c\" variables using more robust format\n>\n>  config.c          | 118 +++++++++++++++++++++++++++++++++++++---------\n>  quote.c           |  15 ++++--\n>  quote.h           |  18 ++++++-\n>  t/t1300-config.sh |  60 +++++++++++++++++++++++\n>  4 files changed, 183 insertions(+), 28 deletions(-)\n>\n> -Peff\n"},{"id":"411948","messageId":"X9FnU5Jj2XPl8BFk@coredump.intra.peff.net","threadId":"54704","inReplyTo":"xmqqy2i6zg1j.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v4 2/6] config: add new way to pass config via `--config-env`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-10T00:09:55Z","receivedAt":"2020-12-10T00:10:38Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Dec 09, 2020 at 04:00:08PM -0800, Junio C Hamano wrote:\n\n> > I think we should also document that <envvar> can't contain an \"=\" sign.\n> > Of course using strrchr() here doesn't help much with just this patch,\n> > because we flatten the string before stuffing it into\n> > $GIT_CONFIG_PARAMETERS, so the reading side would mis-parse it.\n> >\n> > But here's a fix for that. I built it on top of your whole series, since\n> > you touched some of the related functions, but it could easily be\n> > rebased onto just this part.\n> \n> Hmph, so \n> \n>  (1) Patrick's 1 & 2 are about adding --config-env,\n> \n>  (2) These three patches can come on top to make it more robust to\n>      pass key=value with GIT_CONFIG_PARAMETERS (including what is\n>      added via the --config-env=name=envvar), and\n\nYep, exactly.\n\n>  (3) The remainder of Patrick's 6-patch series is to further add the\n>      pairs of environment variables to pass keys and values?\n\nMore or less. I did use the config_parse_pair() helper from patch 4, and\nthere's some textual dependency on his patch 5 (but it would be easy to\nrebase).\n\n> I am still not sure if we want the last part, but whether we take\n> (1) or (3) or neither or both, (2) sounds like a good thing to do.\n> And (2) would not shine without (1).  In the traditional use of -c,\n> we do not know which = from the end user separates key and value,\n> but when (1) places a = to separate the <name> and the value in the\n> environment variable, we know where that = is and can quote\n> accordingly.\n\nExactly. Without (1), then (2) is not nearly as exciting. We could also\nimplement a non-ambiguous version of \"-c\", like:\n\n  git --config key.with=equals.foo some-value ...\n\nwhich would also benefit from (2). But I think Patrick's main goal was\nto get secret values off the command-line, so he'd want either (1) or\n(3) for that.\n\n> But these three patches are done on top of (1) and (3), at least for\n> now.\n\nYes. I'd be happy to rebase them if we're not going to do the\nGIT_CONFIG_{KEY,VALUE}_<n> parts.\n\n> The above is my understanding of the state of these patches.  Am I\n> getting it right?\n\nYep.\n\n-Peff\n"},{"id":"411960","messageId":"xmqqeejyzdej.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"X9FnU5Jj2XPl8BFk@coredump.intra.peff.net","subject":"Re: [PATCH v4 2/6] config: add new way to pass config via `--config-env`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-10T00:57:08Z","receivedAt":"2020-12-10T00:57:52Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> Yes. I'd be happy to rebase them if we're not going to do the\n> GIT_CONFIG_{KEY,VALUE}_<n> parts.\n>\n>> The above is my understanding of the state of these patches.  Am I\n>> getting it right?\n>\n> Yep.\n\nThanks.  \n\nI have no strong feeling for or against the env-pair feature, so the\nway these three parts are structured (i.e. more robust parsing of\n'=' comes at the end) is fine by me.\n\n\n"},{"id":"412014","messageId":"87pn3hwfd5.fsf@evledraar.gmail.com","threadId":"54704","inReplyTo":"X9D5SnXca2rGnJFl@coredump.intra.peff.net","subject":"Re: [PATCH 3/3] config: store \"git -c\" variables using more robust format","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2020-12-10T20:55:18Z","receivedAt":"2020-12-10T20:56:18Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Dec 09 2020, Jeff King wrote:\n\n> The previous commit added a new format for $GIT_CONFIG_PARAMETERS which\n> is able to robustly handle subsections with \"=\" in them. Let's start\n> writing the new format. Unfortunately, this does much less than you'd\n> hope, because \"git -c\" itself has the same ambiguity problem! But it's\n> still worth doing:\n>\n>   - we've now pushed the problem from the inter-process communication\n>     into the \"-c\" command-line parser. This would free us up to later\n>     add an unambiguous format there (e.g., separate arguments like \"git\n>     --config key value\", etc).\n>\n>   - for --config-env, the parser already disallows \"=\" in the\n>     environment variable name. So:\n>\n>       git --config-env section.with=equals.key=ENVVAR\n>\n>     will robustly set section.with=equals.key to the contents of\n>     $ENVVAR.\n>\n> The new test shows the improvement for --config-env.\n>\n> Signed-off-by: Jeff King <peff@peff.net>\n> ---\n> One other side effect I just noticed is that we're very aggressive about\n> trimming leading and trailing whitespace in the old-style format, but\n> the new one will store values verbatim. IMHO that's better overall, but\n> we might consider a preparatory patch to remove that trimming\n> explicitly.\n>\n>  config.c          | 52 ++++++++++++++++++++++++++++++++++++++++-------\n>  t/t1300-config.sh |  8 ++++++++\n>  2 files changed, 53 insertions(+), 7 deletions(-)\n>\n> diff --git a/config.c b/config.c\n> index fb160c33d2..04029e45dc 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -333,38 +333,76 @@ int git_config_include(const char *var, const char *value, void *data)\n>  \treturn ret;\n>  }\n>  \n> -void git_config_push_parameter(const char *text)\n> +static void git_config_push_split_parameter(const char *key, const char *value)\n>  {\n>  \tstruct strbuf env = STRBUF_INIT;\n>  \tconst char *old = getenv(CONFIG_DATA_ENVIRONMENT);\n>  \tif (old && *old) {\n>  \t\tstrbuf_addstr(&env, old);\n>  \t\tstrbuf_addch(&env, ' ');\n>  \t}\n> -\tsq_quote_buf(&env, text);\n> +\tsq_quote_buf(&env, key);\n> +\tstrbuf_addch(&env, '=');\n> +\tif (value)\n> +\t\tsq_quote_buf(&env, value);\n>  \tsetenv(CONFIG_DATA_ENVIRONMENT, env.buf, 1);\n>  \tstrbuf_release(&env);\n>  }\n>  \n> +void git_config_push_parameter(const char *text)\n> +{\n> +\tconst char *value;\n> +\n> +\t/*\n> +\t * When we see:\n> +\t *\n> +\t *   section.subsection=with=equals.key=value\n> +\t *\n> +\t * we cannot tell if it means:\n> +\t *\n> +\t *   [section \"subsection=with=equals\"]\n> +\t *   key = value\n> +\t *\n> +\t * or:\n> +\t *\n> +\t *   [section]\n> +\t *   subsection = with=equals.key=value\n> +\t *\n> +\t * We parse left-to-right for the first \"=\", meaning we'll prefer to\n> +\t * keep the value intact over the subsection. This is historical, but\n> +\t * also sensible since values are more likely to contain odd or\n> +\t * untrusted input than a section name.\n> +\t *\n> +\t * A missing equals is explicitly allowed (as a bool-only entry).\n> +\t */\n> +\tvalue = strchr(text, '=');\n> +\tif (value) {\n> +\t\tchar *key = xmemdupz(text, value - text);\n> +\t\tgit_config_push_split_parameter(key, value + 1);\n> +\t\tfree(key);\n> +\t} else {\n> +\t\tgit_config_push_split_parameter(text, NULL);\n> +\t}\n> +}\n> +\n>  void git_config_push_env(const char *spec)\n>  {\n> -\tstruct strbuf buf = STRBUF_INIT;\n> +\tchar *key;\n>  \tconst char *env_name;\n>  \tconst char *env_value;\n>  \n>  \tenv_name = strrchr(spec, '=');\n>  \tif (!env_name)\n>  \t\tdie(\"invalid config format: %s\", spec);\n> +\tkey = xmemdupz(spec, env_name - spec);\n>  \tenv_name++;\n>  \n>  \tenv_value = getenv(env_name);\n>  \tif (!env_value)\n>  \t\tdie(\"config variable missing for '%s'\", env_name);\n>  \n> -\tstrbuf_add(&buf, spec, env_name - spec);\n> -\tstrbuf_addstr(&buf, env_value);\n> -\tgit_config_push_parameter(buf.buf);\n> -\tstrbuf_release(&buf);\n> +\tgit_config_push_split_parameter(key, env_value);\n> +\tfree(key);\n>  }\n>  \n>  static inline int iskeychar(int c)\n> diff --git a/t/t1300-config.sh b/t/t1300-config.sh\n> index bd602e7720..e06961767f 100755\n> --- a/t/t1300-config.sh\n> +++ b/t/t1300-config.sh\n> @@ -1413,6 +1413,14 @@ test_expect_success 'git -c and --config-env override each other' '\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success '--config-env handles keys with equals' '\n> +\techo value=with=equals >expect &&\n> +\tENVVAR=value=with=equals git \\\n> +\t\t--config-env=section.subsection=with=equals.key=ENVVAR \\\n> +\t\tconfig section.subsection=with=equals.key >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n\nMaybe worth adding a test for the strrchr() semantics here with:\n\n    perl -we '$ENV{\"Y=Z\"}=\"why and zed\"; system \"Z=zed git --config-env=X=Y=Z ...\"'\n\nWhich would show that we can't look up \"Y=Z\", but will always get \"Z\".\n\nI think that's fine b.t.w., 1/2 of the minor objection I had to\n--config-env in\nhttps://lore.kernel.org/git/87y2i7vvz4.fsf@evledraar.gmail.com/ was\nmainly about being unable to e.g. support odd token usernames with the\n\"insteadOf\" feature.\n\nBut aside from having a feature meant to improve security being able to\nbe combined with a config variable we have in a way that leaks the\npassword in ps(1) I think these improved semantics make sense.\n\nI.e. I can't imagine someone wants an env var with \"=\" in it, even\nthough POSIX makes such a thing possible (you just can't do it in a\nshellscript).\n"},{"id":"412022","messageId":"xmqqsg8dtjpz.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"87pn3hwfd5.fsf@evledraar.gmail.com","subject":"Re: [PATCH 3/3] config: store \"git -c\" variables using more robust format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-10T21:49:28Z","receivedAt":"2020-12-10T23:08:57Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> On Wed, Dec 09 2020, Jeff King wrote:\n> ...\n>> +test_expect_success '--config-env handles keys with equals' '\n>> +\techo value=with=equals >expect &&\n>> +\tENVVAR=value=with=equals git \\\n>> +\t\t--config-env=section.subsection=with=equals.key=ENVVAR \\\n>> +\t\tconfig section.subsection=with=equals.key >actual &&\n>> +\ttest_cmp expect actual\n>> +'\n>> +\n>\n> Maybe worth adding a test for the strrchr() semantics here with:\n>\n>     perl -we '$ENV{\"Y=Z\"}=\"why and zed\"; system \"Z=zed git --config-env=X=Y=Z ...\"'\n>\n> Which would show that we can't look up \"Y=Z\", but will always get \"Z\".\n\nYes, that was explained in the cover letter of these three patches\nin <X9D23LQv34A5Q5DC@coredump.intra.peff.net>.  \n\nWe really should document that <envvar> can't contain an \"=\" sign,\nbut I do not see much point in casting that limitation in stone with\na test.  As long as we know things work correctly with environment\nvariables without '=' in their names, we should be happy.\n\n\n"},{"id":"412078","messageId":"X9NyPfvPjgg1mfZH@coredump.intra.peff.net","threadId":"54704","inReplyTo":"87pn3hwfd5.fsf@evledraar.gmail.com","subject":"Re: [PATCH 3/3] config: store \"git -c\" variables using more robust format","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-11T13:21:01Z","receivedAt":"2020-12-11T13:22:31Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Dec 10, 2020 at 09:55:18PM +0100, Ævar Arnfjörð Bjarmason wrote:\n\n> > diff --git a/t/t1300-config.sh b/t/t1300-config.sh\n> > index bd602e7720..e06961767f 100755\n> > --- a/t/t1300-config.sh\n> > +++ b/t/t1300-config.sh\n> > @@ -1413,6 +1413,14 @@ test_expect_success 'git -c and --config-env override each other' '\n> >  \ttest_cmp expect actual\n> >  '\n> >  \n> > +test_expect_success '--config-env handles keys with equals' '\n> > +\techo value=with=equals >expect &&\n> > +\tENVVAR=value=with=equals git \\\n> > +\t\t--config-env=section.subsection=with=equals.key=ENVVAR \\\n> > +\t\tconfig section.subsection=with=equals.key >actual &&\n> > +\ttest_cmp expect actual\n> > +'\n> > +\n> \n> Maybe worth adding a test for the strrchr() semantics here with:\n> \n>     perl -we '$ENV{\"Y=Z\"}=\"why and zed\"; system \"Z=zed git --config-env=X=Y=Z ...\"'\n> \n> Which would show that we can't look up \"Y=Z\", but will always get \"Z\".\n\nWe're already testing those here, though. If we used strchr(), then we'd\nend up setting section.subsection in the above test.\n\nI.e., your X=Y=Z can be tested in either of two ways:\n\n  - check that X=Y is set to $Z\n\n  - check that X is not set to Y=Z\n\nIt's sufficient to test only one, because success in one implies the\nother. And of the two, I think testing the first is much more\ninteresting (because testing \"this expected thing happened\" is much more\nrobust than \"this unexpected thing didn't happen\").\n\n> I.e. I can't imagine someone wants an env var with \"=\" in it, even\n> though POSIX makes such a thing possible (you just can't do it in a\n> shellscript).\n\nYeah, I'm definitely OK with that limitation, but we should document it.\n\n-Peff\n"},{"id":"412079","messageId":"X9NzEfzjvdnvnX42@ncase","threadId":"54704","inReplyTo":"X9D6IyPchkGkYgeB@coredump.intra.peff.net","subject":"Re: [PATCH v4 2/6] config: add new way to pass config via `--config-env`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-11T13:24:33Z","receivedAt":"2020-12-11T13:26:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Dec 09, 2020 at 11:24:03AM -0500, Jeff King wrote:\n> On Wed, Dec 09, 2020 at 03:40:17PM +0100, Ævar Arnfjörð Bjarmason wrote:\n> \n> > > +--config-env=<name>=<envvar>::\n> > > +\tPass a configuration parameter to the command. The <envvar>\n> > > +\tgiven will be replaced with the contents of the environment\n> > > +\tvariable of that name. In contrast to `-c`, an envvar must\n> > > +\talways be given and exist in the environment. Passing an\n> > > +\tenvironment variable with empty value will set <name> to the\n> > > +\tempty string which `git config --type=bool` will convert to\n> > > +\t`false`.\n> > \n> > Okey, because \"-c foo.bar\" (true) \"-c foo.bar=\" is the empty string, but\n> > that doesn't make sene with \"--config-env\". Also the whole part about\n> > --type=bool is just confusing, because it's referring to `-c`'s magic\n> > behavior when it comes to `bool` which we don't have here.\n> \n> Yeah, I agree.\n> \n> > I think it's also worth describing what this is for & what the\n> > limitations are. Maybe:\n> \n> Agreed, and the text you gave looks reasonable. Another reason to use it\n> is that it will (if we add the patches I just sent on top) avoid the\n> key/value ambiguity with equals in the section name.\n\nYeah, I'll pick up the explanation by Ævar, it's a lot better compared\nto what I had. Thanks!\n\n> > Not something new, and maybe not something for this series, but I wish\n> > -c and --config-env would document this limitation that we support \"=\"\n> > in keys in config, but not via those parameters.\n> \n> Yeah. If we add in my patches, then the limitation is gone here (but we\n> should mention the limitation on the environment variable name).\n> \n> I stopped short of adding a variant of \"-c\" that avoids the ambiguity.\n> I'm certainly not opposed to one if somebody wants to do it, but I think\n> documenting the current limitation makes sense in the meantime (and we\n> should do it in this series while we're thinking about it).\n> \n> -Peff\n\nDo you want me to adopt your patches as part of this series?\n\nPatrick\n"},{"id":"412080","messageId":"X9NzE5+LNYqG1s+o@ncase","threadId":"54704","inReplyTo":"X9D23LQv34A5Q5DC@coredump.intra.peff.net","subject":"Re: [PATCH v4 2/6] config: add new way to pass config via `--config-env`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-11T13:24:35Z","receivedAt":"2020-12-11T13:26:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Dec 09, 2020 at 11:10:04AM -0500, Jeff King wrote:\n> On Wed, Dec 09, 2020 at 12:52:26PM +0100, Patrick Steinhardt wrote:\n> \n> > Co-authored-by: Jeff King <peff@peff.net>\n> > Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> \n> In case we want it, this is also:\n> \n>   Signed-off-by: Jeff King <peff@peff.net>\n> \n> > +--config-env=<name>=<envvar>::\n> > +\tPass a configuration parameter to the command. The <envvar>\n> > +\tgiven will be replaced with the contents of the environment\n> > +\tvariable of that name. In contrast to `-c`, an envvar must\n> > +\talways be given and exist in the environment. Passing an\n> > +\tenvironment variable with empty value will set <name> to the\n> > +\tempty string which `git config --type=bool` will convert to\n> > +\t`false`.\n> \n> I agree with Ævar that we probably should keep an empty variable as the\n> empty string. I think some options use an empty string to clear a list\n> (e.g., push.pushOption), and I'm not sure how they'd react to a bool\n> instead. It would be nice to also have a way to do the implicit-bool\n> thing, but I don't think it's strictly necessary (it's always correct to\n> put the string \"true\" into the variable instead).\n\nI think this is just weirdly worded in the `-c` case, which I mostly\ncopied. We _do_ keep the empty string, which effectively means that `git\nconfig --type=bool` will return `false`.\n\nOr do you mean that we should allow `--config-env=foo.bar=`?\n\n> I think we should also document that <envvar> can't contain an \"=\" sign.\n> Of course using strrchr() here doesn't help much with just this patch,\n> because we flatten the string before stuffing it into\n> $GIT_CONFIG_PARAMETERS, so the reading side would mis-parse it.\n\nMakes sense.\n\nPatrick\n\n> But here's a fix for that. I built it on top of your whole series, since\n> you touched some of the related functions, but it could easily be\n> rebased onto just this part.\n> \n>   [1/3]: quote: make sq_dequote_step() a public function\n>   [2/3]: config: parse more robust format in GIT_CONFIG_PARAMETERS\n>   [3/3]: config: store \"git -c\" variables using more robust format\n> \n>  config.c          | 118 +++++++++++++++++++++++++++++++++++++---------\n>  quote.c           |  15 ++++--\n>  quote.h           |  18 ++++++-\n>  t/t1300-config.sh |  60 +++++++++++++++++++++++\n>  4 files changed, 183 insertions(+), 28 deletions(-)\n> \n> -Peff\n"},{"id":"412082","messageId":"X9N1hcGl2rKH+CUU@ncase","threadId":"54704","inReplyTo":"87y2i7vvz4.fsf@evledraar.gmail.com","subject":"Re: [PATCH v4 0/6] config: allow specifying config entries via env","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-11T13:35:01Z","receivedAt":"2020-12-11T13:36:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Dec 09, 2020 at 04:29:35PM +0100, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Wed, Dec 09 2020, Patrick Steinhardt wrote:\n> \n> > this is the fourth version of my patch series which aims to implement a\n> > way to pass config entries via the environment while avoiding any\n> > requirements to perform shell quoting on the user's side.\n> >\n> > Given that the What's Cooking report notes that my third version is\n> > about to be dropped dropped because the `--config-env` way of doing\n> > things is preferred, I've now adopted that approach. I've taken the\n> > patch which Peff posted originally (with one change strchr->strrchr) and\n> > added documentation and tests to it.\n> >\n> > This patch series still includes my old proposal as it would actually be\n> > a better fit for our usecase at GitLab I have in mind, which is to put\n> > all configuration which applies to all git commands into the commands\n> > instead of using a config file for this. I have structured the series in\n> > such a way though that those patches come last -- so if you continue to\n> > think this approach shouldn't make it in, please feel free to drop\n> > patches 3-6.\n> \n> To add even more to your headaches (sorry!) I hadn't really fully looked\n> at that --config-env proposal.\n> \n> As noted in my per-patch reply in [1] it will still expose the key part\n> of the key=value, and in at least one place (url.<base>.insteadOf) the\n> key is where we'll pass the user/password on the command-line still with\n> that.\n\nTrue, that's one of the things I don't quite like about `--config-env`.\n\n> I'd much prefer either your 6/6 over --config-env for that reason & that\n> --config-env makes it impossible to pass a key with \"=\" in. For \"-c\" I\n> don't think that's much of an issue, but e.g. with\n> \"url.<base>.insteadOf\" needing to take arbitrary passwords + us\n> implicitly/explicitly advertising this as a \"here's how you can pass the\n> password\" feature not being able to have \"=\" is more painful.\n> \n> I mildly prefer Jeff's suggestion of just getting GIT_CONFIG_PARAMETERS\n> to the point where we could document it [2][3] to both of those, but\n> that's mostly an asthetic concern of dealing with N values. It won't\n> matter for the security aspect (but I think you (but haven't tested)\n> that you still can't pass a \"=\", but your 6/6 does allow that).\n\nDocumenting the format would be interesting, but I'm still not quite\nsure how it'd be used then. Using a separate `git shell-quote` binary\njust to correctly convert the strings to what git would expect doesn't\nseem ideal to me, also because it would mean a separate process for each\ngit invocation which wants to use GIT_CONFIG_PARAMETERS. On the other\nhand, reimplementing the shellquoting functionality wherever you want to\nuse it doesn't sound ideal either.\n\n[snip]\n\n> I do that that whatever we go for this series would be much better if\n> the commit messages / added docs explained why we're doing particular\n> things, and to users why they'd use one method but not the other.\n\nMakes sense. The commit messages do mention it, but docs don't. I plan\nto take your explanation anyway as it's a lot better compared to what I\nhad, and it does explain why one would want to use `--config-env`.\n\n> E.g. IIRC this whole series is because it's a hassle to invoke\n> core.askpass in some stateful program where you'd like to just provide a\n> transitory password. I think some brief cross-linking or explanation\n> somewhere of these various ways to pass sensitive values around would be\n> relly helpful.\n\nIt had been the original intention, yes. And it still is, but in fact\nthe usecase has broadened to also use it to get rid of our global git\nconfig in Gitaly. Which is a little bit awkward to do with\n`--config-env` or `-c`, as now a ps(1) would first show a dozen of\nconfiguration values only to have the real command buried somewhere at\nthe back. It would have been easy to implement though with the\nGIT_CONFIG_ envvars.\n\nGranted, we could still do the same by just using GIT_CONFIG_PAREMETERS.\nBut I'm kind of hesitant to reimplement the shell-quoting procedures in\nGitaly, especially considering that we'd put untrusted data in there.\n\nPatrick\n"},{"id":"412084","messageId":"X9OB7ek8fVRXUBdK@coredump.intra.peff.net","threadId":"54704","inReplyTo":"X9N1hcGl2rKH+CUU@ncase","subject":"Re: [PATCH v4 0/6] config: allow specifying config entries via env","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-11T14:27:57Z","receivedAt":"2020-12-11T14:55:03Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Dec 11, 2020 at 02:35:01PM +0100, Patrick Steinhardt wrote:\n\n> > E.g. IIRC this whole series is because it's a hassle to invoke\n> > core.askpass in some stateful program where you'd like to just provide a\n> > transitory password. I think some brief cross-linking or explanation\n> > somewhere of these various ways to pass sensitive values around would be\n> > relly helpful.\n> \n> It had been the original intention, yes. And it still is, but in fact\n> the usecase has broadened to also use it to get rid of our global git\n> config in Gitaly. Which is a little bit awkward to do with\n> `--config-env` or `-c`, as now a ps(1) would first show a dozen of\n> configuration values only to have the real command buried somewhere at\n> the back. It would have been easy to implement though with the\n> GIT_CONFIG_ envvars.\n\nI don't know what kinds of variables you want to set exactly, but\nanother possible option here is some mechanism to point Git to an extra\nconfig file. This would work if you are setting a bunch of options in\nsome static way, but not if you're setting them to custom values for\neach command invocation (because then you'd be dealing with a temp file,\nwhich is annoying and error-prone).\n\nI'm thinking something like a $GIT_CONFIG_ENV_FILE that is parsed after\nrepo config but before $GIT_CONFIG_PARAMETERS.\n\nOr alternatively, add an includeIf directive that lets you do something\nlike:\n\n  [includeIf \"env:FOO\"]\n  path = foo.gitconfig\n\nwhich triggers if $FOO is set. But again, that's only useful if you have\ncertain \"profiles\" of config you're trying to set, and not custom\nvalues.\n\n-Peff\n"},{"id":"412085","messageId":"X9OFRiqDDYtbg87i@coredump.intra.peff.net","threadId":"54704","inReplyTo":"X9OB7ek8fVRXUBdK@coredump.intra.peff.net","subject":"Re: [PATCH v4 0/6] config: allow specifying config entries via env","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-11T14:42:14Z","receivedAt":"2020-12-11T15:08:20Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Dec 11, 2020 at 09:27:57AM -0500, Jeff King wrote:\n\n> I don't know what kinds of variables you want to set exactly, but\n> another possible option here is some mechanism to point Git to an extra\n> config file. This would work if you are setting a bunch of options in\n> some static way, but not if you're setting them to custom values for\n> each command invocation (because then you'd be dealing with a temp file,\n> which is annoying and error-prone).\n> \n> I'm thinking something like a $GIT_CONFIG_ENV_FILE that is parsed after\n> repo config but before $GIT_CONFIG_PARAMETERS.\n\nOne more (probably insane) idea, that you are free to ignore unless it\nsparks your interest.\n\n$GIT_CONFIG_ENV could contain an actual config-file snippet itself.\nI.e.:\n\n  GIT_CONFIG_ENV='\n\t[foo]\n\tbar = value\n\t[another \"section\"]\n\tkey = \"more complicated value\"\n  '\n\nIn fact, we could have implemented $GIT_CONFIG_PARAMETERS that way from\nthe very beginning. I'd be hesitant to change it now, though.\n\nIt doesn't really make your quoting problem go away, in that you'd now\nhave to generate a valid and correct config file, which is even more\ncomplicated than shell-quoting. :) But it is at least a well-documented\nformat whose generator might be used for other things, too.\n\n-Peff\n"},{"id":"412086","messageId":"X9OGiuUUcVw83obp@ncase","threadId":"54704","inReplyTo":"X9OB7ek8fVRXUBdK@coredump.intra.peff.net","subject":"Re: [PATCH v4 0/6] config: allow specifying config entries via env","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-11T14:47:38Z","receivedAt":"2020-12-11T15:17:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Dec 11, 2020 at 09:27:57AM -0500, Jeff King wrote:\n> On Fri, Dec 11, 2020 at 02:35:01PM +0100, Patrick Steinhardt wrote:\n> \n> > > E.g. IIRC this whole series is because it's a hassle to invoke\n> > > core.askpass in some stateful program where you'd like to just provide a\n> > > transitory password. I think some brief cross-linking or explanation\n> > > somewhere of these various ways to pass sensitive values around would be\n> > > relly helpful.\n> > \n> > It had been the original intention, yes. And it still is, but in fact\n> > the usecase has broadened to also use it to get rid of our global git\n> > config in Gitaly. Which is a little bit awkward to do with\n> > `--config-env` or `-c`, as now a ps(1) would first show a dozen of\n> > configuration values only to have the real command buried somewhere at\n> > the back. It would have been easy to implement though with the\n> > GIT_CONFIG_ envvars.\n> \n> I don't know what kinds of variables you want to set exactly, but\n> another possible option here is some mechanism to point Git to an extra\n> config file. This would work if you are setting a bunch of options in\n> some static way, but not if you're setting them to custom values for\n> each command invocation (because then you'd be dealing with a temp file,\n> which is annoying and error-prone).\n> \n> I'm thinking something like a $GIT_CONFIG_ENV_FILE that is parsed after\n> repo config but before $GIT_CONFIG_PARAMETERS.\n> \n> Or alternatively, add an includeIf directive that lets you do something\n> like:\n> \n>   [includeIf \"env:FOO\"]\n>   path = foo.gitconfig\n> \n> which triggers if $FOO is set. But again, that's only useful if you have\n> certain \"profiles\" of config you're trying to set, and not custom\n> values.\n> \n> -Peff\n\nThe issue we have is that the config file isn't necessarily under our\ncontrol. It is in most cases, like e.g. when Gitaly gets deployed via\nOmnibus. But we also allow for source-based installations, where the\nuser configures most things manually. And in that case, we have to ask\nthe user to \"Please set config variables A, B and C\". Naturally, this is\neasy to forget, will drift apart in future releases and so on.\n\nTo fix this, the plan is to move all required configuration items into\nGitaly itself, which GIT_CONFIG_COUNT would've allowd to do quite\nnicely. Something like Ævar's proposal to allow reading the config from\na file descriptor would also work, and just putting the whole\nconfiguration into an environment variable (similar to your\nGIT_CONFIG_ENV_FILE, but containing contents instead of a path). And\nfinally, using `-c` would also work, with the downside of making it\nharder to see what's going on with all the git processes.\n\nWith regards to what we require from the config, you can have a look\ne.g. at [1]. It doesn't contain much, but we expect the following ones\nto be set:\n\n    - core.autocrlf=input\n    - gc.auto=0\n    - repack.writeBitmaps=true\n    - receive.advertisePushOptions=true\n    - core.fsyncObjectFiles=true\n\nAnyway, this is all rather specific to Gitaly and may thus not be too\ninteresting for other. So in the end, we'll just live with the tradeoffs\nof whatever solution we end up with.\n\nPatrick\n\n[1]: https://docs.gitlab.com/ee/install/installation.html#configure-it\n"},{"id":"412087","messageId":"X9OICyWMn0sSRb/3@ncase","threadId":"54704","inReplyTo":"X9OAcgPxyYIJo+J/@coredump.intra.peff.net","subject":"Re: [PATCH v4 2/6] config: add new way to pass config via `--config-env`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-11T14:54:03Z","receivedAt":"2020-12-11T15:18:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Dec 11, 2020 at 09:21:38AM -0500, Jeff King wrote:\n> On Fri, Dec 11, 2020 at 02:24:33PM +0100, Patrick Steinhardt wrote:\n> \n> > Do you want me to adopt your patches as part of this series?\n> \n> Yeah, if you're willing to. I don't mind spinning it off into its own\n> series if you don't want to (the tricky part is that we're touching a\n> couple of the same spots, though, so if you're willing to pick them up,\n> I think that makes coordination easier).\n> \n> -Peff\n\nI can do so. The only question that I have is whether I should rebase it\non top of 6/6 or on top of 2/6. It's hard for me to gauge whether 6/6 is\ngoing to make it in or not due to the conflicting opinions on it. It\ncurrently seems to me like we tend towards a \"no\", which is also what\nthe \"What's cooking\" report said. But there were also some opinions in\nfavor of it, which made me wonder. If this is a definitive \"no\", then\nI'm happy to stop bothering with them to make the patch series easier to\nmanage.\n\nPatrick\n"},{"id":"412088","messageId":"X9OJGZhJFjqG/t3S@ncase","threadId":"54704","inReplyTo":"X9OFRiqDDYtbg87i@coredump.intra.peff.net","subject":"Re: [PATCH v4 0/6] config: allow specifying config entries via env","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-11T14:58:33Z","receivedAt":"2020-12-11T15:26:26Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Dec 11, 2020 at 09:42:14AM -0500, Jeff King wrote:\n> On Fri, Dec 11, 2020 at 09:27:57AM -0500, Jeff King wrote:\n> \n> > I don't know what kinds of variables you want to set exactly, but\n> > another possible option here is some mechanism to point Git to an extra\n> > config file. This would work if you are setting a bunch of options in\n> > some static way, but not if you're setting them to custom values for\n> > each command invocation (because then you'd be dealing with a temp file,\n> > which is annoying and error-prone).\n> > \n> > I'm thinking something like a $GIT_CONFIG_ENV_FILE that is parsed after\n> > repo config but before $GIT_CONFIG_PARAMETERS.\n> \n> One more (probably insane) idea, that you are free to ignore unless it\n> sparks your interest.\n> \n> $GIT_CONFIG_ENV could contain an actual config-file snippet itself.\n> I.e.:\n> \n>   GIT_CONFIG_ENV='\n> \t[foo]\n> \tbar = value\n> \t[another \"section\"]\n> \tkey = \"more complicated value\"\n>   '\n> \n> In fact, we could have implemented $GIT_CONFIG_PARAMETERS that way from\n> the very beginning. I'd be hesitant to change it now, though.\n> \n> It doesn't really make your quoting problem go away, in that you'd now\n> have to generate a valid and correct config file, which is even more\n> complicated than shell-quoting. :) But it is at least a well-documented\n> format whose generator might be used for other things, too.\n> \n> -Peff\n\nOur mails crossed, but I did have the same idea. I don't even think it\nthat insane -- the format is well-documented, it solves some of the\nissues I have and implementing it shouldn't be hard considering that all\ninfra for it exists already. True, it won't fix the quoting issue. But\nit would neatly fix our \"global config\" problem without cluttering\noutput of ps(1).\n\nPatrick\n"},{"id":"412089","messageId":"87h7oswepj.fsf@evledraar.gmail.com","threadId":"54704","inReplyTo":"X9OGiuUUcVw83obp@ncase","subject":"Re: [PATCH v4 0/6] config: allow specifying config entries via env","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2020-12-11T15:21:44Z","receivedAt":"2020-12-11T16:10:30Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Dec 11 2020, Patrick Steinhardt wrote:\n\n> On Fri, Dec 11, 2020 at 09:27:57AM -0500, Jeff King wrote:\n>> On Fri, Dec 11, 2020 at 02:35:01PM +0100, Patrick Steinhardt wrote:\n>> \n>> > > E.g. IIRC this whole series is because it's a hassle to invoke\n>> > > core.askpass in some stateful program where you'd like to just provide a\n>> > > transitory password. I think some brief cross-linking or explanation\n>> > > somewhere of these various ways to pass sensitive values around would be\n>> > > relly helpful.\n>> > \n>> > It had been the original intention, yes. And it still is, but in fact\n>> > the usecase has broadened to also use it to get rid of our global git\n>> > config in Gitaly. Which is a little bit awkward to do with\n>> > `--config-env` or `-c`, as now a ps(1) would first show a dozen of\n>> > configuration values only to have the real command buried somewhere at\n>> > the back. It would have been easy to implement though with the\n>> > GIT_CONFIG_ envvars.\n>> \n>> I don't know what kinds of variables you want to set exactly, but\n>> another possible option here is some mechanism to point Git to an extra\n>> config file. This would work if you are setting a bunch of options in\n>> some static way, but not if you're setting them to custom values for\n>> each command invocation (because then you'd be dealing with a temp file,\n>> which is annoying and error-prone).\n>> \n>> I'm thinking something like a $GIT_CONFIG_ENV_FILE that is parsed after\n>> repo config but before $GIT_CONFIG_PARAMETERS.\n>> \n>> Or alternatively, add an includeIf directive that lets you do something\n>> like:\n>> \n>>   [includeIf \"env:FOO\"]\n>>   path = foo.gitconfig\n>> \n>> which triggers if $FOO is set. But again, that's only useful if you have\n>> certain \"profiles\" of config you're trying to set, and not custom\n>> values.\n>> \n>> -Peff\n>\n> The issue we have is that the config file isn't necessarily under our\n> control. It is in most cases, like e.g. when Gitaly gets deployed via\n> Omnibus. But we also allow for source-based installations, where the\n> user configures most things manually. And in that case, we have to ask\n> the user to \"Please set config variables A, B and C\". Naturally, this is\n> easy to forget, will drift apart in future releases and so on.\n>\n> To fix this, the plan is to move all required configuration items into\n> Gitaly itself, which GIT_CONFIG_COUNT would've allowd to do quite\n> nicely. Something like Ævar's proposal to allow reading the config from\n> a file descriptor would also work, and just putting the whole\n> configuration into an environment variable (similar to your\n> GIT_CONFIG_ENV_FILE, but containing contents instead of a path). And\n> finally, using `-c` would also work, with the downside of making it\n> harder to see what's going on with all the git processes.\n\nAside from other stuff mentioned in this thread a trick I've used for a\nwhile to make things \"git-y\" is:\n\n    [alias]\n    sh = !sh\n\nThen you can just:\n\n    git -c foo.bar=baz sh -c 'git config --get foo.bar'\n\nOr, with a symlink from \"git-aly\" to \"gitaly\" in $PATH:\n\n    git -c foo.bar=baz aly [...]\n\nAlthough that's more a hack, and may go away depending on what happens\nto dashed builtins (I don't know what Johannes was planning there).\n\nOf course this only works for global config and \"I want to run this\nscript doing a bunch of git stuff, and using this config\", not\ne.g. dynamically setting a password for one request.\n\n> With regards to what we require from the config, you can have a look\n> e.g. at [1]. It doesn't contain much, but we expect the following ones\n> to be set:\n>\n>     - core.autocrlf=input\n>     - gc.auto=0\n>     - repack.writeBitmaps=true\n>     - receive.advertisePushOptions=true\n>     - core.fsyncObjectFiles=true\n>\n> Anyway, this is all rather specific to Gitaly and may thus not be too\n> interesting for other. So in the end, we'll just live with the tradeoffs\n> of whatever solution we end up with.\n>\n> Patrick\n>\n> [1]: https://docs.gitlab.com/ee/install/installation.html#configure-it\n\n"},{"id":"412090","messageId":"X9OYMnb8g8Hisvv0@coredump.intra.peff.net","threadId":"54704","inReplyTo":"X9OGiuUUcVw83obp@ncase","subject":"Re: [PATCH v4 0/6] config: allow specifying config entries via env","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-11T16:02:58Z","receivedAt":"2020-12-11T17:16:12Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Dec 11, 2020 at 03:47:38PM +0100, Patrick Steinhardt wrote:\n\n> The issue we have is that the config file isn't necessarily under our\n> control. It is in most cases, like e.g. when Gitaly gets deployed via\n> Omnibus. But we also allow for source-based installations, where the\n> user configures most things manually. And in that case, we have to ask\n> the user to \"Please set config variables A, B and C\". Naturally, this is\n> easy to forget, will drift apart in future releases and so on.\n\nFor GitHub, we ship a VM appliance, so we just put what we want into\n/etc/gitconfig. I think in the worst case you could simplify everything\ndown to \"put [include]path=/usr/share/gitlab/gitconfig into your system\nconfig.  Though I'm a little surprised that you wouldn't just ship your\nown version of Git that is used on the backend (so you know you have the\nright version, plus any custom patches you'd want), and then point its\nsystem config to /usr/share/gitlab/ or whatever.\n\nBut I'm probably just showing my ignorance of your setup / install\nprocedures, and there are a dozen reasons why that wouldn't work. ;)\n\n> To fix this, the plan is to move all required configuration items into\n> Gitaly itself, which GIT_CONFIG_COUNT would've allowd to do quite\n> nicely. Something like Ævar's proposal to allow reading the config from\n> a file descriptor would also work, and just putting the whole\n> configuration into an environment variable (similar to your\n> GIT_CONFIG_ENV_FILE, but containing contents instead of a path). And\n> finally, using `-c` would also work, with the downside of making it\n> harder to see what's going on with all the git processes.\n\nWe do have a couple scripts (like our git-repack wrapper) that make\nheavy use of \"git -c\" to tweak things that don't have a command-line\noption, or for which using it is awkward. It does clutter up \"ps\" a bit,\nbut it's sometimes nice to see the extra values, too (just as you'd see\ncommand-line options).\n\n-Peff\n"},{"id":"412091","messageId":"X9OZ8YcydvXQZCap@coredump.intra.peff.net","threadId":"54704","inReplyTo":"X9OICyWMn0sSRb/3@ncase","subject":"Re: [PATCH v4 2/6] config: add new way to pass config via `--config-env`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-11T16:10:25Z","receivedAt":"2020-12-11T17:19:45Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Dec 11, 2020 at 03:54:03PM +0100, Patrick Steinhardt wrote:\n\n> > Yeah, if you're willing to. I don't mind spinning it off into its own\n> > series if you don't want to (the tricky part is that we're touching a\n> > couple of the same spots, though, so if you're willing to pick them up,\n> > I think that makes coordination easier).\n> > \n> \n> I can do so. The only question that I have is whether I should rebase it\n> on top of 6/6 or on top of 2/6. It's hard for me to gauge whether 6/6 is\n> going to make it in or not due to the conflicting opinions on it. It\n> currently seems to me like we tend towards a \"no\", which is also what\n> the \"What's cooking\" report said. But there were also some opinions in\n> favor of it, which made me wonder. If this is a definitive \"no\", then\n> I'm happy to stop bothering with them to make the patch series easier to\n> manage.\n\nI'd probably do it on top of 2/6 (well, perhaps shuffling 4/6 forward is\nneeded, then, I think). And then that punts the decision.\n\nAs for the general idea of 6/6, I think I'm a soft \"no\" there. Normally\nmy opinion for things I wouldn't use myself is \"hey, go to town, if\nyou're willing to write the patch and it won't hurt anybody else\". My\nonly reservation is that it's a public-facing interface, so we'll have\nto support it forever. And I don't love the interface.\n\nThat's not a reflection on how you did the series, btw. I think you've\ndone a very good job of trying to address everyone's concerns, and\nbalance them with having a way to get data through the environment that\ndoesn't require error-prone quoting and parsing. But at the end, I think\nwe are left with a fundamental tradeoff: an interface that is clunky\nbecause of the counted variables, or one if that is clunky because of\nthe quoting.\n\n(And by \"soft no\", I just mean that I wouldn't pursue it further in your\nshoes, but I'm not going to strenuously object if you and others want to\ngo forward).\n\n-Peff\n"},{"id":"412104","messageId":"X9OAIs6cGdNVt4xV@coredump.intra.peff.net","threadId":"54704","inReplyTo":"X9NzE5+LNYqG1s+o@ncase","subject":"Re: [PATCH v4 2/6] config: add new way to pass config via `--config-env`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-11T14:20:18Z","receivedAt":"2020-12-12T01:01:01Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Dec 11, 2020 at 02:24:35PM +0100, Patrick Steinhardt wrote:\n\n> > > +--config-env=<name>=<envvar>::\n> > > +\tPass a configuration parameter to the command. The <envvar>\n> > > +\tgiven will be replaced with the contents of the environment\n> > > +\tvariable of that name. In contrast to `-c`, an envvar must\n> > > +\talways be given and exist in the environment. Passing an\n> > > +\tenvironment variable with empty value will set <name> to the\n> > > +\tempty string which `git config --type=bool` will convert to\n> > > +\t`false`.\n> > \n> > I agree with Ævar that we probably should keep an empty variable as the\n> > empty string. I think some options use an empty string to clear a list\n> > (e.g., push.pushOption), and I'm not sure how they'd react to a bool\n> > instead. It would be nice to also have a way to do the implicit-bool\n> > thing, but I don't think it's strictly necessary (it's always correct to\n> > put the string \"true\" into the variable instead).\n> \n> I think this is just weirdly worded in the `-c` case, which I mostly\n> copied. We _do_ keep the empty string, which effectively means that `git\n> config --type=bool` will return `false`.\n\nOh indeed, I misread what you wrote in the documentation. I think it is\ndoing the right thing, then. IMHO it is not worth even calling out\nspecially, since there is no matching implicit-bool form. I.e., I'd\nprobably just cut the final sentence.\n\n> Or do you mean that we should allow `--config-env=foo.bar=`?\n\nHmm, yeah, that would work as an \"implicit bool\". But it's sufficiently\nugly and non-intuitive (and weirdly overlapping with \"-c\") that I'm not\nsure it is worth supporting.\n\n-Peff\n"},{"id":"412105","messageId":"X9OAcgPxyYIJo+J/@coredump.intra.peff.net","threadId":"54704","inReplyTo":"X9NzEfzjvdnvnX42@ncase","subject":"Re: [PATCH v4 2/6] config: add new way to pass config via `--config-env`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-11T14:21:38Z","receivedAt":"2020-12-12T01:01:01Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Dec 11, 2020 at 02:24:33PM +0100, Patrick Steinhardt wrote:\n\n> Do you want me to adopt your patches as part of this series?\n\nYeah, if you're willing to. I don't mind spinning it off into its own\nseries if you don't want to (the tricky part is that we're touching a\ncouple of the same spots, though, so if you're willing to pick them up,\nI think that makes coordination easier).\n\n-Peff\n"},{"id":"412342","messageId":"470521e72865d7043fe0f1ce0f3e39a146fa2805.1608104755.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1608104755.git.ps@pks.im","subject":"[PATCH v5 1/8] git: add `--super-prefix` to usage string","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-16T07:52:42Z","receivedAt":"2020-12-16T07:53:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When the `--super-prefix` option was implmented in 74866d7579 (git: make\nsuper-prefix option, 2016-10-07), its existence was only documented in\nthe manpage but not in the command's own usage string. Given that the\ncommit message didn't mention that this was done intentionally and given\nthat it's documented in the manpage, this seems like an oversight.\n\nAdd it to the usage string to fix the inconsistency.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n git.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/git.c b/git.c\nindex a00a0a4d94..5a8ff12f87 100644\n--- a/git.c\n+++ b/git.c\n@@ -29,6 +29,7 @@ const char git_usage_string[] =\n \t   \"           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\\n\"\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\\n\"\n \t   \"           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\\n\"\n+\t   \"           [--super-prefix=<path>]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n-- \n2.29.2\n\n"},{"id":"412343","messageId":"cover.1608104755.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1606214397.git.ps@pks.im","subject":"[PATCH v5 0/8] config: allow specifying config entries via env","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-16T07:52:38Z","receivedAt":"2020-12-16T07:53:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the fifth version of my patch series which aims to implement a\nway to pass config entries via the environment while avoiding any\nrequirements to perform shell quoting on the user's side.\n\nChanges in this version include:\n\n    - I've adopted Jeff's patches to make GIT_CONFIG_PARAMETERS more\n      robust by using quoting for both key and value of the config\n      entry. This allows to store entries which for example have an\n      equals sign in their key.\n\n    - I've replaced the documentation of `git --config-env` by Ævar's,\n      which was much better.\n\n    - I've amended the documentation of GIT_CONFIG_COUNT to document the\n      intended usecase.\n\nThe series is structured as following:\n\n    - Patch 1 is a while-at-it patch for the `--super-prefix` usage\n      string which was missing in `git --help`.\n\n    - Patch 2 implements `git --config-env`.\n\n    - Patch 3-6 implement robust handling of GIT_CONFIG_PARAMETERS.\n\n    - Patch 7-8 implement GIT_CONFIG_COUNT handling.\n\nAs before, if the GIT_CONFIG_COUNT code is unwanted, please feel free to\ncut off after the 6th patch.\n\nPatrick\n\nJeff King (3):\n  quote: make sq_dequote_step() a public function\n  config: store \"git -c\" variables using more robust format\n  config: parse more robust format in GIT_CONFIG_PARAMETERS\n\nPatrick Steinhardt (5):\n  git: add `--super-prefix` to usage string\n  config: add new way to pass config via `--config-env`\n  config: extract function to parse config pairs\n  environment: make `getenv_safe()` a public function\n  config: allow specifying config entries via envvar pairs\n\n Documentation/git-config.txt |  16 +++\n Documentation/git.txt        |  23 +++-\n cache.h                      |   1 +\n config.c                     | 205 ++++++++++++++++++++++++++++----\n config.h                     |   1 +\n environment.c                |   8 +-\n environment.h                |  12 ++\n git.c                        |   3 +\n quote.c                      |  15 ++-\n quote.h                      |  18 ++-\n t/t1300-config.sh            | 220 ++++++++++++++++++++++++++++++++++-\n 11 files changed, 483 insertions(+), 39 deletions(-)\n create mode 100644 environment.h\n\n-- \n2.29.2\n\n"},{"id":"412344","messageId":"56c9221c4cc8c3e52823938938e3f65a3433f9bf.1608104755.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1608104755.git.ps@pks.im","subject":"[PATCH v5 2/8] config: add new way to pass config via `--config-env`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-16T07:52:47Z","receivedAt":"2020-12-16T07:53:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While it's already possible to pass runtime configuration via `git -c\n<key>=<value>`, it may be undesirable to use when the value contains\nsensitive information. E.g. if one wants to set `http.extraHeader` to\ncontain an authentication token, doing so via `-c` would trivially leak\nthose credentials via e.g. ps(1), which typically also shows command\narguments.\n\nTo enable this usecase without leaking credentials, this commit\nintroduces a new switch `--config-env=<key>=<envvar>`. Instead of\ndirectly passing a value for the given key, it instead allows the user\nto specify the name of an environment variable. The value of that\nvariable will then be used as value of the key.\n\nCo-authored-by: Jeff King <peff@peff.net>\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git.txt | 23 +++++++++++++++++++++-\n config.c              | 21 ++++++++++++++++++++\n config.h              |  1 +\n git.c                 |  4 +++-\n t/t1300-config.sh     | 45 +++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 92 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex c463b937a8..80fb8fab11 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n     [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n     [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]\n     [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n-    [--super-prefix=<path>]\n+    [--super-prefix=<path>] [--config-env <name>=<envvar>]\n     <command> [<args>]\n \n DESCRIPTION\n@@ -80,6 +80,27 @@ config file). Including the equals but with an empty value (like `git -c\n foo.bar= ...`) sets `foo.bar` to the empty string which `git config\n --type=bool` will convert to `false`.\n \n+--config-env=<name>=<envvar>::\n+\tLike `-c <name>=<var>` except the value is the name of an\n+\tenvironment variable from which to retrieve the value. Unlike\n+\t`-c` there is no shortcut for directly setting the value to an\n+\tempty string, instead the environment variable itself must be\n+\tset to the empty strin. Errors if the `<envvar>` does not exist\n+\tin the environment. `<envvar>` may not contain an equals sign\n+\tto avoid ambiguity with `<name>`s which contain one.\n+\n+\tThis is useful for cases where you want to pass transitory\n+\tconfiguration options to git, but are doing so on OS's where\n+\tother processes might be able to read your cmdline\n+\t(e.g. `/proc/self/cmdline`), but not your environ\n+\t(e.g. `/proc/self/environ`). That behavior is the default on\n+\tLinux, but may not be on your system.\n+\n+\tNote that this might add security for variables such as\n+\t`http.extraHeader` where the sensitive information is part of\n+\tthe value, but not e.g. `url.<base.insteadOf` where the\n+\tsensitive information can be part of the key.\n+\n --exec-path[=<path>]::\n \tPath to wherever your core Git programs are installed.\n \tThis can also be controlled by setting the GIT_EXEC_PATH\ndiff --git a/config.c b/config.c\nindex 1137bd73af..cde3511110 100644\n--- a/config.c\n+++ b/config.c\n@@ -345,6 +345,27 @@ void git_config_push_parameter(const char *text)\n \tstrbuf_release(&env);\n }\n \n+void git_config_push_env(const char *spec)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *env_name;\n+\tconst char *env_value;\n+\n+\tenv_name = strrchr(spec, '=');\n+\tif (!env_name)\n+\t\tdie(\"invalid config format: %s\", spec);\n+\tenv_name++;\n+\n+\tenv_value = getenv(env_name);\n+\tif (!env_value)\n+\t\tdie(\"config variable missing for '%s'\", env_name);\n+\n+\tstrbuf_add(&buf, spec, env_name - spec);\n+\tstrbuf_addstr(&buf, env_value);\n+\tgit_config_push_parameter(buf.buf);\n+\tstrbuf_release(&buf);\n+}\n+\n static inline int iskeychar(int c)\n {\n \treturn isalnum(c) || c == '-';\ndiff --git a/config.h b/config.h\nindex c1449bb790..19a9adbaa9 100644\n--- a/config.h\n+++ b/config.h\n@@ -138,6 +138,7 @@ int git_config_from_mem(config_fn_t fn,\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     const struct object_id *oid, void *data);\n void git_config_push_parameter(const char *text);\n+void git_config_push_env(const char *spec);\n int git_config_from_parameters(config_fn_t fn, void *data);\n void read_early_config(config_fn_t cb, void *data);\n void read_very_early_config(config_fn_t cb, void *data);\ndiff --git a/git.c b/git.c\nindex 5a8ff12f87..b5f63d346b 100644\n--- a/git.c\n+++ b/git.c\n@@ -29,7 +29,7 @@ const char git_usage_string[] =\n \t   \"           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\\n\"\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\\n\"\n \t   \"           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\\n\"\n-\t   \"           [--super-prefix=<path>]\\n\"\n+\t   \"           [--super-prefix=<path>] [--config-env=<name>=<envvar>]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n@@ -255,6 +255,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tgit_config_push_parameter((*argv)[1]);\n \t\t\t(*argv)++;\n \t\t\t(*argc)--;\n+\t\t} else if (skip_prefix(cmd, \"--config-env=\", &cmd)) {\n+\t\t\tgit_config_push_env(cmd);\n \t\t} else if (!strcmp(cmd, \"--literal-pathspecs\")) {\n \t\t\tsetenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, \"1\", 1);\n \t\t\tif (envchanged)\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 97a04c6cc2..46a94814d5 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1316,6 +1316,51 @@ test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n \t\tgit config --get-regexp \"env.*\"\n '\n \n+test_expect_success 'git --config-env=key=envvar support' '\n+\tcat >expect <<-\\EOF &&\n+\tvalue\n+\tvalue\n+\tfalse\n+\tEOF\n+\t{\n+\t\tenv ENVVAR=value git --config-env=core.name=ENVVAR config core.name &&\n+\t\tenv ENVVAR=value git --config-env=foo.CamelCase=ENVVAR config foo.camelcase &&\n+\t\tenv ENVVAR= git --config-env=foo.flag=ENVVAR config --bool foo.flag\n+\t} >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git --config-env fails with invalid parameters' '\n+\ttest_must_fail git --config-env=foo.flag config --bool foo.flag 2>error &&\n+\ttest_i18ngrep \"invalid config format\" error &&\n+\ttest_must_fail git --config-env=foo.flag=NONEXISTENT config --bool foo.flag 2>error &&\n+\ttest_i18ngrep \"config variable missing\" error\n+'\n+\n+test_expect_success 'git -c and --config-env work together' '\n+\tcat >expect <<-\\EOF &&\n+\tbar.cmd cmd-value\n+\tbar.env env-value\n+\tEOF\n+\tenv ENVVAR=env-value git \\\n+\t\t-c bar.cmd=cmd-value \\\n+\t\t--config-env=bar.env=ENVVAR \\\n+\t\tconfig --get-regexp \"^bar.*\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git -c and --config-env override each other' '\n+\tcat >expect <<-\\EOF &&\n+\tenv\n+\tcmd\n+\tEOF\n+\t{\n+\t\tenv ENVVAR=env git -c bar.bar=cmd --config-env=bar.bar=ENVVAR config bar.bar &&\n+\t\tenv ENVVAR=env git --config-env=bar.bar=ENVVAR -c bar.bar=cmd config bar.bar\n+\t} >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n-- \n2.29.2\n\n"},{"id":"412345","messageId":"8c6cdd57a0f92442c3cbd5317af06586cda8d411.1608104755.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1608104755.git.ps@pks.im","subject":"[PATCH v5 4/8] config: extract function to parse config pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-16T07:54:26Z","receivedAt":"2020-12-16T07:55:17Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `git_config_parse_parameter` is responsible for parsing a\n`foo.bar=baz`-formatted configuration key, sanitizing the key and then\nprocessing it via the given callback function. Given that we're about to\nadd a second user which is going to process keys which already has keys\nand values separated, this commit extracts a function\n`config_parse_pair` which only does the sanitization and processing\npart as a preparatory step.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.c | 24 +++++++++++++++++-------\n 1 file changed, 17 insertions(+), 7 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex cde3511110..151980e5c9 100644\n--- a/config.c\n+++ b/config.c\n@@ -458,11 +458,26 @@ int git_config_key_is_valid(const char *key)\n \treturn !git_config_parse_key_1(key, NULL, NULL, 1);\n }\n \n+static int config_parse_pair(const char *key, const char *value,\n+\t\t\t  config_fn_t fn, void *data)\n+{\n+\tchar *canonical_name;\n+\tint ret;\n+\n+\tif (!strlen(key))\n+\t\treturn error(_(\"empty config key\"));\n+\tif (git_config_parse_key(key, &canonical_name, NULL))\n+\t\treturn -1;\n+\n+\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n+\tfree(canonical_name);\n+\treturn ret;\n+}\n+\n int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n \tconst char *value;\n-\tchar *canonical_name;\n \tstruct strbuf **pair;\n \tint ret;\n \n@@ -483,12 +498,7 @@ int git_config_parse_parameter(const char *text,\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \t}\n \n-\tif (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {\n-\t\tret = -1;\n-\t} else {\n-\t\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n-\t\tfree(canonical_name);\n-\t}\n+\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n \tstrbuf_list_free(pair);\n \treturn ret;\n }\n-- \n2.29.2\n\n"},{"id":"412346","messageId":"dfceffd8d4fbc3c99cfa7c5d838e4c3a2db6598a.1608104755.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1608104755.git.ps@pks.im","subject":"[PATCH v5 8/8] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-16T07:54:48Z","receivedAt":"2020-12-16T07:55:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While we currently have the `GIT_CONFIG_PARAMETERS` environment variable\nwhich can be used to pass runtime configuration data to git processes,\nit's an internal implementation detail and not supposed to be used by\nend users.\n\nNext to being for internal use only, this way of passing config entries\nhas a major downside: the config keys need to be parsed as they contain\nboth key and value in a single variable. As such, it is left to the user\nto escape any potentially harmful characters in the value, which is\nquite hard to do if values are controlled by a third party.\n\nThis commit thus adds a new way of adding config entries via the\nenvironment which gets rid of this shortcoming. If the user passes the\n`GIT_CONFIG_COUNT=$n` environment variable, Git will parse environment\nvariable pairs `GIT_CONFIG_KEY_$i` and `GIT_CONFIG_VALUE_$i` for each\n`i` in `[0,n)`.\n\nWhile the same can be achieved with `git -c <name>=<value>`, one may\nwish to not do so for potentially sensitive information. E.g. if one\nwants to set `http.extraHeader` to contain an authentication token,\ndoing so via `-c` would trivially leak those credentials via e.g. ps(1),\nwhich typically also shows command arguments.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-config.txt |  16 +++++\n cache.h                      |   1 +\n config.c                     |  67 +++++++++++++++++---\n environment.c                |   1 +\n t/t1300-config.sh            | 115 ++++++++++++++++++++++++++++++++++-\n 5 files changed, 191 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-config.txt b/Documentation/git-config.txt\nindex 0e9351d3cb..72ccea4419 100644\n--- a/Documentation/git-config.txt\n+++ b/Documentation/git-config.txt\n@@ -346,6 +346,22 @@ GIT_CONFIG_NOSYSTEM::\n \n See also <<FILES>>.\n \n+GIT_CONFIG_COUNT::\n+GIT_CONFIG_KEY_<n>::\n+GIT_CONFIG_VALUE_<n>::\n+\tIf GIT_CONFIG_COUNT is set to a positive number, all environment pairs\n+\tGIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> up to that number will be\n+\tadded to the process's runtime configuration. The config pairs are\n+\tzero-indexed. Any missing key or value is treated as an error. An empty\n+\tGIT_CONFIG_COUNT is treated the same as GIT_CONFIG_COUNT=0, namely no\n+\tpairs are processed. These environment variables will override values\n+\tin configuration files, but will be overridden by any explicit options\n+\tpassed via `git -c`.\n+\n+\tThis is useful for cases where you want to spawn multiple git commands\n+\twith a common configuration but cannot depend on a configuration file,\n+\tfor example when writing scripts.\n+\n \n [[EXAMPLES]]\n EXAMPLES\ndiff --git a/cache.h b/cache.h\nindex 8d279bc110..294841fca7 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -472,6 +472,7 @@ static inline enum object_type object_type(unsigned int mode)\n #define TEMPLATE_DIR_ENVIRONMENT \"GIT_TEMPLATE_DIR\"\n #define CONFIG_ENVIRONMENT \"GIT_CONFIG\"\n #define CONFIG_DATA_ENVIRONMENT \"GIT_CONFIG_PARAMETERS\"\n+#define CONFIG_COUNT_ENVIRONMENT \"GIT_CONFIG_COUNT\"\n #define EXEC_PATH_ENVIRONMENT \"GIT_EXEC_PATH\"\n #define CEILING_DIRECTORIES_ENVIRONMENT \"GIT_CEILING_DIRECTORIES\"\n #define NO_REPLACE_OBJECTS_ENVIRONMENT \"GIT_NO_REPLACE_OBJECTS\"\ndiff --git a/config.c b/config.c\nindex 60a7261807..1742aefa3e 100644\n--- a/config.c\n+++ b/config.c\n@@ -8,6 +8,7 @@\n #include \"cache.h\"\n #include \"branch.h\"\n #include \"config.h\"\n+#include \"environment.h\"\n #include \"repository.h\"\n #include \"lockfile.h\"\n #include \"exec-cmd.h\"\n@@ -594,23 +595,73 @@ static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n \n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n-\tconst char *env = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tconst char *env;\n+\tstruct strbuf envvar = STRBUF_INIT;\n+\tstruct strvec to_free = STRVEC_INIT;\n \tint ret = 0;\n-\tchar *envw;\n+\tchar *envw = NULL;\n \tstruct config_source source;\n \n-\tif (!env)\n-\t\treturn 0;\n-\n \tmemset(&source, 0, sizeof(source));\n \tsource.prev = cf;\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n \tcf = &source;\n \n-\t/* sq_dequote will write over it */\n-\tenvw = xstrdup(env);\n-\tret = parse_config_env_list(envw, fn, data);\n+\tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n+\tif (env) {\n+\t\tunsigned long count;\n+\t\tchar *endp;\n+\t\tint i;\n \n+\t\tcount = strtoul(env, &endp, 10);\n+\t\tif (*endp) {\n+\t\t\tret = error(_(\"bogus count in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (count > INT_MAX) {\n+\t\t\tret = error(_(\"too many entries in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (i = 0; i < count; i++) {\n+\t\t\tconst char *key, *value;\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_KEY_%d\", i);\n+\t\t\tkey = getenv_safe(&to_free, envvar.buf);\n+\t\t\tif (!key) {\n+\t\t\t\tret = error(_(\"missing config key %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_VALUE_%d\", i);\n+\t\t\tvalue = getenv_safe(&to_free, envvar.buf);\n+\t\t\tif (!value) {\n+\t\t\t\tret = error(_(\"missing config value %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tenv = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tif (env) {\n+\t\t/* sq_dequote will write over it */\n+\t\tenvw = xstrdup(env);\n+\t\tif (parse_config_env_list(envw, fn, data) < 0) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+out:\n+\tstrbuf_release(&envvar);\n+\tstrvec_clear(&to_free);\n \tfree(envw);\n \tcf = source.prev;\n \treturn ret;\ndiff --git a/environment.c b/environment.c\nindex 2234af462c..2f27008424 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -117,6 +117,7 @@ const char * const local_repo_env[] = {\n \tALTERNATE_DB_ENVIRONMENT,\n \tCONFIG_ENVIRONMENT,\n \tCONFIG_DATA_ENVIRONMENT,\n+\tCONFIG_COUNT_ENVIRONMENT,\n \tDB_ENVIRONMENT,\n \tGIT_DIR_ENVIRONMENT,\n \tGIT_WORK_TREE_ENVIRONMENT,\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 35a1a6e8b1..e06961767f 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1421,6 +1421,117 @@ test_expect_success '--config-env handles keys with equals' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'git config handles environment config pairs' '\n+\tGIT_CONFIG_COUNT=2 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"foo\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"bar\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one foo\n+\tpair.two bar\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs without count' '\n+\ttest_must_fail env GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one 2>error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one\n+'\n+\n+test_expect_success 'git config ignores pairs exceeding count' '\n+\tGIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"value\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with empty count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT= GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config fails with invalid count' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=10a git config --list 2>error &&\n+\ttest_i18ngrep \"bogus count\" error &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=9999999999999999 git config --list 2>error &&\n+\ttest_i18ngrep \"too many entries\" error\n+'\n+\n+test_expect_success 'git config fails with missing config key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config key\" error\n+'\n+\n+test_expect_success 'git config fails with missing config value' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=\"pair.one\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config value\" error\n+'\n+\n+test_expect_success 'git config fails with invalid config pair key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0= GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=missing-section GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list\n+'\n+\n+test_expect_success 'environment overrides config file' '\n+\ttest_when_finished \"rm -f .git/config\" &&\n+\tcat >.git/config <<-EOF &&\n+\t[pair]\n+\tone = value\n+\tEOF\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=override \\\n+\t\tgit config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'GIT_CONFIG_PARAMETERS overrides environment config' '\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n+\t\tGIT_CONFIG_PARAMETERS=\"${SQ}pair.one=override${SQ}\" \\\n+\t\tgit config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'command line overrides environment config' '\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n+\t\tgit -c pair.one=override config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n@@ -1766,9 +1877,11 @@ test_expect_success '--show-origin with --list' '\n \tfile:.git/config\tuser.override=local\n \tfile:.git/config\tinclude.path=../include/relative.include\n \tfile:.git/../include/relative.include\tuser.relative=include\n+\tcommand line:\tuser.environ=true\n \tcommand line:\tuser.cmdline=true\n \tEOF\n-\tgit -c user.cmdline=true config --list --show-origin >output &&\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=user.environ GIT_CONFIG_VALUE_0=true\\\n+\t\tgit -c user.cmdline=true config --list --show-origin >output &&\n \ttest_cmp expect output\n '\n \n-- \n2.29.2\n\n"},{"id":"412347","messageId":"2f51a0c5fca7da1a6d563da6c9747f862ba0700a.1608104755.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1608104755.git.ps@pks.im","subject":"[PATCH v5 7/8] environment: make `getenv_safe()` a public function","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-16T07:54:40Z","receivedAt":"2020-12-16T07:56:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `getenv_safe()` helper function helps to safely retrieve multiple\nenvironment values without the need to depend on platform-specific\nbehaviour for the return value's lifetime. We'll make use of this\nfunction in a following patch, so let's make it available by making it\nnon-static and adding a declaration.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n environment.c |  7 ++-----\n environment.h | 12 ++++++++++++\n 2 files changed, 14 insertions(+), 5 deletions(-)\n create mode 100644 environment.h\n\ndiff --git a/environment.c b/environment.c\nindex bb518c61cd..2234af462c 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -9,6 +9,7 @@\n  */\n #include \"cache.h\"\n #include \"branch.h\"\n+#include \"environment.h\"\n #include \"repository.h\"\n #include \"config.h\"\n #include \"refs.h\"\n@@ -152,11 +153,7 @@ static char *expand_namespace(const char *raw_namespace)\n \treturn strbuf_detach(&buf, NULL);\n }\n \n-/*\n- * Wrapper of getenv() that returns a strdup value. This value is kept\n- * in argv to be freed later.\n- */\n-static const char *getenv_safe(struct strvec *argv, const char *name)\n+const char *getenv_safe(struct strvec *argv, const char *name)\n {\n \tconst char *value = getenv(name);\n \ndiff --git a/environment.h b/environment.h\nnew file mode 100644\nindex 0000000000..d438b5c8f3\n--- /dev/null\n+++ b/environment.h\n@@ -0,0 +1,12 @@\n+#ifndef ENVIRONMENT_H\n+#define ENVIRONMENT_H\n+\n+#include \"strvec.h\"\n+\n+/*\n+ * Wrapper of getenv() that returns a strdup value. This value is kept\n+ * in argv to be freed later.\n+ */\n+const char *getenv_safe(struct strvec *argv, const char *name);\n+\n+#endif\n-- \n2.29.2\n\n"},{"id":"412349","messageId":"d832f3dedf5bde4cd9389ddab734703ff2dbd5a1.1608104755.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1608104755.git.ps@pks.im","subject":"[PATCH v5 6/8] config: parse more robust format in GIT_CONFIG_PARAMETERS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-16T07:57:07Z","receivedAt":"2020-12-16T07:58:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Jeff King <peff@peff.net>\n\nWhen we stuff config options into GIT_CONFIG_PARAMETERS, we shell-quote\neach one as a single unit, like:\n\n  'section.one=value1' 'section.two=value2'\n\nOn the reading side, we de-quote to get the individual strings, and then\nparse them by splitting on the first \"=\" we find. This format is\nambiguous, because an \"=\" may appear in a subsection. So the config\nrepresented in a file by both:\n\n  [section \"subsection=with=equals\"]\n  key = value\n\nand:\n\n  [section]\n  subsection = with=equals.key=value\n\nends up in this flattened format like:\n\n  'section.subsection=with=equals.key=value'\n\nand we can't tell which was desired. We have traditionally resolved this\nby taking the first \"=\" we see starting from the left, meaning that we\nallowed arbitrary content in the value, but not in the subsection.\n\nLet's make our environment format a bit more robust by separately\nquoting the key and value. That turns those examples into:\n\n  'section.subsection=with=equals.key'='value'\n\nand:\n\n  'section.subsection'='with=equals.key=value'\n\nrespectively, and we can tell the difference between them. We can detect\nwhich format is in use for any given element of the list based on the\npresence of the unquoted \"=\". That means we can continue to allow the\nold format to work to support any callers which manually used the old\nformat, and we can even intermingle the two formats. The old format\nwasn't documented, and nobody was supposed to be using it. But it's\nlikely that such callers exist in the wild, so it's nice if we can avoid\nbreaking them. Likewise, it may be possible to trigger an older version\nof \"git -c\" that runs a script that calls into a newer version of \"git\n-c\"; that new version would see the intermingled format.\n\nThis does create one complication, which is that the obvious format in\nthe new scheme for\n\n  [section]\n  some-bool\n\nis:\n\n  'section.some-bool'\n\nwith no equals. We'd mistake that for an old-style variable. And it even\nhas the same meaning in the old style, but:\n\n  [section \"with=equals\"]\n  some-bool\n\ndoes not. It would be:\n\n  'section.with=equals=some-bool'\n\nwhich we'd take to mean:\n\n  [section]\n  with = equals=some-bool\n\nin the old, ambiguous style. Likewise, we can't use:\n\n  'section.some-bool'=''\n\nbecause that's ambiguous with an actual empty string. Instead, we'll\nagain use the shell-quoting to give us a hint, and use:\n\n  'section.some-bool'=\n\nto show that we have no value.\n\nNote that this commit just expands the reading side. We'll start writing\nthe new format via \"git -c\" in a future patch. In the meantime, the\nexisting \"git -c\" tests will make sure we didn't break reading the old\nformat. But we'll also add some explicit coverage of the two formats to\nmake sure we continue to handle the old one after we move the writing\nside over.\n\nAnd one final note: since we're now using the shell-quoting as a\nsemantically meaningful hint, this closes the door to us ever allowing\narbitrary shell quoting, like:\n\n  'a'shell'would'be'ok'with'this'.key=value\n\nBut we have never supported that (only what sq_quote() would produce),\nand we are probably better off keeping things simple, robust, and\nbackwards-compatible, than trying to make it easier for humans. We'll\ncontinue not to advertise the format of the variable to users, and\ninstead keep \"git -c\" as the recommended mechanism for setting config\n(even if we are trying to be kind not to break users who may be relying\non the current undocumented format).\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n config.c          | 69 +++++++++++++++++++++++++++++++++++------------\n t/t1300-config.sh | 52 +++++++++++++++++++++++++++++++++++\n 2 files changed, 104 insertions(+), 17 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 53ed048689..60a7261807 100644\n--- a/config.c\n+++ b/config.c\n@@ -541,14 +541,62 @@ int git_config_parse_parameter(const char *text,\n \treturn ret;\n }\n \n+static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n+{\n+\tchar *cur = env;\n+\twhile (cur && *cur) {\n+\t\tconst char *key = sq_dequote_step(cur, &cur);\n+\t\tif (!key)\n+\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\n+\t\tif (!cur || isspace(*cur)) {\n+\t\t\t/* old-style 'key=value' */\n+\t\t\tif (git_config_parse_parameter(key, fn, data) < 0)\n+\t\t\t\treturn -1;\n+\t\t}\n+\t\telse if (*cur == '=') {\n+\t\t\t/* new-style 'key'='value' */\n+\t\t\tconst char *value;\n+\n+\t\t\tcur++;\n+\t\t\tif (*cur == '\\'') {\n+\t\t\t\t/* quoted value */\n+\t\t\t\tvalue = sq_dequote_step(cur, &cur);\n+\t\t\t\tif (!value || (cur && !isspace(*cur))) {\n+\t\t\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t\t\t}\n+\t\t\t} else if (!*cur || isspace(*cur)) {\n+\t\t\t\t/* implicit bool: 'key'= */\n+\t\t\t\tvalue = NULL;\n+\t\t\t} else {\n+\t\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t\t}\n+\n+\t\t\tif (config_parse_pair(key, value, fn, data) < 0)\n+\t\t\t\treturn -1;\n+\t\t}\n+\t\telse {\n+\t\t\t/* unknown format */\n+\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t}\n+\n+\t\tif (cur) {\n+\t\t\twhile (isspace(*cur))\n+\t\t\t\tcur++;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n \tconst char *env = getenv(CONFIG_DATA_ENVIRONMENT);\n \tint ret = 0;\n \tchar *envw;\n-\tconst char **argv = NULL;\n-\tint nr = 0, alloc = 0;\n-\tint i;\n \tstruct config_source source;\n \n \tif (!env)\n@@ -561,21 +609,8 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \n \t/* sq_dequote will write over it */\n \tenvw = xstrdup(env);\n+\tret = parse_config_env_list(envw, fn, data);\n \n-\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n-\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n-\t\tgoto out;\n-\t}\n-\n-\tfor (i = 0; i < nr; i++) {\n-\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n-\t\t\tret = -1;\n-\t\t\tgoto out;\n-\t\t}\n-\t}\n-\n-out:\n-\tfree(argv);\n \tfree(envw);\n \tcf = source.prev;\n \treturn ret;\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 36a60879f6..35a1a6e8b1 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1294,6 +1294,58 @@ test_expect_success 'git -c is not confused by empty environment' '\n \tGIT_CONFIG_PARAMETERS=\"\" git -c x.one=1 config --list\n '\n \n+test_expect_success 'GIT_CONFIG_PARAMETERS handles old-style entries' '\n+\tv=\"${SQ}key.one=foo${SQ}\" &&\n+\tv=\"$v  ${SQ}key.two=bar${SQ}\" &&\n+\tv=\"$v ${SQ}key.ambiguous=section.whatever=value${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.one foo\n+\tkey.two bar\n+\tkey.ambiguous section.whatever=value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'GIT_CONFIG_PARAMETERS handles new-style entries' '\n+\tv=\"${SQ}key.one${SQ}=${SQ}foo${SQ}\" &&\n+\tv=\"$v  ${SQ}key.two${SQ}=${SQ}bar${SQ}\" &&\n+\tv=\"$v ${SQ}key.ambiguous=section.whatever${SQ}=${SQ}value${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.one foo\n+\tkey.two bar\n+\tkey.ambiguous=section.whatever value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'old and new-style entries can mix' '\n+\tv=\"${SQ}key.oldone=oldfoo${SQ}\" &&\n+\tv=\"$v ${SQ}key.newone${SQ}=${SQ}newfoo${SQ}\" &&\n+\tv=\"$v ${SQ}key.oldtwo=oldbar${SQ}\" &&\n+\tv=\"$v ${SQ}key.newtwo${SQ}=${SQ}newbar${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.oldone oldfoo\n+\tkey.newone newfoo\n+\tkey.oldtwo oldbar\n+\tkey.newtwo newbar\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'old and new bools with ambiguous subsection' '\n+\tv=\"${SQ}key.with=equals.oldbool${SQ}\" &&\n+\tv=\"$v ${SQ}key.with=equals.newbool${SQ}=\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.with equals.oldbool\n+\tkey.with=equals.newbool\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n \tcat >expect <<-\\EOF &&\n \tenv.one one\n-- \n2.29.2\n\n"},{"id":"412348","messageId":"5729f5d406311ec139b1827fc2419255e296921a.1608104755.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1608104755.git.ps@pks.im","subject":"[PATCH v5 3/8] quote: make sq_dequote_step() a public function","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-16T07:56:48Z","receivedAt":"2020-12-16T07:58:14Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Jeff King <peff@peff.net>\n\nWe provide a function for dequoting an entire string, as well as one for\nhandling a space-separated list of quoted strings. But there's no way\nfor a caller to parse a string like 'foo'='bar', even though it is easy\nto generate one using sq_quote_buf() or similar.\n\nLet's make the single-step function available to callers outside of\nquote.c. Note that we do need to adjust its implementation slightly: it\ninsists on seeing whitespace between items, and we'd like to be more\nflexible than that. Since it only has a single caller, we can move that\ncheck (and slurping up any extra whitespace) into that caller.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n quote.c | 15 ++++++++++-----\n quote.h | 18 ++++++++++++++++--\n 2 files changed, 26 insertions(+), 7 deletions(-)\n\ndiff --git a/quote.c b/quote.c\nindex 69f4ca45da..8a3a5e39eb 100644\n--- a/quote.c\n+++ b/quote.c\n@@ -116,7 +116,7 @@ void sq_append_quote_argv_pretty(struct strbuf *dst, const char **argv)\n \t}\n }\n \n-static char *sq_dequote_step(char *arg, char **next)\n+char *sq_dequote_step(char *arg, char **next)\n {\n \tchar *dst = arg;\n \tchar *src = arg;\n@@ -153,11 +153,8 @@ static char *sq_dequote_step(char *arg, char **next)\n \t\t\t}\n \t\t/* Fallthrough */\n \t\tdefault:\n-\t\t\tif (!next || !isspace(*src))\n+\t\t\tif (!next)\n \t\t\t\treturn NULL;\n-\t\t\tdo {\n-\t\t\t\tc = *++src;\n-\t\t\t} while (isspace(c));\n \t\t\t*dst = 0;\n \t\t\t*next = src;\n \t\t\treturn arg;\n@@ -182,6 +179,14 @@ static int sq_dequote_to_argv_internal(char *arg,\n \t\tchar *dequoted = sq_dequote_step(next, &next);\n \t\tif (!dequoted)\n \t\t\treturn -1;\n+\t\tif (next) {\n+\t\t\tchar c;\n+\t\t\tif (!isspace(*next))\n+\t\t\t\treturn -1;\n+\t\t\tdo {\n+\t\t\t\tc = *++next;\n+\t\t\t} while (isspace(c));\n+\t\t}\n \t\tif (argv) {\n \t\t\tALLOC_GROW(*argv, *nr + 1, *alloc);\n \t\t\t(*argv)[(*nr)++] = dequoted;\ndiff --git a/quote.h b/quote.h\nindex 4b72a583cf..768cc6338e 100644\n--- a/quote.h\n+++ b/quote.h\n@@ -42,12 +42,26 @@ void sq_quote_buf_pretty(struct strbuf *, const char *src);\n void sq_quote_argv_pretty(struct strbuf *, const char **argv);\n void sq_append_quote_argv_pretty(struct strbuf *dst, const char **argv);\n \n-/* This unwraps what sq_quote() produces in place, but returns\n+/*\n+ * This unwraps what sq_quote() produces in place, but returns\n  * NULL if the input does not look like what sq_quote would have\n- * produced.\n+ * produced (the full string must be a single quoted item).\n  */\n char *sq_dequote(char *);\n \n+/*\n+ * Like sq_dequote(), but dequote a single item, and leave \"next\" pointing to\n+ * the next character. E.g., in the string:\n+ *\n+ *   'one' 'two' 'three'\n+ *\n+ * after the first call, the return value would be the unquoted string \"one\",\n+ * with \"next\" pointing to the space between \"one\" and \"two\"). The caller is\n+ * responsible for advancing the pointer to the start of the next item before\n+ * calling sq_dequote_step() again.\n+ */\n+char *sq_dequote_step(char *src, char **next);\n+\n /*\n  * Same as the above, but can be used to unwrap many arguments in the\n  * same string separated by space. Like sq_quote, it works in place,\n-- \n2.29.2\n\n"},{"id":"412350","messageId":"ff96e59e7902419dfb76ab812bc2d6bf493109b3.1608104755.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1608104755.git.ps@pks.im","subject":"[PATCH v5 5/8] config: store \"git -c\" variables using more robust format","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2020-12-16T07:56:58Z","receivedAt":"2020-12-16T07:58:14Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Jeff King <peff@peff.net>\n\nThe previous commit added a new format for $GIT_CONFIG_PARAMETERS which\nis able to robustly handle subsections with \"=\" in them. Let's start\nwriting the new format. Unfortunately, this does much less than you'd\nhope, because \"git -c\" itself has the same ambiguity problem! But it's\nstill worth doing:\n\n  - we've now pushed the problem from the inter-process communication\n    into the \"-c\" command-line parser. This would free us up to later\n    add an unambiguous format there (e.g., separate arguments like \"git\n    --config key value\", etc).\n\n  - for --config-env, the parser already disallows \"=\" in the\n    environment variable name. So:\n\n      git --config-env section.with=equals.key=ENVVAR\n\n    will robustly set section.with=equals.key to the contents of\n    $ENVVAR.\n\nThe new test shows the improvement for --config-env.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n config.c          | 52 ++++++++++++++++++++++++++++++++++++++++-------\n t/t1300-config.sh |  8 ++++++++\n 2 files changed, 53 insertions(+), 7 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 151980e5c9..53ed048689 100644\n--- a/config.c\n+++ b/config.c\n@@ -332,7 +332,7 @@ int git_config_include(const char *var, const char *value, void *data)\n \treturn ret;\n }\n \n-void git_config_push_parameter(const char *text)\n+static void git_config_push_split_parameter(const char *key, const char *value)\n {\n \tstruct strbuf env = STRBUF_INIT;\n \tconst char *old = getenv(CONFIG_DATA_ENVIRONMENT);\n@@ -340,30 +340,68 @@ void git_config_push_parameter(const char *text)\n \t\tstrbuf_addstr(&env, old);\n \t\tstrbuf_addch(&env, ' ');\n \t}\n-\tsq_quote_buf(&env, text);\n+\tsq_quote_buf(&env, key);\n+\tstrbuf_addch(&env, '=');\n+\tif (value)\n+\t\tsq_quote_buf(&env, value);\n \tsetenv(CONFIG_DATA_ENVIRONMENT, env.buf, 1);\n \tstrbuf_release(&env);\n }\n \n+void git_config_push_parameter(const char *text)\n+{\n+\tconst char *value;\n+\n+\t/*\n+\t * When we see:\n+\t *\n+\t *   section.subsection=with=equals.key=value\n+\t *\n+\t * we cannot tell if it means:\n+\t *\n+\t *   [section \"subsection=with=equals\"]\n+\t *   key = value\n+\t *\n+\t * or:\n+\t *\n+\t *   [section]\n+\t *   subsection = with=equals.key=value\n+\t *\n+\t * We parse left-to-right for the first \"=\", meaning we'll prefer to\n+\t * keep the value intact over the subsection. This is historical, but\n+\t * also sensible since values are more likely to contain odd or\n+\t * untrusted input than a section name.\n+\t *\n+\t * A missing equals is explicitly allowed (as a bool-only entry).\n+\t */\n+\tvalue = strchr(text, '=');\n+\tif (value) {\n+\t\tchar *key = xmemdupz(text, value - text);\n+\t\tgit_config_push_split_parameter(key, value + 1);\n+\t\tfree(key);\n+\t} else {\n+\t\tgit_config_push_split_parameter(text, NULL);\n+\t}\n+}\n+\n void git_config_push_env(const char *spec)\n {\n-\tstruct strbuf buf = STRBUF_INIT;\n+\tchar *key;\n \tconst char *env_name;\n \tconst char *env_value;\n \n \tenv_name = strrchr(spec, '=');\n \tif (!env_name)\n \t\tdie(\"invalid config format: %s\", spec);\n+\tkey = xmemdupz(spec, env_name - spec);\n \tenv_name++;\n \n \tenv_value = getenv(env_name);\n \tif (!env_value)\n \t\tdie(\"config variable missing for '%s'\", env_name);\n \n-\tstrbuf_add(&buf, spec, env_name - spec);\n-\tstrbuf_addstr(&buf, env_value);\n-\tgit_config_push_parameter(buf.buf);\n-\tstrbuf_release(&buf);\n+\tgit_config_push_split_parameter(key, env_value);\n+\tfree(key);\n }\n \n static inline int iskeychar(int c)\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 46a94814d5..36a60879f6 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1361,6 +1361,14 @@ test_expect_success 'git -c and --config-env override each other' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--config-env handles keys with equals' '\n+\techo value=with=equals >expect &&\n+\tENVVAR=value=with=equals git \\\n+\t\t--config-env=section.subsection=with=equals.key=ENVVAR \\\n+\t\tconfig section.subsection=with=equals.key >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n-- \n2.29.2\n\n"},{"id":"412426","messageId":"ccb476b8-9835-3810-c272-b74822fe74eb@gmail.com","threadId":"54704","inReplyTo":"d832f3dedf5bde4cd9389ddab734703ff2dbd5a1.1608104755.git.ps@pks.im","subject":"Re: [PATCH v5 6/8] config: parse more robust format in GIT_CONFIG_PARAMETERS","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2020-12-16T20:01:41Z","receivedAt":"2020-12-16T20:02:47Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Patrick/Peff\n\nOn 16/12/2020 07:57, Patrick Steinhardt wrote:\n> From: Jeff King <peff@peff.net>\n> \n> When we stuff config options into GIT_CONFIG_PARAMETERS, we shell-quote\n> each one as a single unit, like:\n> \n>    'section.one=value1' 'section.two=value2'\n> \n> On the reading side, we de-quote to get the individual strings, and then\n> parse them by splitting on the first \"=\" we find. This format is\n> ambiguous, because an \"=\" may appear in a subsection. So the config\n> represented in a file by both:\n> \n>    [section \"subsection=with=equals\"]\n>    key = value\n> \n> and:\n> \n>    [section]\n>    subsection = with=equals.key=value\n> \n> ends up in this flattened format like:\n> \n>    'section.subsection=with=equals.key=value'\n> \n> and we can't tell which was desired. We have traditionally resolved this\n> by taking the first \"=\" we see starting from the left, meaning that we\n> allowed arbitrary content in the value, but not in the subsection.\n\nI was just wondering what happens if a subsection name contains a single \nquote - can we handle that now and how is it affected by this change?\n\nBest Wishes\n\nPhillip\n\n> Let's make our environment format a bit more robust by separately\n> quoting the key and value. That turns those examples into:\n> \n>    'section.subsection=with=equals.key'='value'\n> \n> and:\n> \n>    'section.subsection'='with=equals.key=value'\n> \n> respectively, and we can tell the difference between them. We can detect\n> which format is in use for any given element of the list based on the\n> presence of the unquoted \"=\". That means we can continue to allow the\n> old format to work to support any callers which manually used the old\n> format, and we can even intermingle the two formats. The old format\n> wasn't documented, and nobody was supposed to be using it. But it's\n> likely that such callers exist in the wild, so it's nice if we can avoid\n> breaking them. Likewise, it may be possible to trigger an older version\n> of \"git -c\" that runs a script that calls into a newer version of \"git\n> -c\"; that new version would see the intermingled format.\n> \n> This does create one complication, which is that the obvious format in\n> the new scheme for\n> \n>    [section]\n>    some-bool\n> \n> is:\n> \n>    'section.some-bool'\n> \n> with no equals. We'd mistake that for an old-style variable. And it even\n> has the same meaning in the old style, but:\n> \n>    [section \"with=equals\"]\n>    some-bool\n> \n> does not. It would be:\n> \n>    'section.with=equals=some-bool'\n> \n> which we'd take to mean:\n> \n>    [section]\n>    with = equals=some-bool\n> \n> in the old, ambiguous style. Likewise, we can't use:\n> \n>    'section.some-bool'=''\n> \n> because that's ambiguous with an actual empty string. Instead, we'll\n> again use the shell-quoting to give us a hint, and use:\n> \n>    'section.some-bool'=\n> \n> to show that we have no value.\n> \n> Note that this commit just expands the reading side. We'll start writing\n> the new format via \"git -c\" in a future patch. In the meantime, the\n> existing \"git -c\" tests will make sure we didn't break reading the old\n> format. But we'll also add some explicit coverage of the two formats to\n> make sure we continue to handle the old one after we move the writing\n> side over.\n> \n> And one final note: since we're now using the shell-quoting as a\n> semantically meaningful hint, this closes the door to us ever allowing\n> arbitrary shell quoting, like:\n> \n>    'a'shell'would'be'ok'with'this'.key=value\n> \n> But we have never supported that (only what sq_quote() would produce),\n> and we are probably better off keeping things simple, robust, and\n> backwards-compatible, than trying to make it easier for humans. We'll\n> continue not to advertise the format of the variable to users, and\n> instead keep \"git -c\" as the recommended mechanism for setting config\n> (even if we are trying to be kind not to break users who may be relying\n> on the current undocumented format).\n> \n> Signed-off-by: Jeff King <peff@peff.net>\n> ---\n>   config.c          | 69 +++++++++++++++++++++++++++++++++++------------\n>   t/t1300-config.sh | 52 +++++++++++++++++++++++++++++++++++\n>   2 files changed, 104 insertions(+), 17 deletions(-)\n> \n> diff --git a/config.c b/config.c\n> index 53ed048689..60a7261807 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -541,14 +541,62 @@ int git_config_parse_parameter(const char *text,\n>   \treturn ret;\n>   }\n>   \n> +static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n> +{\n> +\tchar *cur = env;\n> +\twhile (cur && *cur) {\n> +\t\tconst char *key = sq_dequote_step(cur, &cur);\n> +\t\tif (!key)\n> +\t\t\treturn error(_(\"bogus format in %s\"),\n> +\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n> +\n> +\t\tif (!cur || isspace(*cur)) {\n> +\t\t\t/* old-style 'key=value' */\n> +\t\t\tif (git_config_parse_parameter(key, fn, data) < 0)\n> +\t\t\t\treturn -1;\n> +\t\t}\n> +\t\telse if (*cur == '=') {\n> +\t\t\t/* new-style 'key'='value' */\n> +\t\t\tconst char *value;\n> +\n> +\t\t\tcur++;\n> +\t\t\tif (*cur == '\\'') {\n> +\t\t\t\t/* quoted value */\n> +\t\t\t\tvalue = sq_dequote_step(cur, &cur);\n> +\t\t\t\tif (!value || (cur && !isspace(*cur))) {\n> +\t\t\t\t\treturn error(_(\"bogus format in %s\"),\n> +\t\t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n> +\t\t\t\t}\n> +\t\t\t} else if (!*cur || isspace(*cur)) {\n> +\t\t\t\t/* implicit bool: 'key'= */\n> +\t\t\t\tvalue = NULL;\n> +\t\t\t} else {\n> +\t\t\t\treturn error(_(\"bogus format in %s\"),\n> +\t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n> +\t\t\t}\n> +\n> +\t\t\tif (config_parse_pair(key, value, fn, data) < 0)\n> +\t\t\t\treturn -1;\n> +\t\t}\n> +\t\telse {\n> +\t\t\t/* unknown format */\n> +\t\t\treturn error(_(\"bogus format in %s\"),\n> +\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n> +\t\t}\n> +\n> +\t\tif (cur) {\n> +\t\t\twhile (isspace(*cur))\n> +\t\t\t\tcur++;\n> +\t\t}\n> +\t}\n> +\treturn 0;\n> +}\n> +\n>   int git_config_from_parameters(config_fn_t fn, void *data)\n>   {\n>   \tconst char *env = getenv(CONFIG_DATA_ENVIRONMENT);\n>   \tint ret = 0;\n>   \tchar *envw;\n> -\tconst char **argv = NULL;\n> -\tint nr = 0, alloc = 0;\n> -\tint i;\n>   \tstruct config_source source;\n>   \n>   \tif (!env)\n> @@ -561,21 +609,8 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n>   \n>   \t/* sq_dequote will write over it */\n>   \tenvw = xstrdup(env);\n> +\tret = parse_config_env_list(envw, fn, data);\n>   \n> -\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n> -\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n> -\t\tgoto out;\n> -\t}\n> -\n> -\tfor (i = 0; i < nr; i++) {\n> -\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n> -\t\t\tret = -1;\n> -\t\t\tgoto out;\n> -\t\t}\n> -\t}\n> -\n> -out:\n> -\tfree(argv);\n>   \tfree(envw);\n>   \tcf = source.prev;\n>   \treturn ret;\n> diff --git a/t/t1300-config.sh b/t/t1300-config.sh\n> index 36a60879f6..35a1a6e8b1 100755\n> --- a/t/t1300-config.sh\n> +++ b/t/t1300-config.sh\n> @@ -1294,6 +1294,58 @@ test_expect_success 'git -c is not confused by empty environment' '\n>   \tGIT_CONFIG_PARAMETERS=\"\" git -c x.one=1 config --list\n>   '\n>   \n> +test_expect_success 'GIT_CONFIG_PARAMETERS handles old-style entries' '\n> +\tv=\"${SQ}key.one=foo${SQ}\" &&\n> +\tv=\"$v  ${SQ}key.two=bar${SQ}\" &&\n> +\tv=\"$v ${SQ}key.ambiguous=section.whatever=value${SQ}\" &&\n> +\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n> +\tcat >expect <<-EOF &&\n> +\tkey.one foo\n> +\tkey.two bar\n> +\tkey.ambiguous section.whatever=value\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'GIT_CONFIG_PARAMETERS handles new-style entries' '\n> +\tv=\"${SQ}key.one${SQ}=${SQ}foo${SQ}\" &&\n> +\tv=\"$v  ${SQ}key.two${SQ}=${SQ}bar${SQ}\" &&\n> +\tv=\"$v ${SQ}key.ambiguous=section.whatever${SQ}=${SQ}value${SQ}\" &&\n> +\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n> +\tcat >expect <<-EOF &&\n> +\tkey.one foo\n> +\tkey.two bar\n> +\tkey.ambiguous=section.whatever value\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'old and new-style entries can mix' '\n> +\tv=\"${SQ}key.oldone=oldfoo${SQ}\" &&\n> +\tv=\"$v ${SQ}key.newone${SQ}=${SQ}newfoo${SQ}\" &&\n> +\tv=\"$v ${SQ}key.oldtwo=oldbar${SQ}\" &&\n> +\tv=\"$v ${SQ}key.newtwo${SQ}=${SQ}newbar${SQ}\" &&\n> +\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n> +\tcat >expect <<-EOF &&\n> +\tkey.oldone oldfoo\n> +\tkey.newone newfoo\n> +\tkey.oldtwo oldbar\n> +\tkey.newtwo newbar\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'old and new bools with ambiguous subsection' '\n> +\tv=\"${SQ}key.with=equals.oldbool${SQ}\" &&\n> +\tv=\"$v ${SQ}key.with=equals.newbool${SQ}=\" &&\n> +\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n> +\tcat >expect <<-EOF &&\n> +\tkey.with equals.oldbool\n> +\tkey.with=equals.newbool\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n>   test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n>   \tcat >expect <<-\\EOF &&\n>   \tenv.one one\n> \n"},{"id":"412952","messageId":"xmqqczz06x83.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"dfceffd8d4fbc3c99cfa7c5d838e4c3a2db6598a.1608104755.git.ps@pks.im","subject":"Re: [PATCH v5 8/8] config: allow specifying config entries via envvar pairs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-23T21:14:52Z","receivedAt":"2020-12-23T21:15:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> While we currently have the `GIT_CONFIG_PARAMETERS` environment variable\n> which can be used to pass runtime configuration data to git processes,\n> it's an internal implementation detail and not supposed to be used by\n> end users.\n>\n> Next to being for internal use only, this way of passing config entries\n> has a major downside: the config keys need to be parsed as they contain\n> both key and value in a single variable. As such, it is left to the user\n> to escape any potentially harmful characters in the value, which is\n> quite hard to do if values are controlled by a third party.\n>\n> This commit thus adds a new way of adding config entries via the\n> environment which gets rid of this shortcoming. If the user passes the\n> `GIT_CONFIG_COUNT=$n` environment variable, Git will parse environment\n> variable pairs `GIT_CONFIG_KEY_$i` and `GIT_CONFIG_VALUE_$i` for each\n> `i` in `[0,n)`.\n>\n> While the same can be achieved with `git -c <name>=<value>`, one may\n> wish to not do so for potentially sensitive information. E.g. if one\n> wants to set `http.extraHeader` to contain an authentication token,\n> doing so via `-c` would trivially leak those credentials via e.g. ps(1),\n> which typically also shows command arguments.\n>\n> Signed-off-by: Patrick Steinhardt <ps@pks.im>\n> ---\n>  Documentation/git-config.txt |  16 +++++\n>  cache.h                      |   1 +\n>  config.c                     |  67 +++++++++++++++++---\n>  environment.c                |   1 +\n>  t/t1300-config.sh            | 115 ++++++++++++++++++++++++++++++++++-\n>  5 files changed, 191 insertions(+), 9 deletions(-)\n>\n> diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt\n> index 0e9351d3cb..72ccea4419 100644\n> --- a/Documentation/git-config.txt\n> +++ b/Documentation/git-config.txt\n> @@ -346,6 +346,22 @@ GIT_CONFIG_NOSYSTEM::\n>  \n>  See also <<FILES>>.\n>  \n> +GIT_CONFIG_COUNT::\n> +GIT_CONFIG_KEY_<n>::\n> +GIT_CONFIG_VALUE_<n>::\n> +\tIf GIT_CONFIG_COUNT is set to a positive number, all environment pairs\n> +\tGIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> up to that number will be\n> +\tadded to the process's runtime configuration. The config pairs are\n> +\tzero-indexed. Any missing key or value is treated as an error. An empty\n> +\tGIT_CONFIG_COUNT is treated the same as GIT_CONFIG_COUNT=0, namely no\n> +\tpairs are processed. These environment variables will override values\n> +\tin configuration files, but will be overridden by any explicit options\n> +\tpassed via `git -c`.\n> +\n> +\tThis is useful for cases where you want to spawn multiple git commands\n> +\twith a common configuration but cannot depend on a configuration file,\n> +\tfor example when writing scripts.\n\nDedent these three lines, and replace the blank lines before it with\na line with a single '+' on it (an example is found in the paragraph\nthat describes the \"--get-color\" option; look for \"type=color\" in\nthe same file).  Otherwise these subsequent paragraphs are treated\ndifferently from the first paragraph.\n\nThe same problem may exist in new paragraphs in git.txt that\ndescribes the \"--config-env\" stuff.\n\nThanks.\n"},{"id":"412953","messageId":"xmqq5z4s6w8w.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"56c9221c4cc8c3e52823938938e3f65a3433f9bf.1608104755.git.ps@pks.im","subject":"Re: [PATCH v5 2/8] config: add new way to pass config via `--config-env`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-23T21:35:59Z","receivedAt":"2020-12-23T21:37:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> +--config-env=<name>=<envvar>::\n> +\tLike `-c <name>=<var>` except the value is the name of an\n> +\tenvironment variable from which to retrieve the value. Unlike\n\nLet's avoid overusing the word \"value\", as it can refer to\n<name>=<envvar> as the whole (which is the value given to\n--config-env), or <envvar> itself (which may appear to the value\ngiven to <name>), or the value in the environment veraible.\n\n\tLike `-c <name>=<value>`, give configuration variable\n\t'<name>' a value, where <envvar> is the name of an\n\tenvironment variable from which to retrieve the\n\tvalue.\n\nor something along that line.\n\n> +\t`-c` there is no shortcut for directly setting the value to an\n> +\tempty string, instead the environment variable itself must be\n> +\tset to the empty strin. Errors if the `<envvar>` does not exist\n\n\tset to the empty string.  It is an error if the ...\n\n> +\tin the environment. `<envvar>` may not contain an equals sign\n> +\tto avoid ambiguity with `<name>`s which contain one.\n\n\twhich may contain one.\n\n> +\tThis is useful for cases where you want to pass transitory\n> +\tconfiguration options to git, but are doing so on OS's where\n> +\tother processes might be able to read your cmdline\n> +\t(e.g. `/proc/self/cmdline`), but not your environ\n> +\t(e.g. `/proc/self/environ`). That behavior is the default on\n> +\tLinux, but may not be on your system.\n> +\n> +\tNote that this might add security for variables such as\n> +\t`http.extraHeader` where the sensitive information is part of\n> +\tthe value, but not e.g. `url.<base.insteadOf` where the\n\n\"url.<base>.insteadOf\"\n\n> +\tsensitive information can be part of the key.\n\nWhen writing multi-paragraph description, the second and later\nparagraphs need to be dedented and the paragraph breaks are denoted\nnot by a blank line but by a line with only a single '+' on it.\n\nI didn't look at the implementation or tests, as I think it hasn't\nchanged since the last round, and the last round was looked at by\nPeff already.\n\nThanks.\n"},{"id":"412954","messageId":"xmqq1rfg6vcz.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"xmqqczz06x83.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 8/8] config: allow specifying config entries via envvar pairs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-23T21:55:08Z","receivedAt":"2020-12-23T21:56:12Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> The same problem may exist in new paragraphs in git.txt that\n> describes the \"--config-env\" stuff.\n\nHere is what I tentatively queued on top of these 8 patches as a fixup.\n\nThanks.\n\n\n Documentation/git-config.txt |  8 ++++----\n Documentation/git.txt        | 29 +++++++++++++++--------------\n 2 files changed, 19 insertions(+), 18 deletions(-)\n\ndiff --git a/Documentation/git-config.txt b/Documentation/git-config.txt\nindex b71c1ac7b8..67eb40f506 100644\n--- a/Documentation/git-config.txt\n+++ b/Documentation/git-config.txt\n@@ -348,10 +348,10 @@ GIT_CONFIG_VALUE_<n>::\n \tpairs are processed. These environment variables will override values\n \tin configuration files, but will be overridden by any explicit options\n \tpassed via `git -c`.\n-\n-\tThis is useful for cases where you want to spawn multiple git commands\n-\twith a common configuration but cannot depend on a configuration file,\n-\tfor example when writing scripts.\n++\n+This is useful for cases where you want to spawn multiple git commands\n+with a common configuration but cannot depend on a configuration file,\n+for example when writing scripts.\n \n \n [[EXAMPLES]]\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex 80fb8fab11..3b0f87a71b 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -81,25 +81,26 @@ foo.bar= ...`) sets `foo.bar` to the empty string which `git config\n --type=bool` will convert to `false`.\n \n --config-env=<name>=<envvar>::\n-\tLike `-c <name>=<var>` except the value is the name of an\n+\tLike `-c <name>=<value>`, give configuration variable\n+\t'<name>' a value, where <envvar> is the name of an\n \tenvironment variable from which to retrieve the value. Unlike\n \t`-c` there is no shortcut for directly setting the value to an\n \tempty string, instead the environment variable itself must be\n-\tset to the empty strin. Errors if the `<envvar>` does not exist\n+\tset to the empty string.  It is an error if the `<envvar>` does not exist\n \tin the environment. `<envvar>` may not contain an equals sign\n \tto avoid ambiguity with `<name>`s which contain one.\n-\n-\tThis is useful for cases where you want to pass transitory\n-\tconfiguration options to git, but are doing so on OS's where\n-\tother processes might be able to read your cmdline\n-\t(e.g. `/proc/self/cmdline`), but not your environ\n-\t(e.g. `/proc/self/environ`). That behavior is the default on\n-\tLinux, but may not be on your system.\n-\n-\tNote that this might add security for variables such as\n-\t`http.extraHeader` where the sensitive information is part of\n-\tthe value, but not e.g. `url.<base.insteadOf` where the\n-\tsensitive information can be part of the key.\n++\n+This is useful for cases where you want to pass transitory\n+configuration options to git, but are doing so on OS's where\n+other processes might be able to read your cmdline\n+(e.g. `/proc/self/cmdline`), but not your environ\n+(e.g. `/proc/self/environ`). That behavior is the default on\n+Linux, but may not be on your system.\n++\n+Note that this might add security for variables such as\n+`http.extraHeader` where the sensitive information is part of\n+the value, but not e.g. `url.<base>.insteadOf` where the\n+sensitive information can be part of the key.\n \n --exec-path[=<path>]::\n \tPath to wherever your core Git programs are installed.\n-- \n2.30.0-rc1-197-ga312a798fc\n\n"},{"id":"413561","messageId":"X/WQsO47uhgvrcaS@ncase","threadId":"54704","inReplyTo":"xmqq1rfg6vcz.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 8/8] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-06T10:28:00Z","receivedAt":"2021-01-06T10:29:35Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Dec 23, 2020 at 01:55:08PM -0800, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > The same problem may exist in new paragraphs in git.txt that\n> > describes the \"--config-env\" stuff.\n> \n> Here is what I tentatively queued on top of these 8 patches as a fixup.\n> \n> Thanks.\n\nYour changes look good to me, thanks!\n\nPatrick\n\n> \n>  Documentation/git-config.txt |  8 ++++----\n>  Documentation/git.txt        | 29 +++++++++++++++--------------\n>  2 files changed, 19 insertions(+), 18 deletions(-)\n> \n> diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt\n> index b71c1ac7b8..67eb40f506 100644\n> --- a/Documentation/git-config.txt\n> +++ b/Documentation/git-config.txt\n> @@ -348,10 +348,10 @@ GIT_CONFIG_VALUE_<n>::\n>  \tpairs are processed. These environment variables will override values\n>  \tin configuration files, but will be overridden by any explicit options\n>  \tpassed via `git -c`.\n> -\n> -\tThis is useful for cases where you want to spawn multiple git commands\n> -\twith a common configuration but cannot depend on a configuration file,\n> -\tfor example when writing scripts.\n> ++\n> +This is useful for cases where you want to spawn multiple git commands\n> +with a common configuration but cannot depend on a configuration file,\n> +for example when writing scripts.\n>  \n>  \n>  [[EXAMPLES]]\n> diff --git a/Documentation/git.txt b/Documentation/git.txt\n> index 80fb8fab11..3b0f87a71b 100644\n> --- a/Documentation/git.txt\n> +++ b/Documentation/git.txt\n> @@ -81,25 +81,26 @@ foo.bar= ...`) sets `foo.bar` to the empty string which `git config\n>  --type=bool` will convert to `false`.\n>  \n>  --config-env=<name>=<envvar>::\n> -\tLike `-c <name>=<var>` except the value is the name of an\n> +\tLike `-c <name>=<value>`, give configuration variable\n> +\t'<name>' a value, where <envvar> is the name of an\n>  \tenvironment variable from which to retrieve the value. Unlike\n>  \t`-c` there is no shortcut for directly setting the value to an\n>  \tempty string, instead the environment variable itself must be\n> -\tset to the empty strin. Errors if the `<envvar>` does not exist\n> +\tset to the empty string.  It is an error if the `<envvar>` does not exist\n>  \tin the environment. `<envvar>` may not contain an equals sign\n>  \tto avoid ambiguity with `<name>`s which contain one.\n> -\n> -\tThis is useful for cases where you want to pass transitory\n> -\tconfiguration options to git, but are doing so on OS's where\n> -\tother processes might be able to read your cmdline\n> -\t(e.g. `/proc/self/cmdline`), but not your environ\n> -\t(e.g. `/proc/self/environ`). That behavior is the default on\n> -\tLinux, but may not be on your system.\n> -\n> -\tNote that this might add security for variables such as\n> -\t`http.extraHeader` where the sensitive information is part of\n> -\tthe value, but not e.g. `url.<base.insteadOf` where the\n> -\tsensitive information can be part of the key.\n> ++\n> +This is useful for cases where you want to pass transitory\n> +configuration options to git, but are doing so on OS's where\n> +other processes might be able to read your cmdline\n> +(e.g. `/proc/self/cmdline`), but not your environ\n> +(e.g. `/proc/self/environ`). That behavior is the default on\n> +Linux, but may not be on your system.\n> ++\n> +Note that this might add security for variables such as\n> +`http.extraHeader` where the sensitive information is part of\n> +the value, but not e.g. `url.<base>.insteadOf` where the\n> +sensitive information can be part of the key.\n>  \n>  --exec-path[=<path>]::\n>  \tPath to wherever your core Git programs are installed.\n> -- \n> 2.30.0-rc1-197-ga312a798fc\n> \n"},{"id":"413606","messageId":"xmqqble1rczk.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"X/WQsO47uhgvrcaS@ncase","subject":"Re: [PATCH v5 8/8] config: allow specifying config entries via envvar pairs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-06T21:07:11Z","receivedAt":"2021-01-06T21:08:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Wed, Dec 23, 2020 at 01:55:08PM -0800, Junio C Hamano wrote:\n>> Junio C Hamano <gitster@pobox.com> writes:\n>> \n>> > The same problem may exist in new paragraphs in git.txt that\n>> > describes the \"--config-env\" stuff.\n>> \n>> Here is what I tentatively queued on top of these 8 patches as a fixup.\n>> \n>> Thanks.\n>\n> Your changes look good to me, thanks!\n\nYou're welcome.  Looking forward to seeing a new round with these\nminor fixes squashed in, so that we do not have a series with known\nbreakages in early parts that are fixed in later steps.\n\nThanks.\n"},{"id":"413646","messageId":"0a9b085fe5e2440f9c94819377985ed83bd80d05.1610001187.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610001187.git.ps@pks.im","subject":"[PATCH v6 4/8] config: extract function to parse config pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-07T06:37:01Z","receivedAt":"2021-01-07T06:38:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `git_config_parse_parameter` is responsible for parsing a\n`foo.bar=baz`-formatted configuration key, sanitizing the key and then\nprocessing it via the given callback function. Given that we're about to\nadd a second user which is going to process keys which already has keys\nand values separated, this commit extracts a function\n`config_parse_pair` which only does the sanitization and processing\npart as a preparatory step.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.c | 24 +++++++++++++++++-------\n 1 file changed, 17 insertions(+), 7 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex cde3511110..151980e5c9 100644\n--- a/config.c\n+++ b/config.c\n@@ -458,11 +458,26 @@ int git_config_key_is_valid(const char *key)\n \treturn !git_config_parse_key_1(key, NULL, NULL, 1);\n }\n \n+static int config_parse_pair(const char *key, const char *value,\n+\t\t\t  config_fn_t fn, void *data)\n+{\n+\tchar *canonical_name;\n+\tint ret;\n+\n+\tif (!strlen(key))\n+\t\treturn error(_(\"empty config key\"));\n+\tif (git_config_parse_key(key, &canonical_name, NULL))\n+\t\treturn -1;\n+\n+\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n+\tfree(canonical_name);\n+\treturn ret;\n+}\n+\n int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n \tconst char *value;\n-\tchar *canonical_name;\n \tstruct strbuf **pair;\n \tint ret;\n \n@@ -483,12 +498,7 @@ int git_config_parse_parameter(const char *text,\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \t}\n \n-\tif (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {\n-\t\tret = -1;\n-\t} else {\n-\t\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n-\t\tfree(canonical_name);\n-\t}\n+\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n \tstrbuf_list_free(pair);\n \treturn ret;\n }\n-- \n2.30.0\n\n"},{"id":"413647","messageId":"cover.1610001187.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1606214397.git.ps@pks.im","subject":"[PATCH v6 0/8] config: allow specifying config entries via env","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-07T06:36:42Z","receivedAt":"2021-01-07T06:38:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the sixth version of my patch series which aims to implement a\nway to pass config entries via the environment while avoiding any\nrequirements to perform shell quoting on the user's side.\n\nThe only change in this version is improved formatting and wording of\nthe documentation as proposed by Junio. Please see the attached\nrange-diff.\n\nPatrick\n\nJeff King (3):\n  quote: make sq_dequote_step() a public function\n  config: store \"git -c\" variables using more robust format\n  config: parse more robust format in GIT_CONFIG_PARAMETERS\n\nPatrick Steinhardt (5):\n  git: add `--super-prefix` to usage string\n  config: add new way to pass config via `--config-env`\n  config: extract function to parse config pairs\n  environment: make `getenv_safe()` a public function\n  config: allow specifying config entries via envvar pairs\n\n Documentation/git-config.txt |  16 +++\n Documentation/git.txt        |  24 +++-\n cache.h                      |   1 +\n config.c                     | 205 ++++++++++++++++++++++++++++----\n config.h                     |   1 +\n environment.c                |   8 +-\n environment.h                |  12 ++\n git.c                        |   3 +\n quote.c                      |  15 ++-\n quote.h                      |  18 ++-\n t/t1300-config.sh            | 220 ++++++++++++++++++++++++++++++++++-\n 11 files changed, 484 insertions(+), 39 deletions(-)\n create mode 100644 environment.h\n\nRange-diff against v5:\n1:  470521e728 = 1:  cd3de0743a git: add `--super-prefix` to usage string\n2:  56c9221c4c ! 2:  9b8461010e config: add new way to pass config via `--config-env`\n    @@ Documentation/git.txt: config file). Including the equals but with an empty valu\n      --type=bool` will convert to `false`.\n      \n     +--config-env=<name>=<envvar>::\n    -+\tLike `-c <name>=<var>` except the value is the name of an\n    ++\tLike `-c <name>=<value>`, give configuration variable\n    ++\t'<name>' a value, where <envvar> is the name of an\n     +\tenvironment variable from which to retrieve the value. Unlike\n     +\t`-c` there is no shortcut for directly setting the value to an\n     +\tempty string, instead the environment variable itself must be\n    -+\tset to the empty strin. Errors if the `<envvar>` does not exist\n    ++\tset to the empty string.  It is an error if the `<envvar>` does not exist\n     +\tin the environment. `<envvar>` may not contain an equals sign\n     +\tto avoid ambiguity with `<name>`s which contain one.\n    -+\n    -+\tThis is useful for cases where you want to pass transitory\n    -+\tconfiguration options to git, but are doing so on OS's where\n    -+\tother processes might be able to read your cmdline\n    -+\t(e.g. `/proc/self/cmdline`), but not your environ\n    -+\t(e.g. `/proc/self/environ`). That behavior is the default on\n    -+\tLinux, but may not be on your system.\n    -+\n    -+\tNote that this might add security for variables such as\n    -+\t`http.extraHeader` where the sensitive information is part of\n    -+\tthe value, but not e.g. `url.<base.insteadOf` where the\n    -+\tsensitive information can be part of the key.\n    +++\n    ++This is useful for cases where you want to pass transitory\n    ++configuration options to git, but are doing so on OS's where\n    ++other processes might be able to read your cmdline\n    ++(e.g. `/proc/self/cmdline`), but not your environ\n    ++(e.g. `/proc/self/environ`). That behavior is the default on\n    ++Linux, but may not be on your system.\n    +++\n    ++Note that this might add security for variables such as\n    ++`http.extraHeader` where the sensitive information is part of\n    ++the value, but not e.g. `url.<base>.insteadOf` where the\n    ++sensitive information can be part of the key.\n     +\n      --exec-path[=<path>]::\n      \tPath to wherever your core Git programs are installed.\n3:  5729f5d406 = 3:  9d4c8d7be9 quote: make sq_dequote_step() a public function\n4:  8c6cdd57a0 = 4:  0a9b085fe5 config: extract function to parse config pairs\n5:  ff96e59e79 = 5:  b96686c9cd config: store \"git -c\" variables using more robust format\n6:  d832f3dedf = 6:  6597700ffb config: parse more robust format in GIT_CONFIG_PARAMETERS\n7:  2f51a0c5fc = 7:  cade8fb12f environment: make `getenv_safe()` a public function\n8:  dfceffd8d4 ! 8:  4e3f208d13 config: allow specifying config entries via envvar pairs\n    @@ Documentation/git-config.txt: GIT_CONFIG_NOSYSTEM::\n     +\tpairs are processed. These environment variables will override values\n     +\tin configuration files, but will be overridden by any explicit options\n     +\tpassed via `git -c`.\n    -+\n    -+\tThis is useful for cases where you want to spawn multiple git commands\n    -+\twith a common configuration but cannot depend on a configuration file,\n    -+\tfor example when writing scripts.\n    +++\n    ++This is useful for cases where you want to spawn multiple git commands\n    ++with a common configuration but cannot depend on a configuration file,\n    ++for example when writing scripts.\n     +\n      \n      [[EXAMPLES]]\n-- \n2.30.0\n\n"},{"id":"413648","messageId":"cd3de0743a258485c79c4690b6c907728104930a.1610001187.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610001187.git.ps@pks.im","subject":"[PATCH v6 1/8] git: add `--super-prefix` to usage string","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-07T06:36:47Z","receivedAt":"2021-01-07T06:38:07Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When the `--super-prefix` option was implmented in 74866d7579 (git: make\nsuper-prefix option, 2016-10-07), its existence was only documented in\nthe manpage but not in the command's own usage string. Given that the\ncommit message didn't mention that this was done intentionally and given\nthat it's documented in the manpage, this seems like an oversight.\n\nAdd it to the usage string to fix the inconsistency.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n git.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/git.c b/git.c\nindex a00a0a4d94..5a8ff12f87 100644\n--- a/git.c\n+++ b/git.c\n@@ -29,6 +29,7 @@ const char git_usage_string[] =\n \t   \"           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\\n\"\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\\n\"\n \t   \"           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\\n\"\n+\t   \"           [--super-prefix=<path>]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n-- \n2.30.0\n\n"},{"id":"413649","messageId":"9b8461010e641369316d00e2fc58c16e0e191f42.1610001187.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610001187.git.ps@pks.im","subject":"[PATCH v6 2/8] config: add new way to pass config via `--config-env`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-07T06:36:52Z","receivedAt":"2021-01-07T06:38:07Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While it's already possible to pass runtime configuration via `git -c\n<key>=<value>`, it may be undesirable to use when the value contains\nsensitive information. E.g. if one wants to set `http.extraHeader` to\ncontain an authentication token, doing so via `-c` would trivially leak\nthose credentials via e.g. ps(1), which typically also shows command\narguments.\n\nTo enable this usecase without leaking credentials, this commit\nintroduces a new switch `--config-env=<key>=<envvar>`. Instead of\ndirectly passing a value for the given key, it instead allows the user\nto specify the name of an environment variable. The value of that\nvariable will then be used as value of the key.\n\nCo-authored-by: Jeff King <peff@peff.net>\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git.txt | 24 ++++++++++++++++++++++-\n config.c              | 21 ++++++++++++++++++++\n config.h              |  1 +\n git.c                 |  4 +++-\n t/t1300-config.sh     | 45 +++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 93 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex a6d4ad0818..d36e6fd482 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n     [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n     [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]\n     [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n-    [--super-prefix=<path>]\n+    [--super-prefix=<path>] [--config-env <name>=<envvar>]\n     <command> [<args>]\n \n DESCRIPTION\n@@ -80,6 +80,28 @@ config file). Including the equals but with an empty value (like `git -c\n foo.bar= ...`) sets `foo.bar` to the empty string which `git config\n --type=bool` will convert to `false`.\n \n+--config-env=<name>=<envvar>::\n+\tLike `-c <name>=<value>`, give configuration variable\n+\t'<name>' a value, where <envvar> is the name of an\n+\tenvironment variable from which to retrieve the value. Unlike\n+\t`-c` there is no shortcut for directly setting the value to an\n+\tempty string, instead the environment variable itself must be\n+\tset to the empty string.  It is an error if the `<envvar>` does not exist\n+\tin the environment. `<envvar>` may not contain an equals sign\n+\tto avoid ambiguity with `<name>`s which contain one.\n++\n+This is useful for cases where you want to pass transitory\n+configuration options to git, but are doing so on OS's where\n+other processes might be able to read your cmdline\n+(e.g. `/proc/self/cmdline`), but not your environ\n+(e.g. `/proc/self/environ`). That behavior is the default on\n+Linux, but may not be on your system.\n++\n+Note that this might add security for variables such as\n+`http.extraHeader` where the sensitive information is part of\n+the value, but not e.g. `url.<base>.insteadOf` where the\n+sensitive information can be part of the key.\n+\n --exec-path[=<path>]::\n \tPath to wherever your core Git programs are installed.\n \tThis can also be controlled by setting the GIT_EXEC_PATH\ndiff --git a/config.c b/config.c\nindex 1137bd73af..cde3511110 100644\n--- a/config.c\n+++ b/config.c\n@@ -345,6 +345,27 @@ void git_config_push_parameter(const char *text)\n \tstrbuf_release(&env);\n }\n \n+void git_config_push_env(const char *spec)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *env_name;\n+\tconst char *env_value;\n+\n+\tenv_name = strrchr(spec, '=');\n+\tif (!env_name)\n+\t\tdie(\"invalid config format: %s\", spec);\n+\tenv_name++;\n+\n+\tenv_value = getenv(env_name);\n+\tif (!env_value)\n+\t\tdie(\"config variable missing for '%s'\", env_name);\n+\n+\tstrbuf_add(&buf, spec, env_name - spec);\n+\tstrbuf_addstr(&buf, env_value);\n+\tgit_config_push_parameter(buf.buf);\n+\tstrbuf_release(&buf);\n+}\n+\n static inline int iskeychar(int c)\n {\n \treturn isalnum(c) || c == '-';\ndiff --git a/config.h b/config.h\nindex c1449bb790..19a9adbaa9 100644\n--- a/config.h\n+++ b/config.h\n@@ -138,6 +138,7 @@ int git_config_from_mem(config_fn_t fn,\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     const struct object_id *oid, void *data);\n void git_config_push_parameter(const char *text);\n+void git_config_push_env(const char *spec);\n int git_config_from_parameters(config_fn_t fn, void *data);\n void read_early_config(config_fn_t cb, void *data);\n void read_very_early_config(config_fn_t cb, void *data);\ndiff --git a/git.c b/git.c\nindex 5a8ff12f87..b5f63d346b 100644\n--- a/git.c\n+++ b/git.c\n@@ -29,7 +29,7 @@ const char git_usage_string[] =\n \t   \"           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\\n\"\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\\n\"\n \t   \"           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\\n\"\n-\t   \"           [--super-prefix=<path>]\\n\"\n+\t   \"           [--super-prefix=<path>] [--config-env=<name>=<envvar>]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n@@ -255,6 +255,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tgit_config_push_parameter((*argv)[1]);\n \t\t\t(*argv)++;\n \t\t\t(*argc)--;\n+\t\t} else if (skip_prefix(cmd, \"--config-env=\", &cmd)) {\n+\t\t\tgit_config_push_env(cmd);\n \t\t} else if (!strcmp(cmd, \"--literal-pathspecs\")) {\n \t\t\tsetenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, \"1\", 1);\n \t\t\tif (envchanged)\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 97a04c6cc2..46a94814d5 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1316,6 +1316,51 @@ test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n \t\tgit config --get-regexp \"env.*\"\n '\n \n+test_expect_success 'git --config-env=key=envvar support' '\n+\tcat >expect <<-\\EOF &&\n+\tvalue\n+\tvalue\n+\tfalse\n+\tEOF\n+\t{\n+\t\tenv ENVVAR=value git --config-env=core.name=ENVVAR config core.name &&\n+\t\tenv ENVVAR=value git --config-env=foo.CamelCase=ENVVAR config foo.camelcase &&\n+\t\tenv ENVVAR= git --config-env=foo.flag=ENVVAR config --bool foo.flag\n+\t} >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git --config-env fails with invalid parameters' '\n+\ttest_must_fail git --config-env=foo.flag config --bool foo.flag 2>error &&\n+\ttest_i18ngrep \"invalid config format\" error &&\n+\ttest_must_fail git --config-env=foo.flag=NONEXISTENT config --bool foo.flag 2>error &&\n+\ttest_i18ngrep \"config variable missing\" error\n+'\n+\n+test_expect_success 'git -c and --config-env work together' '\n+\tcat >expect <<-\\EOF &&\n+\tbar.cmd cmd-value\n+\tbar.env env-value\n+\tEOF\n+\tenv ENVVAR=env-value git \\\n+\t\t-c bar.cmd=cmd-value \\\n+\t\t--config-env=bar.env=ENVVAR \\\n+\t\tconfig --get-regexp \"^bar.*\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git -c and --config-env override each other' '\n+\tcat >expect <<-\\EOF &&\n+\tenv\n+\tcmd\n+\tEOF\n+\t{\n+\t\tenv ENVVAR=env git -c bar.bar=cmd --config-env=bar.bar=ENVVAR config bar.bar &&\n+\t\tenv ENVVAR=env git --config-env=bar.bar=ENVVAR -c bar.bar=cmd config bar.bar\n+\t} >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n-- \n2.30.0\n\n"},{"id":"413650","messageId":"9d4c8d7be9e8d358c7b342760034fa90676ed524.1610001187.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610001187.git.ps@pks.im","subject":"[PATCH v6 3/8] quote: make sq_dequote_step() a public function","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-07T06:36:56Z","receivedAt":"2021-01-07T06:38:22Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Jeff King <peff@peff.net>\n\nWe provide a function for dequoting an entire string, as well as one for\nhandling a space-separated list of quoted strings. But there's no way\nfor a caller to parse a string like 'foo'='bar', even though it is easy\nto generate one using sq_quote_buf() or similar.\n\nLet's make the single-step function available to callers outside of\nquote.c. Note that we do need to adjust its implementation slightly: it\ninsists on seeing whitespace between items, and we'd like to be more\nflexible than that. Since it only has a single caller, we can move that\ncheck (and slurping up any extra whitespace) into that caller.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n quote.c | 15 ++++++++++-----\n quote.h | 18 ++++++++++++++++--\n 2 files changed, 26 insertions(+), 7 deletions(-)\n\ndiff --git a/quote.c b/quote.c\nindex 69f4ca45da..8a3a5e39eb 100644\n--- a/quote.c\n+++ b/quote.c\n@@ -116,7 +116,7 @@ void sq_append_quote_argv_pretty(struct strbuf *dst, const char **argv)\n \t}\n }\n \n-static char *sq_dequote_step(char *arg, char **next)\n+char *sq_dequote_step(char *arg, char **next)\n {\n \tchar *dst = arg;\n \tchar *src = arg;\n@@ -153,11 +153,8 @@ static char *sq_dequote_step(char *arg, char **next)\n \t\t\t}\n \t\t/* Fallthrough */\n \t\tdefault:\n-\t\t\tif (!next || !isspace(*src))\n+\t\t\tif (!next)\n \t\t\t\treturn NULL;\n-\t\t\tdo {\n-\t\t\t\tc = *++src;\n-\t\t\t} while (isspace(c));\n \t\t\t*dst = 0;\n \t\t\t*next = src;\n \t\t\treturn arg;\n@@ -182,6 +179,14 @@ static int sq_dequote_to_argv_internal(char *arg,\n \t\tchar *dequoted = sq_dequote_step(next, &next);\n \t\tif (!dequoted)\n \t\t\treturn -1;\n+\t\tif (next) {\n+\t\t\tchar c;\n+\t\t\tif (!isspace(*next))\n+\t\t\t\treturn -1;\n+\t\t\tdo {\n+\t\t\t\tc = *++next;\n+\t\t\t} while (isspace(c));\n+\t\t}\n \t\tif (argv) {\n \t\t\tALLOC_GROW(*argv, *nr + 1, *alloc);\n \t\t\t(*argv)[(*nr)++] = dequoted;\ndiff --git a/quote.h b/quote.h\nindex 4b72a583cf..768cc6338e 100644\n--- a/quote.h\n+++ b/quote.h\n@@ -42,12 +42,26 @@ void sq_quote_buf_pretty(struct strbuf *, const char *src);\n void sq_quote_argv_pretty(struct strbuf *, const char **argv);\n void sq_append_quote_argv_pretty(struct strbuf *dst, const char **argv);\n \n-/* This unwraps what sq_quote() produces in place, but returns\n+/*\n+ * This unwraps what sq_quote() produces in place, but returns\n  * NULL if the input does not look like what sq_quote would have\n- * produced.\n+ * produced (the full string must be a single quoted item).\n  */\n char *sq_dequote(char *);\n \n+/*\n+ * Like sq_dequote(), but dequote a single item, and leave \"next\" pointing to\n+ * the next character. E.g., in the string:\n+ *\n+ *   'one' 'two' 'three'\n+ *\n+ * after the first call, the return value would be the unquoted string \"one\",\n+ * with \"next\" pointing to the space between \"one\" and \"two\"). The caller is\n+ * responsible for advancing the pointer to the start of the next item before\n+ * calling sq_dequote_step() again.\n+ */\n+char *sq_dequote_step(char *src, char **next);\n+\n /*\n  * Same as the above, but can be used to unwrap many arguments in the\n  * same string separated by space. Like sq_quote, it works in place,\n-- \n2.30.0\n\n"},{"id":"413651","messageId":"b96686c9cdaba5b07c0712a6eeb79a2c6bf00857.1610001187.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610001187.git.ps@pks.im","subject":"[PATCH v6 5/8] config: store \"git -c\" variables using more robust format","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-07T06:37:05Z","receivedAt":"2021-01-07T06:39:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Jeff King <peff@peff.net>\n\nThe previous commit added a new format for $GIT_CONFIG_PARAMETERS which\nis able to robustly handle subsections with \"=\" in them. Let's start\nwriting the new format. Unfortunately, this does much less than you'd\nhope, because \"git -c\" itself has the same ambiguity problem! But it's\nstill worth doing:\n\n  - we've now pushed the problem from the inter-process communication\n    into the \"-c\" command-line parser. This would free us up to later\n    add an unambiguous format there (e.g., separate arguments like \"git\n    --config key value\", etc).\n\n  - for --config-env, the parser already disallows \"=\" in the\n    environment variable name. So:\n\n      git --config-env section.with=equals.key=ENVVAR\n\n    will robustly set section.with=equals.key to the contents of\n    $ENVVAR.\n\nThe new test shows the improvement for --config-env.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n config.c          | 52 ++++++++++++++++++++++++++++++++++++++++-------\n t/t1300-config.sh |  8 ++++++++\n 2 files changed, 53 insertions(+), 7 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 151980e5c9..53ed048689 100644\n--- a/config.c\n+++ b/config.c\n@@ -332,7 +332,7 @@ int git_config_include(const char *var, const char *value, void *data)\n \treturn ret;\n }\n \n-void git_config_push_parameter(const char *text)\n+static void git_config_push_split_parameter(const char *key, const char *value)\n {\n \tstruct strbuf env = STRBUF_INIT;\n \tconst char *old = getenv(CONFIG_DATA_ENVIRONMENT);\n@@ -340,30 +340,68 @@ void git_config_push_parameter(const char *text)\n \t\tstrbuf_addstr(&env, old);\n \t\tstrbuf_addch(&env, ' ');\n \t}\n-\tsq_quote_buf(&env, text);\n+\tsq_quote_buf(&env, key);\n+\tstrbuf_addch(&env, '=');\n+\tif (value)\n+\t\tsq_quote_buf(&env, value);\n \tsetenv(CONFIG_DATA_ENVIRONMENT, env.buf, 1);\n \tstrbuf_release(&env);\n }\n \n+void git_config_push_parameter(const char *text)\n+{\n+\tconst char *value;\n+\n+\t/*\n+\t * When we see:\n+\t *\n+\t *   section.subsection=with=equals.key=value\n+\t *\n+\t * we cannot tell if it means:\n+\t *\n+\t *   [section \"subsection=with=equals\"]\n+\t *   key = value\n+\t *\n+\t * or:\n+\t *\n+\t *   [section]\n+\t *   subsection = with=equals.key=value\n+\t *\n+\t * We parse left-to-right for the first \"=\", meaning we'll prefer to\n+\t * keep the value intact over the subsection. This is historical, but\n+\t * also sensible since values are more likely to contain odd or\n+\t * untrusted input than a section name.\n+\t *\n+\t * A missing equals is explicitly allowed (as a bool-only entry).\n+\t */\n+\tvalue = strchr(text, '=');\n+\tif (value) {\n+\t\tchar *key = xmemdupz(text, value - text);\n+\t\tgit_config_push_split_parameter(key, value + 1);\n+\t\tfree(key);\n+\t} else {\n+\t\tgit_config_push_split_parameter(text, NULL);\n+\t}\n+}\n+\n void git_config_push_env(const char *spec)\n {\n-\tstruct strbuf buf = STRBUF_INIT;\n+\tchar *key;\n \tconst char *env_name;\n \tconst char *env_value;\n \n \tenv_name = strrchr(spec, '=');\n \tif (!env_name)\n \t\tdie(\"invalid config format: %s\", spec);\n+\tkey = xmemdupz(spec, env_name - spec);\n \tenv_name++;\n \n \tenv_value = getenv(env_name);\n \tif (!env_value)\n \t\tdie(\"config variable missing for '%s'\", env_name);\n \n-\tstrbuf_add(&buf, spec, env_name - spec);\n-\tstrbuf_addstr(&buf, env_value);\n-\tgit_config_push_parameter(buf.buf);\n-\tstrbuf_release(&buf);\n+\tgit_config_push_split_parameter(key, env_value);\n+\tfree(key);\n }\n \n static inline int iskeychar(int c)\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 46a94814d5..36a60879f6 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1361,6 +1361,14 @@ test_expect_success 'git -c and --config-env override each other' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--config-env handles keys with equals' '\n+\techo value=with=equals >expect &&\n+\tENVVAR=value=with=equals git \\\n+\t\t--config-env=section.subsection=with=equals.key=ENVVAR \\\n+\t\tconfig section.subsection=with=equals.key >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n-- \n2.30.0\n\n"},{"id":"413652","messageId":"6597700ffbb338feb3d82e6baca2591c4e403a24.1610001187.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610001187.git.ps@pks.im","subject":"[PATCH v6 6/8] config: parse more robust format in GIT_CONFIG_PARAMETERS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-07T06:37:10Z","receivedAt":"2021-01-07T06:39:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Jeff King <peff@peff.net>\n\nWhen we stuff config options into GIT_CONFIG_PARAMETERS, we shell-quote\neach one as a single unit, like:\n\n  'section.one=value1' 'section.two=value2'\n\nOn the reading side, we de-quote to get the individual strings, and then\nparse them by splitting on the first \"=\" we find. This format is\nambiguous, because an \"=\" may appear in a subsection. So the config\nrepresented in a file by both:\n\n  [section \"subsection=with=equals\"]\n  key = value\n\nand:\n\n  [section]\n  subsection = with=equals.key=value\n\nends up in this flattened format like:\n\n  'section.subsection=with=equals.key=value'\n\nand we can't tell which was desired. We have traditionally resolved this\nby taking the first \"=\" we see starting from the left, meaning that we\nallowed arbitrary content in the value, but not in the subsection.\n\nLet's make our environment format a bit more robust by separately\nquoting the key and value. That turns those examples into:\n\n  'section.subsection=with=equals.key'='value'\n\nand:\n\n  'section.subsection'='with=equals.key=value'\n\nrespectively, and we can tell the difference between them. We can detect\nwhich format is in use for any given element of the list based on the\npresence of the unquoted \"=\". That means we can continue to allow the\nold format to work to support any callers which manually used the old\nformat, and we can even intermingle the two formats. The old format\nwasn't documented, and nobody was supposed to be using it. But it's\nlikely that such callers exist in the wild, so it's nice if we can avoid\nbreaking them. Likewise, it may be possible to trigger an older version\nof \"git -c\" that runs a script that calls into a newer version of \"git\n-c\"; that new version would see the intermingled format.\n\nThis does create one complication, which is that the obvious format in\nthe new scheme for\n\n  [section]\n  some-bool\n\nis:\n\n  'section.some-bool'\n\nwith no equals. We'd mistake that for an old-style variable. And it even\nhas the same meaning in the old style, but:\n\n  [section \"with=equals\"]\n  some-bool\n\ndoes not. It would be:\n\n  'section.with=equals=some-bool'\n\nwhich we'd take to mean:\n\n  [section]\n  with = equals=some-bool\n\nin the old, ambiguous style. Likewise, we can't use:\n\n  'section.some-bool'=''\n\nbecause that's ambiguous with an actual empty string. Instead, we'll\nagain use the shell-quoting to give us a hint, and use:\n\n  'section.some-bool'=\n\nto show that we have no value.\n\nNote that this commit just expands the reading side. We'll start writing\nthe new format via \"git -c\" in a future patch. In the meantime, the\nexisting \"git -c\" tests will make sure we didn't break reading the old\nformat. But we'll also add some explicit coverage of the two formats to\nmake sure we continue to handle the old one after we move the writing\nside over.\n\nAnd one final note: since we're now using the shell-quoting as a\nsemantically meaningful hint, this closes the door to us ever allowing\narbitrary shell quoting, like:\n\n  'a'shell'would'be'ok'with'this'.key=value\n\nBut we have never supported that (only what sq_quote() would produce),\nand we are probably better off keeping things simple, robust, and\nbackwards-compatible, than trying to make it easier for humans. We'll\ncontinue not to advertise the format of the variable to users, and\ninstead keep \"git -c\" as the recommended mechanism for setting config\n(even if we are trying to be kind not to break users who may be relying\non the current undocumented format).\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n config.c          | 69 +++++++++++++++++++++++++++++++++++------------\n t/t1300-config.sh | 52 +++++++++++++++++++++++++++++++++++\n 2 files changed, 104 insertions(+), 17 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 53ed048689..60a7261807 100644\n--- a/config.c\n+++ b/config.c\n@@ -541,14 +541,62 @@ int git_config_parse_parameter(const char *text,\n \treturn ret;\n }\n \n+static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n+{\n+\tchar *cur = env;\n+\twhile (cur && *cur) {\n+\t\tconst char *key = sq_dequote_step(cur, &cur);\n+\t\tif (!key)\n+\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\n+\t\tif (!cur || isspace(*cur)) {\n+\t\t\t/* old-style 'key=value' */\n+\t\t\tif (git_config_parse_parameter(key, fn, data) < 0)\n+\t\t\t\treturn -1;\n+\t\t}\n+\t\telse if (*cur == '=') {\n+\t\t\t/* new-style 'key'='value' */\n+\t\t\tconst char *value;\n+\n+\t\t\tcur++;\n+\t\t\tif (*cur == '\\'') {\n+\t\t\t\t/* quoted value */\n+\t\t\t\tvalue = sq_dequote_step(cur, &cur);\n+\t\t\t\tif (!value || (cur && !isspace(*cur))) {\n+\t\t\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t\t\t}\n+\t\t\t} else if (!*cur || isspace(*cur)) {\n+\t\t\t\t/* implicit bool: 'key'= */\n+\t\t\t\tvalue = NULL;\n+\t\t\t} else {\n+\t\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t\t}\n+\n+\t\t\tif (config_parse_pair(key, value, fn, data) < 0)\n+\t\t\t\treturn -1;\n+\t\t}\n+\t\telse {\n+\t\t\t/* unknown format */\n+\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t}\n+\n+\t\tif (cur) {\n+\t\t\twhile (isspace(*cur))\n+\t\t\t\tcur++;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n \tconst char *env = getenv(CONFIG_DATA_ENVIRONMENT);\n \tint ret = 0;\n \tchar *envw;\n-\tconst char **argv = NULL;\n-\tint nr = 0, alloc = 0;\n-\tint i;\n \tstruct config_source source;\n \n \tif (!env)\n@@ -561,21 +609,8 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \n \t/* sq_dequote will write over it */\n \tenvw = xstrdup(env);\n+\tret = parse_config_env_list(envw, fn, data);\n \n-\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n-\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n-\t\tgoto out;\n-\t}\n-\n-\tfor (i = 0; i < nr; i++) {\n-\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n-\t\t\tret = -1;\n-\t\t\tgoto out;\n-\t\t}\n-\t}\n-\n-out:\n-\tfree(argv);\n \tfree(envw);\n \tcf = source.prev;\n \treturn ret;\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 36a60879f6..35a1a6e8b1 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1294,6 +1294,58 @@ test_expect_success 'git -c is not confused by empty environment' '\n \tGIT_CONFIG_PARAMETERS=\"\" git -c x.one=1 config --list\n '\n \n+test_expect_success 'GIT_CONFIG_PARAMETERS handles old-style entries' '\n+\tv=\"${SQ}key.one=foo${SQ}\" &&\n+\tv=\"$v  ${SQ}key.two=bar${SQ}\" &&\n+\tv=\"$v ${SQ}key.ambiguous=section.whatever=value${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.one foo\n+\tkey.two bar\n+\tkey.ambiguous section.whatever=value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'GIT_CONFIG_PARAMETERS handles new-style entries' '\n+\tv=\"${SQ}key.one${SQ}=${SQ}foo${SQ}\" &&\n+\tv=\"$v  ${SQ}key.two${SQ}=${SQ}bar${SQ}\" &&\n+\tv=\"$v ${SQ}key.ambiguous=section.whatever${SQ}=${SQ}value${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.one foo\n+\tkey.two bar\n+\tkey.ambiguous=section.whatever value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'old and new-style entries can mix' '\n+\tv=\"${SQ}key.oldone=oldfoo${SQ}\" &&\n+\tv=\"$v ${SQ}key.newone${SQ}=${SQ}newfoo${SQ}\" &&\n+\tv=\"$v ${SQ}key.oldtwo=oldbar${SQ}\" &&\n+\tv=\"$v ${SQ}key.newtwo${SQ}=${SQ}newbar${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.oldone oldfoo\n+\tkey.newone newfoo\n+\tkey.oldtwo oldbar\n+\tkey.newtwo newbar\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'old and new bools with ambiguous subsection' '\n+\tv=\"${SQ}key.with=equals.oldbool${SQ}\" &&\n+\tv=\"$v ${SQ}key.with=equals.newbool${SQ}=\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.with equals.oldbool\n+\tkey.with=equals.newbool\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n \tcat >expect <<-\\EOF &&\n \tenv.one one\n-- \n2.30.0\n\n"},{"id":"413653","messageId":"cade8fb12f125ac8c17c448b4db2d4a6197cdc29.1610001187.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610001187.git.ps@pks.im","subject":"[PATCH v6 7/8] environment: make `getenv_safe()` a public function","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-07T06:37:14Z","receivedAt":"2021-01-07T06:39:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `getenv_safe()` helper function helps to safely retrieve multiple\nenvironment values without the need to depend on platform-specific\nbehaviour for the return value's lifetime. We'll make use of this\nfunction in a following patch, so let's make it available by making it\nnon-static and adding a declaration.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n environment.c |  7 ++-----\n environment.h | 12 ++++++++++++\n 2 files changed, 14 insertions(+), 5 deletions(-)\n create mode 100644 environment.h\n\ndiff --git a/environment.c b/environment.c\nindex bb518c61cd..2234af462c 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -9,6 +9,7 @@\n  */\n #include \"cache.h\"\n #include \"branch.h\"\n+#include \"environment.h\"\n #include \"repository.h\"\n #include \"config.h\"\n #include \"refs.h\"\n@@ -152,11 +153,7 @@ static char *expand_namespace(const char *raw_namespace)\n \treturn strbuf_detach(&buf, NULL);\n }\n \n-/*\n- * Wrapper of getenv() that returns a strdup value. This value is kept\n- * in argv to be freed later.\n- */\n-static const char *getenv_safe(struct strvec *argv, const char *name)\n+const char *getenv_safe(struct strvec *argv, const char *name)\n {\n \tconst char *value = getenv(name);\n \ndiff --git a/environment.h b/environment.h\nnew file mode 100644\nindex 0000000000..d438b5c8f3\n--- /dev/null\n+++ b/environment.h\n@@ -0,0 +1,12 @@\n+#ifndef ENVIRONMENT_H\n+#define ENVIRONMENT_H\n+\n+#include \"strvec.h\"\n+\n+/*\n+ * Wrapper of getenv() that returns a strdup value. This value is kept\n+ * in argv to be freed later.\n+ */\n+const char *getenv_safe(struct strvec *argv, const char *name);\n+\n+#endif\n-- \n2.30.0\n\n"},{"id":"413654","messageId":"4e3f208d1358193f253128dde747b235359edaa5.1610001187.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610001187.git.ps@pks.im","subject":"[PATCH v6 8/8] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-07T06:37:19Z","receivedAt":"2021-01-07T06:39:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While we currently have the `GIT_CONFIG_PARAMETERS` environment variable\nwhich can be used to pass runtime configuration data to git processes,\nit's an internal implementation detail and not supposed to be used by\nend users.\n\nNext to being for internal use only, this way of passing config entries\nhas a major downside: the config keys need to be parsed as they contain\nboth key and value in a single variable. As such, it is left to the user\nto escape any potentially harmful characters in the value, which is\nquite hard to do if values are controlled by a third party.\n\nThis commit thus adds a new way of adding config entries via the\nenvironment which gets rid of this shortcoming. If the user passes the\n`GIT_CONFIG_COUNT=$n` environment variable, Git will parse environment\nvariable pairs `GIT_CONFIG_KEY_$i` and `GIT_CONFIG_VALUE_$i` for each\n`i` in `[0,n)`.\n\nWhile the same can be achieved with `git -c <name>=<value>`, one may\nwish to not do so for potentially sensitive information. E.g. if one\nwants to set `http.extraHeader` to contain an authentication token,\ndoing so via `-c` would trivially leak those credentials via e.g. ps(1),\nwhich typically also shows command arguments.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-config.txt |  16 +++++\n cache.h                      |   1 +\n config.c                     |  67 +++++++++++++++++---\n environment.c                |   1 +\n t/t1300-config.sh            | 115 ++++++++++++++++++++++++++++++++++-\n 5 files changed, 191 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-config.txt b/Documentation/git-config.txt\nindex 0e9351d3cb..4b4cc5c5e8 100644\n--- a/Documentation/git-config.txt\n+++ b/Documentation/git-config.txt\n@@ -346,6 +346,22 @@ GIT_CONFIG_NOSYSTEM::\n \n See also <<FILES>>.\n \n+GIT_CONFIG_COUNT::\n+GIT_CONFIG_KEY_<n>::\n+GIT_CONFIG_VALUE_<n>::\n+\tIf GIT_CONFIG_COUNT is set to a positive number, all environment pairs\n+\tGIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> up to that number will be\n+\tadded to the process's runtime configuration. The config pairs are\n+\tzero-indexed. Any missing key or value is treated as an error. An empty\n+\tGIT_CONFIG_COUNT is treated the same as GIT_CONFIG_COUNT=0, namely no\n+\tpairs are processed. These environment variables will override values\n+\tin configuration files, but will be overridden by any explicit options\n+\tpassed via `git -c`.\n++\n+This is useful for cases where you want to spawn multiple git commands\n+with a common configuration but cannot depend on a configuration file,\n+for example when writing scripts.\n+\n \n [[EXAMPLES]]\n EXAMPLES\ndiff --git a/cache.h b/cache.h\nindex 7109765748..a2e318c62b 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -472,6 +472,7 @@ static inline enum object_type object_type(unsigned int mode)\n #define TEMPLATE_DIR_ENVIRONMENT \"GIT_TEMPLATE_DIR\"\n #define CONFIG_ENVIRONMENT \"GIT_CONFIG\"\n #define CONFIG_DATA_ENVIRONMENT \"GIT_CONFIG_PARAMETERS\"\n+#define CONFIG_COUNT_ENVIRONMENT \"GIT_CONFIG_COUNT\"\n #define EXEC_PATH_ENVIRONMENT \"GIT_EXEC_PATH\"\n #define CEILING_DIRECTORIES_ENVIRONMENT \"GIT_CEILING_DIRECTORIES\"\n #define NO_REPLACE_OBJECTS_ENVIRONMENT \"GIT_NO_REPLACE_OBJECTS\"\ndiff --git a/config.c b/config.c\nindex 60a7261807..1742aefa3e 100644\n--- a/config.c\n+++ b/config.c\n@@ -8,6 +8,7 @@\n #include \"cache.h\"\n #include \"branch.h\"\n #include \"config.h\"\n+#include \"environment.h\"\n #include \"repository.h\"\n #include \"lockfile.h\"\n #include \"exec-cmd.h\"\n@@ -594,23 +595,73 @@ static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n \n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n-\tconst char *env = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tconst char *env;\n+\tstruct strbuf envvar = STRBUF_INIT;\n+\tstruct strvec to_free = STRVEC_INIT;\n \tint ret = 0;\n-\tchar *envw;\n+\tchar *envw = NULL;\n \tstruct config_source source;\n \n-\tif (!env)\n-\t\treturn 0;\n-\n \tmemset(&source, 0, sizeof(source));\n \tsource.prev = cf;\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n \tcf = &source;\n \n-\t/* sq_dequote will write over it */\n-\tenvw = xstrdup(env);\n-\tret = parse_config_env_list(envw, fn, data);\n+\tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n+\tif (env) {\n+\t\tunsigned long count;\n+\t\tchar *endp;\n+\t\tint i;\n \n+\t\tcount = strtoul(env, &endp, 10);\n+\t\tif (*endp) {\n+\t\t\tret = error(_(\"bogus count in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (count > INT_MAX) {\n+\t\t\tret = error(_(\"too many entries in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (i = 0; i < count; i++) {\n+\t\t\tconst char *key, *value;\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_KEY_%d\", i);\n+\t\t\tkey = getenv_safe(&to_free, envvar.buf);\n+\t\t\tif (!key) {\n+\t\t\t\tret = error(_(\"missing config key %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_VALUE_%d\", i);\n+\t\t\tvalue = getenv_safe(&to_free, envvar.buf);\n+\t\t\tif (!value) {\n+\t\t\t\tret = error(_(\"missing config value %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tenv = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tif (env) {\n+\t\t/* sq_dequote will write over it */\n+\t\tenvw = xstrdup(env);\n+\t\tif (parse_config_env_list(envw, fn, data) < 0) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+out:\n+\tstrbuf_release(&envvar);\n+\tstrvec_clear(&to_free);\n \tfree(envw);\n \tcf = source.prev;\n \treturn ret;\ndiff --git a/environment.c b/environment.c\nindex 2234af462c..2f27008424 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -117,6 +117,7 @@ const char * const local_repo_env[] = {\n \tALTERNATE_DB_ENVIRONMENT,\n \tCONFIG_ENVIRONMENT,\n \tCONFIG_DATA_ENVIRONMENT,\n+\tCONFIG_COUNT_ENVIRONMENT,\n \tDB_ENVIRONMENT,\n \tGIT_DIR_ENVIRONMENT,\n \tGIT_WORK_TREE_ENVIRONMENT,\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 35a1a6e8b1..e06961767f 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1421,6 +1421,117 @@ test_expect_success '--config-env handles keys with equals' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'git config handles environment config pairs' '\n+\tGIT_CONFIG_COUNT=2 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"foo\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"bar\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one foo\n+\tpair.two bar\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs without count' '\n+\ttest_must_fail env GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one 2>error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one\n+'\n+\n+test_expect_success 'git config ignores pairs exceeding count' '\n+\tGIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"value\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with empty count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT= GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config fails with invalid count' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=10a git config --list 2>error &&\n+\ttest_i18ngrep \"bogus count\" error &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=9999999999999999 git config --list 2>error &&\n+\ttest_i18ngrep \"too many entries\" error\n+'\n+\n+test_expect_success 'git config fails with missing config key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config key\" error\n+'\n+\n+test_expect_success 'git config fails with missing config value' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=\"pair.one\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config value\" error\n+'\n+\n+test_expect_success 'git config fails with invalid config pair key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0= GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=missing-section GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list\n+'\n+\n+test_expect_success 'environment overrides config file' '\n+\ttest_when_finished \"rm -f .git/config\" &&\n+\tcat >.git/config <<-EOF &&\n+\t[pair]\n+\tone = value\n+\tEOF\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=override \\\n+\t\tgit config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'GIT_CONFIG_PARAMETERS overrides environment config' '\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n+\t\tGIT_CONFIG_PARAMETERS=\"${SQ}pair.one=override${SQ}\" \\\n+\t\tgit config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'command line overrides environment config' '\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n+\t\tgit -c pair.one=override config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n@@ -1766,9 +1877,11 @@ test_expect_success '--show-origin with --list' '\n \tfile:.git/config\tuser.override=local\n \tfile:.git/config\tinclude.path=../include/relative.include\n \tfile:.git/../include/relative.include\tuser.relative=include\n+\tcommand line:\tuser.environ=true\n \tcommand line:\tuser.cmdline=true\n \tEOF\n-\tgit -c user.cmdline=true config --list --show-origin >output &&\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=user.environ GIT_CONFIG_VALUE_0=true\\\n+\t\tgit -c user.cmdline=true config --list --show-origin >output &&\n \ttest_cmp expect output\n '\n \n-- \n2.30.0\n\n"},{"id":"413987","messageId":"X/tjtVGRKRhX0ZvU@ruderich.org","threadId":"54704","inReplyTo":"9b8461010e641369316d00e2fc58c16e0e191f42.1610001187.git.ps@pks.im","subject":"Re: [PATCH v6 2/8] config: add new way to pass config via `--config-env`","fromName":"Simon Ruderich","fromEmail":"simon@ruderich.org","sentAt":"2021-01-10T20:29:41Z","receivedAt":"2021-01-10T20:36:05Z","isPatch":true,"sender":{"key":"simon@ruderich.org","avatar":"https://avatars.githubusercontent.com/u/390994?v=4"},"body":"On Thu, Jan 07, 2021 at 07:36:52AM +0100, Patrick Steinhardt wrote:\n> [snip]\n>\n> +void git_config_push_env(const char *spec)\n> +{\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\tconst char *env_name;\n> +\tconst char *env_value;\n> +\n> +\tenv_name = strrchr(spec, '=');\n> +\tif (!env_name)\n> +\t\tdie(\"invalid config format: %s\", spec);\n> +\tenv_name++;\n> +\n> +\tenv_value = getenv(env_name);\n> +\tif (!env_value)\n> +\t\tdie(\"config variable missing for '%s'\", env_name);\n\nI think \"environment variable\" should be mentioned in the error\nmessage to make it clear what kind of \"variable\" is missing.\n\nBtw. shouldn't these strings get translated (or does die() do\nthat automatically)?\n\nRegards\nSimon\n-- \n+ privacy is necessary\n+ using gnupg http://gnupg.org\n+ public key id: 0x92FEFDB7E44C32F9\n"},{"id":"413993","messageId":"xmqqr1ms8gfm.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"X/tjtVGRKRhX0ZvU@ruderich.org","subject":"Re: [PATCH v6 2/8] config: add new way to pass config via `--config-env`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-11T00:29:01Z","receivedAt":"2021-01-11T00:29:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Simon Ruderich <simon@ruderich.org> writes:\n\n> On Thu, Jan 07, 2021 at 07:36:52AM +0100, Patrick Steinhardt wrote:\n>> [snip]\n>>\n>> +void git_config_push_env(const char *spec)\n>> +{\n>> +\tstruct strbuf buf = STRBUF_INIT;\n>> +\tconst char *env_name;\n>> +\tconst char *env_value;\n>> +\n>> +\tenv_name = strrchr(spec, '=');\n>> +\tif (!env_name)\n>> +\t\tdie(\"invalid config format: %s\", spec);\n>> +\tenv_name++;\n>> +\n>> +\tenv_value = getenv(env_name);\n>> +\tif (!env_value)\n>> +\t\tdie(\"config variable missing for '%s'\", env_name);\n>\n> I think \"environment variable\" should be mentioned in the error\n> message to make it clear what kind of \"variable\" is missing.\n\nGood observation.  This parses foo=bar and complains about bar\nmissing in the environment; It is not a \"config variable\" that is\nmissing.\n\nIt is \"'bar', which is supposed to be there whose value is going to\nbe used as the value of configuration variable 'foo', is missing.\"\n\nI wonder if we should also talk about 'foo' at the same time as a\nhint for what went wrong?  E.g.\n\n\tdie(_(\"missing environment variable '%s' for configuration '%.*s'\"),\n            env_name, (int)((env_name-1) - spec), spec);\n\nI don't offhand know if that is too much info that may not be all\nthat useful, though.\n\n> Btw. shouldn't these strings get translated (or does die() do\n> that automatically)?\n\nThe format string given to die/error/warn should be marked with _().\n\nThanks.\n"},{"id":"414012","messageId":"X/wLURSbxN3jS4eH@ncase","threadId":"54704","inReplyTo":"xmqqr1ms8gfm.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v6 2/8] config: add new way to pass config via `--config-env`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-11T08:24:49Z","receivedAt":"2021-01-11T08:26:26Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Jan 10, 2021 at 04:29:01PM -0800, Junio C Hamano wrote:\n> Simon Ruderich <simon@ruderich.org> writes:\n> \n> > On Thu, Jan 07, 2021 at 07:36:52AM +0100, Patrick Steinhardt wrote:\n> >> [snip]\n> >>\n> >> +void git_config_push_env(const char *spec)\n> >> +{\n> >> +\tstruct strbuf buf = STRBUF_INIT;\n> >> +\tconst char *env_name;\n> >> +\tconst char *env_value;\n> >> +\n> >> +\tenv_name = strrchr(spec, '=');\n> >> +\tif (!env_name)\n> >> +\t\tdie(\"invalid config format: %s\", spec);\n> >> +\tenv_name++;\n> >> +\n> >> +\tenv_value = getenv(env_name);\n> >> +\tif (!env_value)\n> >> +\t\tdie(\"config variable missing for '%s'\", env_name);\n> >\n> > I think \"environment variable\" should be mentioned in the error\n> > message to make it clear what kind of \"variable\" is missing.\n> \n> Good observation.  This parses foo=bar and complains about bar\n> missing in the environment; It is not a \"config variable\" that is\n> missing.\n> \n> It is \"'bar', which is supposed to be there whose value is going to\n> be used as the value of configuration variable 'foo', is missing.\"\n\nIndeed.\n\n> I wonder if we should also talk about 'foo' at the same time as a\n> hint for what went wrong?  E.g.\n> \n> \tdie(_(\"missing environment variable '%s' for configuration '%.*s'\"),\n>             env_name, (int)((env_name-1) - spec), spec);\n> \n> I don't offhand know if that is too much info that may not be all\n> that useful, though.\n\nNo, I think that this error message is quite useful as it points out\nboth what's missing and why we expect it to exist in the first place.\n\n> > Btw. shouldn't these strings get translated (or does die() do\n> > that automatically)?\n> \n> The format string given to die/error/warn should be marked with _().\n> \n> Thanks.\n\nRight, will fix.\n\nPatrick\n"},{"id":"414013","messageId":"b9cf47afe896f8a6a76ba2e8aa87155e147ff31d.1610353895.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610353895.git.ps@pks.im","subject":"[PATCH v7 2/8] config: add new way to pass config via `--config-env`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-11T08:36:49Z","receivedAt":"2021-01-11T08:37:42Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While it's already possible to pass runtime configuration via `git -c\n<key>=<value>`, it may be undesirable to use when the value contains\nsensitive information. E.g. if one wants to set `http.extraHeader` to\ncontain an authentication token, doing so via `-c` would trivially leak\nthose credentials via e.g. ps(1), which typically also shows command\narguments.\n\nTo enable this usecase without leaking credentials, this commit\nintroduces a new switch `--config-env=<key>=<envvar>`. Instead of\ndirectly passing a value for the given key, it instead allows the user\nto specify the name of an environment variable. The value of that\nvariable will then be used as value of the key.\n\nCo-authored-by: Jeff King <peff@peff.net>\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git.txt | 24 +++++++++++++++++++++-\n config.c              | 24 ++++++++++++++++++++++\n config.h              |  1 +\n git.c                 |  4 +++-\n t/t1300-config.sh     | 47 +++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 98 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex a6d4ad0818..d36e6fd482 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n     [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n     [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]\n     [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n-    [--super-prefix=<path>]\n+    [--super-prefix=<path>] [--config-env <name>=<envvar>]\n     <command> [<args>]\n \n DESCRIPTION\n@@ -80,6 +80,28 @@ config file). Including the equals but with an empty value (like `git -c\n foo.bar= ...`) sets `foo.bar` to the empty string which `git config\n --type=bool` will convert to `false`.\n \n+--config-env=<name>=<envvar>::\n+\tLike `-c <name>=<value>`, give configuration variable\n+\t'<name>' a value, where <envvar> is the name of an\n+\tenvironment variable from which to retrieve the value. Unlike\n+\t`-c` there is no shortcut for directly setting the value to an\n+\tempty string, instead the environment variable itself must be\n+\tset to the empty string.  It is an error if the `<envvar>` does not exist\n+\tin the environment. `<envvar>` may not contain an equals sign\n+\tto avoid ambiguity with `<name>`s which contain one.\n++\n+This is useful for cases where you want to pass transitory\n+configuration options to git, but are doing so on OS's where\n+other processes might be able to read your cmdline\n+(e.g. `/proc/self/cmdline`), but not your environ\n+(e.g. `/proc/self/environ`). That behavior is the default on\n+Linux, but may not be on your system.\n++\n+Note that this might add security for variables such as\n+`http.extraHeader` where the sensitive information is part of\n+the value, but not e.g. `url.<base>.insteadOf` where the\n+sensitive information can be part of the key.\n+\n --exec-path[=<path>]::\n \tPath to wherever your core Git programs are installed.\n \tThis can also be controlled by setting the GIT_EXEC_PATH\ndiff --git a/config.c b/config.c\nindex 1137bd73af..6484d13c46 100644\n--- a/config.c\n+++ b/config.c\n@@ -345,6 +345,30 @@ void git_config_push_parameter(const char *text)\n \tstrbuf_release(&env);\n }\n \n+void git_config_push_env(const char *spec)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *env_name;\n+\tconst char *env_value;\n+\n+\tenv_name = strrchr(spec, '=');\n+\tif (!env_name)\n+\t\tdie(_(\"invalid config format: %s\"), spec);\n+\tenv_name++;\n+\tif (!*env_name)\n+\t\tdie(_(\"missing value for --config-env\"));\n+\n+\tenv_value = getenv(env_name);\n+\tif (!env_value)\n+\t\tdie(_(\"missing environment variable '%s' for configuration '%.*s'\"),\n+\t\t    env_name, (int)(env_name - spec - 1), spec);\n+\n+\tstrbuf_add(&buf, spec, env_name - spec);\n+\tstrbuf_addstr(&buf, env_value);\n+\tgit_config_push_parameter(buf.buf);\n+\tstrbuf_release(&buf);\n+}\n+\n static inline int iskeychar(int c)\n {\n \treturn isalnum(c) || c == '-';\ndiff --git a/config.h b/config.h\nindex c1449bb790..19a9adbaa9 100644\n--- a/config.h\n+++ b/config.h\n@@ -138,6 +138,7 @@ int git_config_from_mem(config_fn_t fn,\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     const struct object_id *oid, void *data);\n void git_config_push_parameter(const char *text);\n+void git_config_push_env(const char *spec);\n int git_config_from_parameters(config_fn_t fn, void *data);\n void read_early_config(config_fn_t cb, void *data);\n void read_very_early_config(config_fn_t cb, void *data);\ndiff --git a/git.c b/git.c\nindex 5a8ff12f87..b5f63d346b 100644\n--- a/git.c\n+++ b/git.c\n@@ -29,7 +29,7 @@ const char git_usage_string[] =\n \t   \"           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\\n\"\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\\n\"\n \t   \"           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\\n\"\n-\t   \"           [--super-prefix=<path>]\\n\"\n+\t   \"           [--super-prefix=<path>] [--config-env=<name>=<envvar>]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n@@ -255,6 +255,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tgit_config_push_parameter((*argv)[1]);\n \t\t\t(*argv)++;\n \t\t\t(*argc)--;\n+\t\t} else if (skip_prefix(cmd, \"--config-env=\", &cmd)) {\n+\t\t\tgit_config_push_env(cmd);\n \t\t} else if (!strcmp(cmd, \"--literal-pathspecs\")) {\n \t\t\tsetenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, \"1\", 1);\n \t\t\tif (envchanged)\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 97a04c6cc2..1e23eb8213 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1316,6 +1316,53 @@ test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n \t\tgit config --get-regexp \"env.*\"\n '\n \n+test_expect_success 'git --config-env=key=envvar support' '\n+\tcat >expect <<-\\EOF &&\n+\tvalue\n+\tvalue\n+\tfalse\n+\tEOF\n+\t{\n+\t\tenv ENVVAR=value git --config-env=core.name=ENVVAR config core.name &&\n+\t\tenv ENVVAR=value git --config-env=foo.CamelCase=ENVVAR config foo.camelcase &&\n+\t\tenv ENVVAR= git --config-env=foo.flag=ENVVAR config --bool foo.flag\n+\t} >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git --config-env fails with invalid parameters' '\n+\ttest_must_fail git --config-env=foo.flag config --bool foo.flag 2>error &&\n+\ttest_i18ngrep \"invalid config format\" error &&\n+\ttest_must_fail git --config-env=foo.flag= config --bool foo.flag 2>error &&\n+\ttest_i18ngrep \"missing value for --config-env\" error &&\n+\ttest_must_fail git --config-env=foo.flag=NONEXISTENT config --bool foo.flag 2>error &&\n+\ttest_i18ngrep \"missing environment variable ${SQ}NONEXISTENT${SQ} for configuration ${SQ}foo.flag${SQ}\" error\n+'\n+\n+test_expect_success 'git -c and --config-env work together' '\n+\tcat >expect <<-\\EOF &&\n+\tbar.cmd cmd-value\n+\tbar.env env-value\n+\tEOF\n+\tenv ENVVAR=env-value git \\\n+\t\t-c bar.cmd=cmd-value \\\n+\t\t--config-env=bar.env=ENVVAR \\\n+\t\tconfig --get-regexp \"^bar.*\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git -c and --config-env override each other' '\n+\tcat >expect <<-\\EOF &&\n+\tenv\n+\tcmd\n+\tEOF\n+\t{\n+\t\tenv ENVVAR=env git -c bar.bar=cmd --config-env=bar.bar=ENVVAR config bar.bar &&\n+\t\tenv ENVVAR=env git --config-env=bar.bar=ENVVAR -c bar.bar=cmd config bar.bar\n+\t} >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n-- \n2.30.0\n\n"},{"id":"414014","messageId":"1b47f0db98a4453e4d30815753a57e654fd1aa52.1610353895.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610353895.git.ps@pks.im","subject":"[PATCH v7 3/8] quote: make sq_dequote_step() a public function","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-11T08:36:53Z","receivedAt":"2021-01-11T08:38:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Jeff King <peff@peff.net>\n\nWe provide a function for dequoting an entire string, as well as one for\nhandling a space-separated list of quoted strings. But there's no way\nfor a caller to parse a string like 'foo'='bar', even though it is easy\nto generate one using sq_quote_buf() or similar.\n\nLet's make the single-step function available to callers outside of\nquote.c. Note that we do need to adjust its implementation slightly: it\ninsists on seeing whitespace between items, and we'd like to be more\nflexible than that. Since it only has a single caller, we can move that\ncheck (and slurping up any extra whitespace) into that caller.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n quote.c | 15 ++++++++++-----\n quote.h | 18 ++++++++++++++++--\n 2 files changed, 26 insertions(+), 7 deletions(-)\n\ndiff --git a/quote.c b/quote.c\nindex 69f4ca45da..8a3a5e39eb 100644\n--- a/quote.c\n+++ b/quote.c\n@@ -116,7 +116,7 @@ void sq_append_quote_argv_pretty(struct strbuf *dst, const char **argv)\n \t}\n }\n \n-static char *sq_dequote_step(char *arg, char **next)\n+char *sq_dequote_step(char *arg, char **next)\n {\n \tchar *dst = arg;\n \tchar *src = arg;\n@@ -153,11 +153,8 @@ static char *sq_dequote_step(char *arg, char **next)\n \t\t\t}\n \t\t/* Fallthrough */\n \t\tdefault:\n-\t\t\tif (!next || !isspace(*src))\n+\t\t\tif (!next)\n \t\t\t\treturn NULL;\n-\t\t\tdo {\n-\t\t\t\tc = *++src;\n-\t\t\t} while (isspace(c));\n \t\t\t*dst = 0;\n \t\t\t*next = src;\n \t\t\treturn arg;\n@@ -182,6 +179,14 @@ static int sq_dequote_to_argv_internal(char *arg,\n \t\tchar *dequoted = sq_dequote_step(next, &next);\n \t\tif (!dequoted)\n \t\t\treturn -1;\n+\t\tif (next) {\n+\t\t\tchar c;\n+\t\t\tif (!isspace(*next))\n+\t\t\t\treturn -1;\n+\t\t\tdo {\n+\t\t\t\tc = *++next;\n+\t\t\t} while (isspace(c));\n+\t\t}\n \t\tif (argv) {\n \t\t\tALLOC_GROW(*argv, *nr + 1, *alloc);\n \t\t\t(*argv)[(*nr)++] = dequoted;\ndiff --git a/quote.h b/quote.h\nindex 4b72a583cf..768cc6338e 100644\n--- a/quote.h\n+++ b/quote.h\n@@ -42,12 +42,26 @@ void sq_quote_buf_pretty(struct strbuf *, const char *src);\n void sq_quote_argv_pretty(struct strbuf *, const char **argv);\n void sq_append_quote_argv_pretty(struct strbuf *dst, const char **argv);\n \n-/* This unwraps what sq_quote() produces in place, but returns\n+/*\n+ * This unwraps what sq_quote() produces in place, but returns\n  * NULL if the input does not look like what sq_quote would have\n- * produced.\n+ * produced (the full string must be a single quoted item).\n  */\n char *sq_dequote(char *);\n \n+/*\n+ * Like sq_dequote(), but dequote a single item, and leave \"next\" pointing to\n+ * the next character. E.g., in the string:\n+ *\n+ *   'one' 'two' 'three'\n+ *\n+ * after the first call, the return value would be the unquoted string \"one\",\n+ * with \"next\" pointing to the space between \"one\" and \"two\"). The caller is\n+ * responsible for advancing the pointer to the start of the next item before\n+ * calling sq_dequote_step() again.\n+ */\n+char *sq_dequote_step(char *src, char **next);\n+\n /*\n  * Same as the above, but can be used to unwrap many arguments in the\n  * same string separated by space. Like sq_quote, it works in place,\n-- \n2.30.0\n\n"},{"id":"414015","messageId":"cover.1610353895.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1606214397.git.ps@pks.im","subject":"[PATCH v7 0/8] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-11T08:36:40Z","receivedAt":"2021-01-11T08:38:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the seventh version of my patch series which aims to implement a\nway to pass config entries via the environment while avoiding any\nrequirements to perform shell quoting on the user's side.\n\nThe only change in this version is improved error handling for the\n`--config-env` switch:\n\n    - Error messages are now correctly marked for translation.\n\n    - A separate error message is given if no value is passed to\n      `--config-env`. Previously, we would've tried to look up the\n      empty environment variable (`getenv(\"\")`).\n\n    - The error message when the environment variable is missing was\n      improved.\n\nPlease see the attached ranged-diff for further details.\n\nPatrick\n\nJeff King (2):\n  quote: make sq_dequote_step() a public function\n  config: parse more robust format in GIT_CONFIG_PARAMETERS\n\nPatrick Steinhardt (6):\n  git: add `--super-prefix` to usage string\n  config: add new way to pass config via `--config-env`\n  config: extract function to parse config pairs\n  config: store \"git -c\" variables using more robust format\n  environment: make `getenv_safe()` a public function\n  config: allow specifying config entries via envvar pairs\n\n Documentation/git-config.txt |  16 +++\n Documentation/git.txt        |  24 +++-\n cache.h                      |   1 +\n config.c                     | 208 ++++++++++++++++++++++++++++----\n config.h                     |   1 +\n environment.c                |   8 +-\n environment.h                |  12 ++\n git.c                        |   3 +\n quote.c                      |  15 ++-\n quote.h                      |  18 ++-\n t/t1300-config.sh            | 222 ++++++++++++++++++++++++++++++++++-\n 11 files changed, 489 insertions(+), 39 deletions(-)\n create mode 100644 environment.h\n\nRange-diff against v6:\n1:  cd3de0743a = 1:  55fa4d0d11 git: add `--super-prefix` to usage string\n2:  9b8461010e ! 2:  b9cf47afe8 config: add new way to pass config via `--config-env`\n    @@ config.c: void git_config_push_parameter(const char *text)\n     +\n     +\tenv_name = strrchr(spec, '=');\n     +\tif (!env_name)\n    -+\t\tdie(\"invalid config format: %s\", spec);\n    ++\t\tdie(_(\"invalid config format: %s\"), spec);\n     +\tenv_name++;\n    ++\tif (!*env_name)\n    ++\t\tdie(_(\"missing value for --config-env\"));\n     +\n     +\tenv_value = getenv(env_name);\n     +\tif (!env_value)\n    -+\t\tdie(\"config variable missing for '%s'\", env_name);\n    ++\t\tdie(_(\"missing environment variable '%s' for configuration '%.*s'\"),\n    ++\t\t    env_name, (int)(env_name - spec - 1), spec);\n     +\n     +\tstrbuf_add(&buf, spec, env_name - spec);\n     +\tstrbuf_addstr(&buf, env_value);\n    @@ t/t1300-config.sh: test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n     +test_expect_success 'git --config-env fails with invalid parameters' '\n     +\ttest_must_fail git --config-env=foo.flag config --bool foo.flag 2>error &&\n     +\ttest_i18ngrep \"invalid config format\" error &&\n    ++\ttest_must_fail git --config-env=foo.flag= config --bool foo.flag 2>error &&\n    ++\ttest_i18ngrep \"missing value for --config-env\" error &&\n     +\ttest_must_fail git --config-env=foo.flag=NONEXISTENT config --bool foo.flag 2>error &&\n    -+\ttest_i18ngrep \"config variable missing\" error\n    ++\ttest_i18ngrep \"missing environment variable ${SQ}NONEXISTENT${SQ} for configuration ${SQ}foo.flag${SQ}\" error\n     +'\n     +\n     +test_expect_success 'git -c and --config-env work together' '\n3:  9d4c8d7be9 = 3:  1b47f0db98 quote: make sq_dequote_step() a public function\n4:  0a9b085fe5 = 4:  b9565a050e config: extract function to parse config pairs\n5:  b96686c9cd ! 5:  8f998ac81a config: store \"git -c\" variables using more robust format\n    @@\n      ## Metadata ##\n    -Author: Jeff King <peff@peff.net>\n    +Author: Patrick Steinhardt <ps@pks.im>\n     \n      ## Commit message ##\n         config: store \"git -c\" variables using more robust format\n    @@ config.c: void git_config_push_parameter(const char *text)\n      \n      \tenv_name = strrchr(spec, '=');\n      \tif (!env_name)\n    - \t\tdie(\"invalid config format: %s\", spec);\n    + \t\tdie(_(\"invalid config format: %s\"), spec);\n     +\tkey = xmemdupz(spec, env_name - spec);\n      \tenv_name++;\n    - \n    - \tenv_value = getenv(env_name);\n    - \tif (!env_value)\n    - \t\tdie(\"config variable missing for '%s'\", env_name);\n    + \tif (!*env_name)\n    + \t\tdie(_(\"missing value for --config-env\"));\n    +@@ config.c: void git_config_push_env(const char *spec)\n    + \t\tdie(_(\"missing environment variable '%s' for configuration '%.*s'\"),\n    + \t\t    env_name, (int)(env_name - spec - 1), spec);\n      \n     -\tstrbuf_add(&buf, spec, env_name - spec);\n     -\tstrbuf_addstr(&buf, env_value);\n6:  6597700ffb = 6:  e7b073c9dc config: parse more robust format in GIT_CONFIG_PARAMETERS\n7:  cade8fb12f = 7:  6c1800a18f environment: make `getenv_safe()` a public function\n8:  4e3f208d13 = 8:  ac9e778704 config: allow specifying config entries via envvar pairs\n-- \n2.30.0\n\n"},{"id":"414016","messageId":"55fa4d0d11f92c5b3c86c47b91ca5f4ceab2f81a.1610353895.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610353895.git.ps@pks.im","subject":"[PATCH v7 1/8] git: add `--super-prefix` to usage string","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-11T08:36:45Z","receivedAt":"2021-01-11T08:38:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When the `--super-prefix` option was implmented in 74866d7579 (git: make\nsuper-prefix option, 2016-10-07), its existence was only documented in\nthe manpage but not in the command's own usage string. Given that the\ncommit message didn't mention that this was done intentionally and given\nthat it's documented in the manpage, this seems like an oversight.\n\nAdd it to the usage string to fix the inconsistency.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n git.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/git.c b/git.c\nindex a00a0a4d94..5a8ff12f87 100644\n--- a/git.c\n+++ b/git.c\n@@ -29,6 +29,7 @@ const char git_usage_string[] =\n \t   \"           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\\n\"\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\\n\"\n \t   \"           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\\n\"\n+\t   \"           [--super-prefix=<path>]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n-- \n2.30.0\n\n"},{"id":"414017","messageId":"b9565a050eb7f47d985c76db7665a7936a0a89e8.1610353895.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610353895.git.ps@pks.im","subject":"[PATCH v7 4/8] config: extract function to parse config pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-11T08:36:58Z","receivedAt":"2021-01-11T08:38:11Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `git_config_parse_parameter` is responsible for parsing a\n`foo.bar=baz`-formatted configuration key, sanitizing the key and then\nprocessing it via the given callback function. Given that we're about to\nadd a second user which is going to process keys which already has keys\nand values separated, this commit extracts a function\n`config_parse_pair` which only does the sanitization and processing\npart as a preparatory step.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.c | 24 +++++++++++++++++-------\n 1 file changed, 17 insertions(+), 7 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 6484d13c46..aeffe4f4bc 100644\n--- a/config.c\n+++ b/config.c\n@@ -461,11 +461,26 @@ int git_config_key_is_valid(const char *key)\n \treturn !git_config_parse_key_1(key, NULL, NULL, 1);\n }\n \n+static int config_parse_pair(const char *key, const char *value,\n+\t\t\t  config_fn_t fn, void *data)\n+{\n+\tchar *canonical_name;\n+\tint ret;\n+\n+\tif (!strlen(key))\n+\t\treturn error(_(\"empty config key\"));\n+\tif (git_config_parse_key(key, &canonical_name, NULL))\n+\t\treturn -1;\n+\n+\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n+\tfree(canonical_name);\n+\treturn ret;\n+}\n+\n int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n \tconst char *value;\n-\tchar *canonical_name;\n \tstruct strbuf **pair;\n \tint ret;\n \n@@ -486,12 +501,7 @@ int git_config_parse_parameter(const char *text,\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \t}\n \n-\tif (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {\n-\t\tret = -1;\n-\t} else {\n-\t\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n-\t\tfree(canonical_name);\n-\t}\n+\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n \tstrbuf_list_free(pair);\n \treturn ret;\n }\n-- \n2.30.0\n\n"},{"id":"414018","messageId":"8f998ac81a53a85b16029eed0fdf07f05d1e47f6.1610353895.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610353895.git.ps@pks.im","subject":"[PATCH v7 5/8] config: store \"git -c\" variables using more robust format","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-11T08:37:03Z","receivedAt":"2021-01-11T08:38:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The previous commit added a new format for $GIT_CONFIG_PARAMETERS which\nis able to robustly handle subsections with \"=\" in them. Let's start\nwriting the new format. Unfortunately, this does much less than you'd\nhope, because \"git -c\" itself has the same ambiguity problem! But it's\nstill worth doing:\n\n  - we've now pushed the problem from the inter-process communication\n    into the \"-c\" command-line parser. This would free us up to later\n    add an unambiguous format there (e.g., separate arguments like \"git\n    --config key value\", etc).\n\n  - for --config-env, the parser already disallows \"=\" in the\n    environment variable name. So:\n\n      git --config-env section.with=equals.key=ENVVAR\n\n    will robustly set section.with=equals.key to the contents of\n    $ENVVAR.\n\nThe new test shows the improvement for --config-env.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n config.c          | 52 ++++++++++++++++++++++++++++++++++++++++-------\n t/t1300-config.sh |  8 ++++++++\n 2 files changed, 53 insertions(+), 7 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex aeffe4f4bc..b5475c28aa 100644\n--- a/config.c\n+++ b/config.c\n@@ -332,7 +332,7 @@ int git_config_include(const char *var, const char *value, void *data)\n \treturn ret;\n }\n \n-void git_config_push_parameter(const char *text)\n+static void git_config_push_split_parameter(const char *key, const char *value)\n {\n \tstruct strbuf env = STRBUF_INIT;\n \tconst char *old = getenv(CONFIG_DATA_ENVIRONMENT);\n@@ -340,20 +340,60 @@ void git_config_push_parameter(const char *text)\n \t\tstrbuf_addstr(&env, old);\n \t\tstrbuf_addch(&env, ' ');\n \t}\n-\tsq_quote_buf(&env, text);\n+\tsq_quote_buf(&env, key);\n+\tstrbuf_addch(&env, '=');\n+\tif (value)\n+\t\tsq_quote_buf(&env, value);\n \tsetenv(CONFIG_DATA_ENVIRONMENT, env.buf, 1);\n \tstrbuf_release(&env);\n }\n \n+void git_config_push_parameter(const char *text)\n+{\n+\tconst char *value;\n+\n+\t/*\n+\t * When we see:\n+\t *\n+\t *   section.subsection=with=equals.key=value\n+\t *\n+\t * we cannot tell if it means:\n+\t *\n+\t *   [section \"subsection=with=equals\"]\n+\t *   key = value\n+\t *\n+\t * or:\n+\t *\n+\t *   [section]\n+\t *   subsection = with=equals.key=value\n+\t *\n+\t * We parse left-to-right for the first \"=\", meaning we'll prefer to\n+\t * keep the value intact over the subsection. This is historical, but\n+\t * also sensible since values are more likely to contain odd or\n+\t * untrusted input than a section name.\n+\t *\n+\t * A missing equals is explicitly allowed (as a bool-only entry).\n+\t */\n+\tvalue = strchr(text, '=');\n+\tif (value) {\n+\t\tchar *key = xmemdupz(text, value - text);\n+\t\tgit_config_push_split_parameter(key, value + 1);\n+\t\tfree(key);\n+\t} else {\n+\t\tgit_config_push_split_parameter(text, NULL);\n+\t}\n+}\n+\n void git_config_push_env(const char *spec)\n {\n-\tstruct strbuf buf = STRBUF_INIT;\n+\tchar *key;\n \tconst char *env_name;\n \tconst char *env_value;\n \n \tenv_name = strrchr(spec, '=');\n \tif (!env_name)\n \t\tdie(_(\"invalid config format: %s\"), spec);\n+\tkey = xmemdupz(spec, env_name - spec);\n \tenv_name++;\n \tif (!*env_name)\n \t\tdie(_(\"missing value for --config-env\"));\n@@ -363,10 +403,8 @@ void git_config_push_env(const char *spec)\n \t\tdie(_(\"missing environment variable '%s' for configuration '%.*s'\"),\n \t\t    env_name, (int)(env_name - spec - 1), spec);\n \n-\tstrbuf_add(&buf, spec, env_name - spec);\n-\tstrbuf_addstr(&buf, env_value);\n-\tgit_config_push_parameter(buf.buf);\n-\tstrbuf_release(&buf);\n+\tgit_config_push_split_parameter(key, env_value);\n+\tfree(key);\n }\n \n static inline int iskeychar(int c)\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 1e23eb8213..24919d5445 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1363,6 +1363,14 @@ test_expect_success 'git -c and --config-env override each other' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--config-env handles keys with equals' '\n+\techo value=with=equals >expect &&\n+\tENVVAR=value=with=equals git \\\n+\t\t--config-env=section.subsection=with=equals.key=ENVVAR \\\n+\t\tconfig section.subsection=with=equals.key >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n-- \n2.30.0\n\n"},{"id":"414019","messageId":"6c1800a18f3216541c79fd7809f8f6a6338ae317.1610353895.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610353895.git.ps@pks.im","subject":"[PATCH v7 7/8] environment: make `getenv_safe()` a public function","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-11T08:37:11Z","receivedAt":"2021-01-11T08:38:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `getenv_safe()` helper function helps to safely retrieve multiple\nenvironment values without the need to depend on platform-specific\nbehaviour for the return value's lifetime. We'll make use of this\nfunction in a following patch, so let's make it available by making it\nnon-static and adding a declaration.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n environment.c |  7 ++-----\n environment.h | 12 ++++++++++++\n 2 files changed, 14 insertions(+), 5 deletions(-)\n create mode 100644 environment.h\n\ndiff --git a/environment.c b/environment.c\nindex bb518c61cd..2234af462c 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -9,6 +9,7 @@\n  */\n #include \"cache.h\"\n #include \"branch.h\"\n+#include \"environment.h\"\n #include \"repository.h\"\n #include \"config.h\"\n #include \"refs.h\"\n@@ -152,11 +153,7 @@ static char *expand_namespace(const char *raw_namespace)\n \treturn strbuf_detach(&buf, NULL);\n }\n \n-/*\n- * Wrapper of getenv() that returns a strdup value. This value is kept\n- * in argv to be freed later.\n- */\n-static const char *getenv_safe(struct strvec *argv, const char *name)\n+const char *getenv_safe(struct strvec *argv, const char *name)\n {\n \tconst char *value = getenv(name);\n \ndiff --git a/environment.h b/environment.h\nnew file mode 100644\nindex 0000000000..d438b5c8f3\n--- /dev/null\n+++ b/environment.h\n@@ -0,0 +1,12 @@\n+#ifndef ENVIRONMENT_H\n+#define ENVIRONMENT_H\n+\n+#include \"strvec.h\"\n+\n+/*\n+ * Wrapper of getenv() that returns a strdup value. This value is kept\n+ * in argv to be freed later.\n+ */\n+const char *getenv_safe(struct strvec *argv, const char *name);\n+\n+#endif\n-- \n2.30.0\n\n"},{"id":"414020","messageId":"e7b073c9dc8d356ca6085fc19fd57ccbd81ef9f7.1610353895.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610353895.git.ps@pks.im","subject":"[PATCH v7 6/8] config: parse more robust format in GIT_CONFIG_PARAMETERS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-11T08:37:07Z","receivedAt":"2021-01-11T08:39:08Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Jeff King <peff@peff.net>\n\nWhen we stuff config options into GIT_CONFIG_PARAMETERS, we shell-quote\neach one as a single unit, like:\n\n  'section.one=value1' 'section.two=value2'\n\nOn the reading side, we de-quote to get the individual strings, and then\nparse them by splitting on the first \"=\" we find. This format is\nambiguous, because an \"=\" may appear in a subsection. So the config\nrepresented in a file by both:\n\n  [section \"subsection=with=equals\"]\n  key = value\n\nand:\n\n  [section]\n  subsection = with=equals.key=value\n\nends up in this flattened format like:\n\n  'section.subsection=with=equals.key=value'\n\nand we can't tell which was desired. We have traditionally resolved this\nby taking the first \"=\" we see starting from the left, meaning that we\nallowed arbitrary content in the value, but not in the subsection.\n\nLet's make our environment format a bit more robust by separately\nquoting the key and value. That turns those examples into:\n\n  'section.subsection=with=equals.key'='value'\n\nand:\n\n  'section.subsection'='with=equals.key=value'\n\nrespectively, and we can tell the difference between them. We can detect\nwhich format is in use for any given element of the list based on the\npresence of the unquoted \"=\". That means we can continue to allow the\nold format to work to support any callers which manually used the old\nformat, and we can even intermingle the two formats. The old format\nwasn't documented, and nobody was supposed to be using it. But it's\nlikely that such callers exist in the wild, so it's nice if we can avoid\nbreaking them. Likewise, it may be possible to trigger an older version\nof \"git -c\" that runs a script that calls into a newer version of \"git\n-c\"; that new version would see the intermingled format.\n\nThis does create one complication, which is that the obvious format in\nthe new scheme for\n\n  [section]\n  some-bool\n\nis:\n\n  'section.some-bool'\n\nwith no equals. We'd mistake that for an old-style variable. And it even\nhas the same meaning in the old style, but:\n\n  [section \"with=equals\"]\n  some-bool\n\ndoes not. It would be:\n\n  'section.with=equals=some-bool'\n\nwhich we'd take to mean:\n\n  [section]\n  with = equals=some-bool\n\nin the old, ambiguous style. Likewise, we can't use:\n\n  'section.some-bool'=''\n\nbecause that's ambiguous with an actual empty string. Instead, we'll\nagain use the shell-quoting to give us a hint, and use:\n\n  'section.some-bool'=\n\nto show that we have no value.\n\nNote that this commit just expands the reading side. We'll start writing\nthe new format via \"git -c\" in a future patch. In the meantime, the\nexisting \"git -c\" tests will make sure we didn't break reading the old\nformat. But we'll also add some explicit coverage of the two formats to\nmake sure we continue to handle the old one after we move the writing\nside over.\n\nAnd one final note: since we're now using the shell-quoting as a\nsemantically meaningful hint, this closes the door to us ever allowing\narbitrary shell quoting, like:\n\n  'a'shell'would'be'ok'with'this'.key=value\n\nBut we have never supported that (only what sq_quote() would produce),\nand we are probably better off keeping things simple, robust, and\nbackwards-compatible, than trying to make it easier for humans. We'll\ncontinue not to advertise the format of the variable to users, and\ninstead keep \"git -c\" as the recommended mechanism for setting config\n(even if we are trying to be kind not to break users who may be relying\non the current undocumented format).\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n config.c          | 69 +++++++++++++++++++++++++++++++++++------------\n t/t1300-config.sh | 52 +++++++++++++++++++++++++++++++++++\n 2 files changed, 104 insertions(+), 17 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex b5475c28aa..33099a3b0d 100644\n--- a/config.c\n+++ b/config.c\n@@ -544,14 +544,62 @@ int git_config_parse_parameter(const char *text,\n \treturn ret;\n }\n \n+static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n+{\n+\tchar *cur = env;\n+\twhile (cur && *cur) {\n+\t\tconst char *key = sq_dequote_step(cur, &cur);\n+\t\tif (!key)\n+\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\n+\t\tif (!cur || isspace(*cur)) {\n+\t\t\t/* old-style 'key=value' */\n+\t\t\tif (git_config_parse_parameter(key, fn, data) < 0)\n+\t\t\t\treturn -1;\n+\t\t}\n+\t\telse if (*cur == '=') {\n+\t\t\t/* new-style 'key'='value' */\n+\t\t\tconst char *value;\n+\n+\t\t\tcur++;\n+\t\t\tif (*cur == '\\'') {\n+\t\t\t\t/* quoted value */\n+\t\t\t\tvalue = sq_dequote_step(cur, &cur);\n+\t\t\t\tif (!value || (cur && !isspace(*cur))) {\n+\t\t\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t\t\t}\n+\t\t\t} else if (!*cur || isspace(*cur)) {\n+\t\t\t\t/* implicit bool: 'key'= */\n+\t\t\t\tvalue = NULL;\n+\t\t\t} else {\n+\t\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t\t}\n+\n+\t\t\tif (config_parse_pair(key, value, fn, data) < 0)\n+\t\t\t\treturn -1;\n+\t\t}\n+\t\telse {\n+\t\t\t/* unknown format */\n+\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t}\n+\n+\t\tif (cur) {\n+\t\t\twhile (isspace(*cur))\n+\t\t\t\tcur++;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n \tconst char *env = getenv(CONFIG_DATA_ENVIRONMENT);\n \tint ret = 0;\n \tchar *envw;\n-\tconst char **argv = NULL;\n-\tint nr = 0, alloc = 0;\n-\tint i;\n \tstruct config_source source;\n \n \tif (!env)\n@@ -564,21 +612,8 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \n \t/* sq_dequote will write over it */\n \tenvw = xstrdup(env);\n+\tret = parse_config_env_list(envw, fn, data);\n \n-\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n-\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n-\t\tgoto out;\n-\t}\n-\n-\tfor (i = 0; i < nr; i++) {\n-\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n-\t\t\tret = -1;\n-\t\t\tgoto out;\n-\t\t}\n-\t}\n-\n-out:\n-\tfree(argv);\n \tfree(envw);\n \tcf = source.prev;\n \treturn ret;\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 24919d5445..0063e9f059 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1294,6 +1294,58 @@ test_expect_success 'git -c is not confused by empty environment' '\n \tGIT_CONFIG_PARAMETERS=\"\" git -c x.one=1 config --list\n '\n \n+test_expect_success 'GIT_CONFIG_PARAMETERS handles old-style entries' '\n+\tv=\"${SQ}key.one=foo${SQ}\" &&\n+\tv=\"$v  ${SQ}key.two=bar${SQ}\" &&\n+\tv=\"$v ${SQ}key.ambiguous=section.whatever=value${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.one foo\n+\tkey.two bar\n+\tkey.ambiguous section.whatever=value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'GIT_CONFIG_PARAMETERS handles new-style entries' '\n+\tv=\"${SQ}key.one${SQ}=${SQ}foo${SQ}\" &&\n+\tv=\"$v  ${SQ}key.two${SQ}=${SQ}bar${SQ}\" &&\n+\tv=\"$v ${SQ}key.ambiguous=section.whatever${SQ}=${SQ}value${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.one foo\n+\tkey.two bar\n+\tkey.ambiguous=section.whatever value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'old and new-style entries can mix' '\n+\tv=\"${SQ}key.oldone=oldfoo${SQ}\" &&\n+\tv=\"$v ${SQ}key.newone${SQ}=${SQ}newfoo${SQ}\" &&\n+\tv=\"$v ${SQ}key.oldtwo=oldbar${SQ}\" &&\n+\tv=\"$v ${SQ}key.newtwo${SQ}=${SQ}newbar${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.oldone oldfoo\n+\tkey.newone newfoo\n+\tkey.oldtwo oldbar\n+\tkey.newtwo newbar\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'old and new bools with ambiguous subsection' '\n+\tv=\"${SQ}key.with=equals.oldbool${SQ}\" &&\n+\tv=\"$v ${SQ}key.with=equals.newbool${SQ}=\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.with equals.oldbool\n+\tkey.with=equals.newbool\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n \tcat >expect <<-\\EOF &&\n \tenv.one one\n-- \n2.30.0\n\n"},{"id":"414021","messageId":"ac9e7787049d673c3227437660140f0f6de7e1cf.1610353895.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610353895.git.ps@pks.im","subject":"[PATCH v7 8/8] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-11T08:37:16Z","receivedAt":"2021-01-11T08:39:08Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While we currently have the `GIT_CONFIG_PARAMETERS` environment variable\nwhich can be used to pass runtime configuration data to git processes,\nit's an internal implementation detail and not supposed to be used by\nend users.\n\nNext to being for internal use only, this way of passing config entries\nhas a major downside: the config keys need to be parsed as they contain\nboth key and value in a single variable. As such, it is left to the user\nto escape any potentially harmful characters in the value, which is\nquite hard to do if values are controlled by a third party.\n\nThis commit thus adds a new way of adding config entries via the\nenvironment which gets rid of this shortcoming. If the user passes the\n`GIT_CONFIG_COUNT=$n` environment variable, Git will parse environment\nvariable pairs `GIT_CONFIG_KEY_$i` and `GIT_CONFIG_VALUE_$i` for each\n`i` in `[0,n)`.\n\nWhile the same can be achieved with `git -c <name>=<value>`, one may\nwish to not do so for potentially sensitive information. E.g. if one\nwants to set `http.extraHeader` to contain an authentication token,\ndoing so via `-c` would trivially leak those credentials via e.g. ps(1),\nwhich typically also shows command arguments.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-config.txt |  16 +++++\n cache.h                      |   1 +\n config.c                     |  67 +++++++++++++++++---\n environment.c                |   1 +\n t/t1300-config.sh            | 115 ++++++++++++++++++++++++++++++++++-\n 5 files changed, 191 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-config.txt b/Documentation/git-config.txt\nindex 0e9351d3cb..4b4cc5c5e8 100644\n--- a/Documentation/git-config.txt\n+++ b/Documentation/git-config.txt\n@@ -346,6 +346,22 @@ GIT_CONFIG_NOSYSTEM::\n \n See also <<FILES>>.\n \n+GIT_CONFIG_COUNT::\n+GIT_CONFIG_KEY_<n>::\n+GIT_CONFIG_VALUE_<n>::\n+\tIf GIT_CONFIG_COUNT is set to a positive number, all environment pairs\n+\tGIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> up to that number will be\n+\tadded to the process's runtime configuration. The config pairs are\n+\tzero-indexed. Any missing key or value is treated as an error. An empty\n+\tGIT_CONFIG_COUNT is treated the same as GIT_CONFIG_COUNT=0, namely no\n+\tpairs are processed. These environment variables will override values\n+\tin configuration files, but will be overridden by any explicit options\n+\tpassed via `git -c`.\n++\n+This is useful for cases where you want to spawn multiple git commands\n+with a common configuration but cannot depend on a configuration file,\n+for example when writing scripts.\n+\n \n [[EXAMPLES]]\n EXAMPLES\ndiff --git a/cache.h b/cache.h\nindex 7109765748..a2e318c62b 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -472,6 +472,7 @@ static inline enum object_type object_type(unsigned int mode)\n #define TEMPLATE_DIR_ENVIRONMENT \"GIT_TEMPLATE_DIR\"\n #define CONFIG_ENVIRONMENT \"GIT_CONFIG\"\n #define CONFIG_DATA_ENVIRONMENT \"GIT_CONFIG_PARAMETERS\"\n+#define CONFIG_COUNT_ENVIRONMENT \"GIT_CONFIG_COUNT\"\n #define EXEC_PATH_ENVIRONMENT \"GIT_EXEC_PATH\"\n #define CEILING_DIRECTORIES_ENVIRONMENT \"GIT_CEILING_DIRECTORIES\"\n #define NO_REPLACE_OBJECTS_ENVIRONMENT \"GIT_NO_REPLACE_OBJECTS\"\ndiff --git a/config.c b/config.c\nindex 33099a3b0d..2627a05e91 100644\n--- a/config.c\n+++ b/config.c\n@@ -8,6 +8,7 @@\n #include \"cache.h\"\n #include \"branch.h\"\n #include \"config.h\"\n+#include \"environment.h\"\n #include \"repository.h\"\n #include \"lockfile.h\"\n #include \"exec-cmd.h\"\n@@ -597,23 +598,73 @@ static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n \n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n-\tconst char *env = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tconst char *env;\n+\tstruct strbuf envvar = STRBUF_INIT;\n+\tstruct strvec to_free = STRVEC_INIT;\n \tint ret = 0;\n-\tchar *envw;\n+\tchar *envw = NULL;\n \tstruct config_source source;\n \n-\tif (!env)\n-\t\treturn 0;\n-\n \tmemset(&source, 0, sizeof(source));\n \tsource.prev = cf;\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n \tcf = &source;\n \n-\t/* sq_dequote will write over it */\n-\tenvw = xstrdup(env);\n-\tret = parse_config_env_list(envw, fn, data);\n+\tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n+\tif (env) {\n+\t\tunsigned long count;\n+\t\tchar *endp;\n+\t\tint i;\n \n+\t\tcount = strtoul(env, &endp, 10);\n+\t\tif (*endp) {\n+\t\t\tret = error(_(\"bogus count in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (count > INT_MAX) {\n+\t\t\tret = error(_(\"too many entries in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (i = 0; i < count; i++) {\n+\t\t\tconst char *key, *value;\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_KEY_%d\", i);\n+\t\t\tkey = getenv_safe(&to_free, envvar.buf);\n+\t\t\tif (!key) {\n+\t\t\t\tret = error(_(\"missing config key %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_VALUE_%d\", i);\n+\t\t\tvalue = getenv_safe(&to_free, envvar.buf);\n+\t\t\tif (!value) {\n+\t\t\t\tret = error(_(\"missing config value %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tenv = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tif (env) {\n+\t\t/* sq_dequote will write over it */\n+\t\tenvw = xstrdup(env);\n+\t\tif (parse_config_env_list(envw, fn, data) < 0) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+out:\n+\tstrbuf_release(&envvar);\n+\tstrvec_clear(&to_free);\n \tfree(envw);\n \tcf = source.prev;\n \treturn ret;\ndiff --git a/environment.c b/environment.c\nindex 2234af462c..2f27008424 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -117,6 +117,7 @@ const char * const local_repo_env[] = {\n \tALTERNATE_DB_ENVIRONMENT,\n \tCONFIG_ENVIRONMENT,\n \tCONFIG_DATA_ENVIRONMENT,\n+\tCONFIG_COUNT_ENVIRONMENT,\n \tDB_ENVIRONMENT,\n \tGIT_DIR_ENVIRONMENT,\n \tGIT_WORK_TREE_ENVIRONMENT,\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 0063e9f059..6ecf2c11a7 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1423,6 +1423,117 @@ test_expect_success '--config-env handles keys with equals' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'git config handles environment config pairs' '\n+\tGIT_CONFIG_COUNT=2 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"foo\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"bar\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one foo\n+\tpair.two bar\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs without count' '\n+\ttest_must_fail env GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one 2>error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one\n+'\n+\n+test_expect_success 'git config ignores pairs exceeding count' '\n+\tGIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"value\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with empty count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT= GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config fails with invalid count' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=10a git config --list 2>error &&\n+\ttest_i18ngrep \"bogus count\" error &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=9999999999999999 git config --list 2>error &&\n+\ttest_i18ngrep \"too many entries\" error\n+'\n+\n+test_expect_success 'git config fails with missing config key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config key\" error\n+'\n+\n+test_expect_success 'git config fails with missing config value' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=\"pair.one\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config value\" error\n+'\n+\n+test_expect_success 'git config fails with invalid config pair key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0= GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=missing-section GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list\n+'\n+\n+test_expect_success 'environment overrides config file' '\n+\ttest_when_finished \"rm -f .git/config\" &&\n+\tcat >.git/config <<-EOF &&\n+\t[pair]\n+\tone = value\n+\tEOF\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=override \\\n+\t\tgit config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'GIT_CONFIG_PARAMETERS overrides environment config' '\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n+\t\tGIT_CONFIG_PARAMETERS=\"${SQ}pair.one=override${SQ}\" \\\n+\t\tgit config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'command line overrides environment config' '\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n+\t\tgit -c pair.one=override config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n@@ -1768,9 +1879,11 @@ test_expect_success '--show-origin with --list' '\n \tfile:.git/config\tuser.override=local\n \tfile:.git/config\tinclude.path=../include/relative.include\n \tfile:.git/../include/relative.include\tuser.relative=include\n+\tcommand line:\tuser.environ=true\n \tcommand line:\tuser.cmdline=true\n \tEOF\n-\tgit -c user.cmdline=true config --list --show-origin >output &&\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=user.environ GIT_CONFIG_VALUE_0=true\\\n+\t\tgit -c user.cmdline=true config --list --show-origin >output &&\n \ttest_cmp expect output\n '\n \n-- \n2.30.0\n\n"},{"id":"414081","messageId":"xmqq1rer6r2g.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"b9cf47afe896f8a6a76ba2e8aa87155e147ff31d.1610353895.git.ps@pks.im","subject":"Re: [PATCH v7 2/8] config: add new way to pass config via `--config-env`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-11T22:34:31Z","receivedAt":"2021-01-11T22:35:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> +void git_config_push_env(const char *spec)\n> +{\n> +\tstruct strbuf buf = STRBUF_INIT;\n> +\tconst char *env_name;\n> +\tconst char *env_value;\n> +\n> +\tenv_name = strrchr(spec, '=');\n> +\tif (!env_name)\n> +\t\tdie(_(\"invalid config format: %s\"), spec);\n> +\tenv_name++;\n> +\tif (!*env_name)\n> +\t\tdie(_(\"missing value for --config-env\"));\n\nIf reporting the name of the configuration variable, for which we\nchecked an environment variable, is worth doing in the !env_value\ncase below, shouldn't we be doing the same here, too?  I.e.\n\n\t\tdie(_(\"missing environment variable name in %s\", spec));;\n\nor something to complain against \"git --config-env foo=\"?\n\n> +\tenv_value = getenv(env_name);\n> +\tif (!env_value)\n> +\t\tdie(_(\"missing environment variable '%s' for configuration '%.*s'\"),\n> +\t\t    env_name, (int)(env_name - spec - 1), spec);\n\n> +test_expect_success 'git --config-env=key=envvar support' '\n> +\tcat >expect <<-\\EOF &&\n> +\tvalue\n> +\tvalue\n> +\tfalse\n> +\tEOF\n> +\t{\n> +\t\tenv ENVVAR=value git --config-env=core.name=ENVVAR config core.name &&\n> +\t\tenv ENVVAR=value git --config-env=foo.CamelCase=ENVVAR config foo.camelcase &&\n> +\t\tenv ENVVAR= git --config-env=foo.flag=ENVVAR config --bool foo.flag\n\nThese \"env \" prefixes are not wrong per-se but are unnecessary.  The\nsame for the rest of this patch.\n\n> +\t} >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git --config-env fails with invalid parameters' '\n> +\ttest_must_fail git --config-env=foo.flag config --bool foo.flag 2>error &&\n> +\ttest_i18ngrep \"invalid config format\" error &&\n> +\ttest_must_fail git --config-env=foo.flag= config --bool foo.flag 2>error &&\n> +\ttest_i18ngrep \"missing value for --config-env\" error &&\n> +\ttest_must_fail git --config-env=foo.flag=NONEXISTENT config --bool foo.flag 2>error &&\n\nHow are we making sure \n\n\t$ NONEXISTENT=True make test\n\nis not what the end-user is running?\n\n\tsane_unset X &&\n\ttest_must_fail git --config-env foo.flag=X config --bool foo.flag\n\nor something along that line, perhaps?\n"},{"id":"414134","messageId":"cover.1610453228.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1606214397.git.ps@pks.im","subject":"[PATCH v8 0/8] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-12T12:26:35Z","receivedAt":"2021-01-12T12:27:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is the eighth version of my patch series which aims to implement a\nway to pass config entries via the environment while avoiding any\nrequirements to perform shell quoting on the user's side.\n\nChanges were all proposed by Junio and are solely in patch 2/8:\n\n    - Improved the error message to include the the config key when the\n      environment variable name is missing.\n\n    - Dropped needless use of env(1) in added tests.\n\n    - The NONEXISTENT envvar is being unset now to fix the case where\n      tests may be run with that variable being set by the user.\n\nPlease see the attached range-diff for more details.\n\nPatrick\n\n\nJeff King (2):\n  quote: make sq_dequote_step() a public function\n  config: parse more robust format in GIT_CONFIG_PARAMETERS\n\nPatrick Steinhardt (6):\n  git: add `--super-prefix` to usage string\n  config: add new way to pass config via `--config-env`\n  config: extract function to parse config pairs\n  config: store \"git -c\" variables using more robust format\n  environment: make `getenv_safe()` a public function\n  config: allow specifying config entries via envvar pairs\n\n Documentation/git-config.txt |  16 +++\n Documentation/git.txt        |  24 +++-\n cache.h                      |   1 +\n config.c                     | 209 ++++++++++++++++++++++++++++----\n config.h                     |   1 +\n environment.c                |   8 +-\n environment.h                |  12 ++\n git.c                        |   3 +\n quote.c                      |  15 ++-\n quote.h                      |  18 ++-\n t/t1300-config.sh            | 223 ++++++++++++++++++++++++++++++++++-\n 11 files changed, 491 insertions(+), 39 deletions(-)\n create mode 100644 environment.h\n\nRange-diff against v7:\n1:  55fa4d0d11 = 1:  55fa4d0d11 git: add `--super-prefix` to usage string\n2:  b9cf47afe8 ! 2:  470396d36f config: add new way to pass config via `--config-env`\n    @@ config.c: void git_config_push_parameter(const char *text)\n     +\t\tdie(_(\"invalid config format: %s\"), spec);\n     +\tenv_name++;\n     +\tif (!*env_name)\n    -+\t\tdie(_(\"missing value for --config-env\"));\n    ++\t\tdie(_(\"missing environment variable name for configuration '%.*s'\"),\n    ++\t\t    (int)(env_name - spec - 1), spec);\n     +\n     +\tenv_value = getenv(env_name);\n     +\tif (!env_value)\n    @@ t/t1300-config.sh: test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n     +\tfalse\n     +\tEOF\n     +\t{\n    -+\t\tenv ENVVAR=value git --config-env=core.name=ENVVAR config core.name &&\n    -+\t\tenv ENVVAR=value git --config-env=foo.CamelCase=ENVVAR config foo.camelcase &&\n    -+\t\tenv ENVVAR= git --config-env=foo.flag=ENVVAR config --bool foo.flag\n    ++\t\tENVVAR=value git --config-env=core.name=ENVVAR config core.name &&\n    ++\t\tENVVAR=value git --config-env=foo.CamelCase=ENVVAR config foo.camelcase &&\n    ++\t\tENVVAR= git --config-env=foo.flag=ENVVAR config --bool foo.flag\n     +\t} >actual &&\n     +\ttest_cmp expect actual\n     +'\n     +\n     +test_expect_success 'git --config-env fails with invalid parameters' '\n     +\ttest_must_fail git --config-env=foo.flag config --bool foo.flag 2>error &&\n    -+\ttest_i18ngrep \"invalid config format\" error &&\n    ++\ttest_i18ngrep \"invalid config format: foo.flag\" error &&\n     +\ttest_must_fail git --config-env=foo.flag= config --bool foo.flag 2>error &&\n    -+\ttest_i18ngrep \"missing value for --config-env\" error &&\n    ++\ttest_i18ngrep \"missing environment variable name for configuration ${SQ}foo.flag${SQ}\" error &&\n    ++\tsane_unset NONEXISTENT &&\n     +\ttest_must_fail git --config-env=foo.flag=NONEXISTENT config --bool foo.flag 2>error &&\n     +\ttest_i18ngrep \"missing environment variable ${SQ}NONEXISTENT${SQ} for configuration ${SQ}foo.flag${SQ}\" error\n     +'\n    @@ t/t1300-config.sh: test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n     +\tbar.cmd cmd-value\n     +\tbar.env env-value\n     +\tEOF\n    -+\tenv ENVVAR=env-value git \\\n    ++\tENVVAR=env-value git \\\n     +\t\t-c bar.cmd=cmd-value \\\n     +\t\t--config-env=bar.env=ENVVAR \\\n     +\t\tconfig --get-regexp \"^bar.*\" >actual &&\n    @@ t/t1300-config.sh: test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n     +\tcmd\n     +\tEOF\n     +\t{\n    -+\t\tenv ENVVAR=env git -c bar.bar=cmd --config-env=bar.bar=ENVVAR config bar.bar &&\n    -+\t\tenv ENVVAR=env git --config-env=bar.bar=ENVVAR -c bar.bar=cmd config bar.bar\n    ++\t\tENVVAR=env git -c bar.bar=cmd --config-env=bar.bar=ENVVAR config bar.bar &&\n    ++\t\tENVVAR=env git --config-env=bar.bar=ENVVAR -c bar.bar=cmd config bar.bar\n     +\t} >actual &&\n     +\ttest_cmp expect actual\n     +'\n3:  1b47f0db98 = 3:  7a7a4ae234 quote: make sq_dequote_step() a public function\n4:  b9565a050e = 4:  39552eb8b9 config: extract function to parse config pairs\n5:  8f998ac81a ! 5:  36c2a51b13 config: store \"git -c\" variables using more robust format\n    @@ config.c: void git_config_push_parameter(const char *text)\n     +\tkey = xmemdupz(spec, env_name - spec);\n      \tenv_name++;\n      \tif (!*env_name)\n    - \t\tdie(_(\"missing value for --config-env\"));\n    + \t\tdie(_(\"missing environment variable name for configuration '%.*s'\"),\n     @@ config.c: void git_config_push_env(const char *spec)\n      \t\tdie(_(\"missing environment variable '%s' for configuration '%.*s'\"),\n      \t\t    env_name, (int)(env_name - spec - 1), spec);\n6:  e7b073c9dc = 6:  d67a3c0f9f config: parse more robust format in GIT_CONFIG_PARAMETERS\n7:  6c1800a18f = 7:  28cc229ade environment: make `getenv_safe()` a public function\n8:  ac9e778704 = 8:  07697b0c21 config: allow specifying config entries via envvar pairs\n-- \n2.30.0\n\n"},{"id":"414135","messageId":"470396d36f938f0070b8c849a85b1a30949056e3.1610453228.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610453228.git.ps@pks.im","subject":"[PATCH v8 2/8] config: add new way to pass config via `--config-env`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-12T12:26:45Z","receivedAt":"2021-01-12T12:27:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While it's already possible to pass runtime configuration via `git -c\n<key>=<value>`, it may be undesirable to use when the value contains\nsensitive information. E.g. if one wants to set `http.extraHeader` to\ncontain an authentication token, doing so via `-c` would trivially leak\nthose credentials via e.g. ps(1), which typically also shows command\narguments.\n\nTo enable this usecase without leaking credentials, this commit\nintroduces a new switch `--config-env=<key>=<envvar>`. Instead of\ndirectly passing a value for the given key, it instead allows the user\nto specify the name of an environment variable. The value of that\nvariable will then be used as value of the key.\n\nCo-authored-by: Jeff King <peff@peff.net>\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git.txt | 24 +++++++++++++++++++++-\n config.c              | 25 ++++++++++++++++++++++\n config.h              |  1 +\n git.c                 |  4 +++-\n t/t1300-config.sh     | 48 +++++++++++++++++++++++++++++++++++++++++++\n 5 files changed, 100 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git.txt b/Documentation/git.txt\nindex a6d4ad0818..d36e6fd482 100644\n--- a/Documentation/git.txt\n+++ b/Documentation/git.txt\n@@ -13,7 +13,7 @@ SYNOPSIS\n     [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n     [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]\n     [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n-    [--super-prefix=<path>]\n+    [--super-prefix=<path>] [--config-env <name>=<envvar>]\n     <command> [<args>]\n \n DESCRIPTION\n@@ -80,6 +80,28 @@ config file). Including the equals but with an empty value (like `git -c\n foo.bar= ...`) sets `foo.bar` to the empty string which `git config\n --type=bool` will convert to `false`.\n \n+--config-env=<name>=<envvar>::\n+\tLike `-c <name>=<value>`, give configuration variable\n+\t'<name>' a value, where <envvar> is the name of an\n+\tenvironment variable from which to retrieve the value. Unlike\n+\t`-c` there is no shortcut for directly setting the value to an\n+\tempty string, instead the environment variable itself must be\n+\tset to the empty string.  It is an error if the `<envvar>` does not exist\n+\tin the environment. `<envvar>` may not contain an equals sign\n+\tto avoid ambiguity with `<name>`s which contain one.\n++\n+This is useful for cases where you want to pass transitory\n+configuration options to git, but are doing so on OS's where\n+other processes might be able to read your cmdline\n+(e.g. `/proc/self/cmdline`), but not your environ\n+(e.g. `/proc/self/environ`). That behavior is the default on\n+Linux, but may not be on your system.\n++\n+Note that this might add security for variables such as\n+`http.extraHeader` where the sensitive information is part of\n+the value, but not e.g. `url.<base>.insteadOf` where the\n+sensitive information can be part of the key.\n+\n --exec-path[=<path>]::\n \tPath to wherever your core Git programs are installed.\n \tThis can also be controlled by setting the GIT_EXEC_PATH\ndiff --git a/config.c b/config.c\nindex 1137bd73af..fd8c0c4dfc 100644\n--- a/config.c\n+++ b/config.c\n@@ -345,6 +345,31 @@ void git_config_push_parameter(const char *text)\n \tstrbuf_release(&env);\n }\n \n+void git_config_push_env(const char *spec)\n+{\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tconst char *env_name;\n+\tconst char *env_value;\n+\n+\tenv_name = strrchr(spec, '=');\n+\tif (!env_name)\n+\t\tdie(_(\"invalid config format: %s\"), spec);\n+\tenv_name++;\n+\tif (!*env_name)\n+\t\tdie(_(\"missing environment variable name for configuration '%.*s'\"),\n+\t\t    (int)(env_name - spec - 1), spec);\n+\n+\tenv_value = getenv(env_name);\n+\tif (!env_value)\n+\t\tdie(_(\"missing environment variable '%s' for configuration '%.*s'\"),\n+\t\t    env_name, (int)(env_name - spec - 1), spec);\n+\n+\tstrbuf_add(&buf, spec, env_name - spec);\n+\tstrbuf_addstr(&buf, env_value);\n+\tgit_config_push_parameter(buf.buf);\n+\tstrbuf_release(&buf);\n+}\n+\n static inline int iskeychar(int c)\n {\n \treturn isalnum(c) || c == '-';\ndiff --git a/config.h b/config.h\nindex c1449bb790..19a9adbaa9 100644\n--- a/config.h\n+++ b/config.h\n@@ -138,6 +138,7 @@ int git_config_from_mem(config_fn_t fn,\n int git_config_from_blob_oid(config_fn_t fn, const char *name,\n \t\t\t     const struct object_id *oid, void *data);\n void git_config_push_parameter(const char *text);\n+void git_config_push_env(const char *spec);\n int git_config_from_parameters(config_fn_t fn, void *data);\n void read_early_config(config_fn_t cb, void *data);\n void read_very_early_config(config_fn_t cb, void *data);\ndiff --git a/git.c b/git.c\nindex 5a8ff12f87..b5f63d346b 100644\n--- a/git.c\n+++ b/git.c\n@@ -29,7 +29,7 @@ const char git_usage_string[] =\n \t   \"           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\\n\"\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\\n\"\n \t   \"           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\\n\"\n-\t   \"           [--super-prefix=<path>]\\n\"\n+\t   \"           [--super-prefix=<path>] [--config-env=<name>=<envvar>]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n@@ -255,6 +255,8 @@ static int handle_options(const char ***argv, int *argc, int *envchanged)\n \t\t\tgit_config_push_parameter((*argv)[1]);\n \t\t\t(*argv)++;\n \t\t\t(*argc)--;\n+\t\t} else if (skip_prefix(cmd, \"--config-env=\", &cmd)) {\n+\t\t\tgit_config_push_env(cmd);\n \t\t} else if (!strcmp(cmd, \"--literal-pathspecs\")) {\n \t\t\tsetenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, \"1\", 1);\n \t\t\tif (envchanged)\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 97a04c6cc2..853f2509c5 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1316,6 +1316,54 @@ test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n \t\tgit config --get-regexp \"env.*\"\n '\n \n+test_expect_success 'git --config-env=key=envvar support' '\n+\tcat >expect <<-\\EOF &&\n+\tvalue\n+\tvalue\n+\tfalse\n+\tEOF\n+\t{\n+\t\tENVVAR=value git --config-env=core.name=ENVVAR config core.name &&\n+\t\tENVVAR=value git --config-env=foo.CamelCase=ENVVAR config foo.camelcase &&\n+\t\tENVVAR= git --config-env=foo.flag=ENVVAR config --bool foo.flag\n+\t} >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git --config-env fails with invalid parameters' '\n+\ttest_must_fail git --config-env=foo.flag config --bool foo.flag 2>error &&\n+\ttest_i18ngrep \"invalid config format: foo.flag\" error &&\n+\ttest_must_fail git --config-env=foo.flag= config --bool foo.flag 2>error &&\n+\ttest_i18ngrep \"missing environment variable name for configuration ${SQ}foo.flag${SQ}\" error &&\n+\tsane_unset NONEXISTENT &&\n+\ttest_must_fail git --config-env=foo.flag=NONEXISTENT config --bool foo.flag 2>error &&\n+\ttest_i18ngrep \"missing environment variable ${SQ}NONEXISTENT${SQ} for configuration ${SQ}foo.flag${SQ}\" error\n+'\n+\n+test_expect_success 'git -c and --config-env work together' '\n+\tcat >expect <<-\\EOF &&\n+\tbar.cmd cmd-value\n+\tbar.env env-value\n+\tEOF\n+\tENVVAR=env-value git \\\n+\t\t-c bar.cmd=cmd-value \\\n+\t\t--config-env=bar.env=ENVVAR \\\n+\t\tconfig --get-regexp \"^bar.*\" >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git -c and --config-env override each other' '\n+\tcat >expect <<-\\EOF &&\n+\tenv\n+\tcmd\n+\tEOF\n+\t{\n+\t\tENVVAR=env git -c bar.bar=cmd --config-env=bar.bar=ENVVAR config bar.bar &&\n+\t\tENVVAR=env git --config-env=bar.bar=ENVVAR -c bar.bar=cmd config bar.bar\n+\t} >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n-- \n2.30.0\n\n"},{"id":"414136","messageId":"39552eb8b931442e656d0287447fce3b2cffa87a.1610453228.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610453228.git.ps@pks.im","subject":"[PATCH v8 4/8] config: extract function to parse config pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-12T12:26:54Z","receivedAt":"2021-01-12T12:27:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The function `git_config_parse_parameter` is responsible for parsing a\n`foo.bar=baz`-formatted configuration key, sanitizing the key and then\nprocessing it via the given callback function. Given that we're about to\nadd a second user which is going to process keys which already has keys\nand values separated, this commit extracts a function\n`config_parse_pair` which only does the sanitization and processing\npart as a preparatory step.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.c | 24 +++++++++++++++++-------\n 1 file changed, 17 insertions(+), 7 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex fd8c0c4dfc..b7a8129f6c 100644\n--- a/config.c\n+++ b/config.c\n@@ -462,11 +462,26 @@ int git_config_key_is_valid(const char *key)\n \treturn !git_config_parse_key_1(key, NULL, NULL, 1);\n }\n \n+static int config_parse_pair(const char *key, const char *value,\n+\t\t\t  config_fn_t fn, void *data)\n+{\n+\tchar *canonical_name;\n+\tint ret;\n+\n+\tif (!strlen(key))\n+\t\treturn error(_(\"empty config key\"));\n+\tif (git_config_parse_key(key, &canonical_name, NULL))\n+\t\treturn -1;\n+\n+\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n+\tfree(canonical_name);\n+\treturn ret;\n+}\n+\n int git_config_parse_parameter(const char *text,\n \t\t\t       config_fn_t fn, void *data)\n {\n \tconst char *value;\n-\tchar *canonical_name;\n \tstruct strbuf **pair;\n \tint ret;\n \n@@ -487,12 +502,7 @@ int git_config_parse_parameter(const char *text,\n \t\treturn error(_(\"bogus config parameter: %s\"), text);\n \t}\n \n-\tif (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {\n-\t\tret = -1;\n-\t} else {\n-\t\tret = (fn(canonical_name, value, data) < 0) ? -1 : 0;\n-\t\tfree(canonical_name);\n-\t}\n+\tret = config_parse_pair(pair[0]->buf, value, fn, data);\n \tstrbuf_list_free(pair);\n \treturn ret;\n }\n-- \n2.30.0\n\n"},{"id":"414137","messageId":"55fa4d0d11f92c5b3c86c47b91ca5f4ceab2f81a.1610453228.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610453228.git.ps@pks.im","subject":"[PATCH v8 1/8] git: add `--super-prefix` to usage string","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-12T12:26:40Z","receivedAt":"2021-01-12T12:27:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When the `--super-prefix` option was implmented in 74866d7579 (git: make\nsuper-prefix option, 2016-10-07), its existence was only documented in\nthe manpage but not in the command's own usage string. Given that the\ncommit message didn't mention that this was done intentionally and given\nthat it's documented in the manpage, this seems like an oversight.\n\nAdd it to the usage string to fix the inconsistency.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n git.c | 1 +\n 1 file changed, 1 insertion(+)\n\ndiff --git a/git.c b/git.c\nindex a00a0a4d94..5a8ff12f87 100644\n--- a/git.c\n+++ b/git.c\n@@ -29,6 +29,7 @@ const char git_usage_string[] =\n \t   \"           [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\\n\"\n \t   \"           [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare]\\n\"\n \t   \"           [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\\n\"\n+\t   \"           [--super-prefix=<path>]\\n\"\n \t   \"           <command> [<args>]\");\n \n const char git_more_info_string[] =\n-- \n2.30.0\n\n"},{"id":"414138","messageId":"7a7a4ae234d30c56e04c19a5e5b47afaa8680c72.1610453228.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610453228.git.ps@pks.im","subject":"[PATCH v8 3/8] quote: make sq_dequote_step() a public function","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-12T12:26:49Z","receivedAt":"2021-01-12T12:28:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Jeff King <peff@peff.net>\n\nWe provide a function for dequoting an entire string, as well as one for\nhandling a space-separated list of quoted strings. But there's no way\nfor a caller to parse a string like 'foo'='bar', even though it is easy\nto generate one using sq_quote_buf() or similar.\n\nLet's make the single-step function available to callers outside of\nquote.c. Note that we do need to adjust its implementation slightly: it\ninsists on seeing whitespace between items, and we'd like to be more\nflexible than that. Since it only has a single caller, we can move that\ncheck (and slurping up any extra whitespace) into that caller.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n quote.c | 15 ++++++++++-----\n quote.h | 18 ++++++++++++++++--\n 2 files changed, 26 insertions(+), 7 deletions(-)\n\ndiff --git a/quote.c b/quote.c\nindex 69f4ca45da..8a3a5e39eb 100644\n--- a/quote.c\n+++ b/quote.c\n@@ -116,7 +116,7 @@ void sq_append_quote_argv_pretty(struct strbuf *dst, const char **argv)\n \t}\n }\n \n-static char *sq_dequote_step(char *arg, char **next)\n+char *sq_dequote_step(char *arg, char **next)\n {\n \tchar *dst = arg;\n \tchar *src = arg;\n@@ -153,11 +153,8 @@ static char *sq_dequote_step(char *arg, char **next)\n \t\t\t}\n \t\t/* Fallthrough */\n \t\tdefault:\n-\t\t\tif (!next || !isspace(*src))\n+\t\t\tif (!next)\n \t\t\t\treturn NULL;\n-\t\t\tdo {\n-\t\t\t\tc = *++src;\n-\t\t\t} while (isspace(c));\n \t\t\t*dst = 0;\n \t\t\t*next = src;\n \t\t\treturn arg;\n@@ -182,6 +179,14 @@ static int sq_dequote_to_argv_internal(char *arg,\n \t\tchar *dequoted = sq_dequote_step(next, &next);\n \t\tif (!dequoted)\n \t\t\treturn -1;\n+\t\tif (next) {\n+\t\t\tchar c;\n+\t\t\tif (!isspace(*next))\n+\t\t\t\treturn -1;\n+\t\t\tdo {\n+\t\t\t\tc = *++next;\n+\t\t\t} while (isspace(c));\n+\t\t}\n \t\tif (argv) {\n \t\t\tALLOC_GROW(*argv, *nr + 1, *alloc);\n \t\t\t(*argv)[(*nr)++] = dequoted;\ndiff --git a/quote.h b/quote.h\nindex 4b72a583cf..768cc6338e 100644\n--- a/quote.h\n+++ b/quote.h\n@@ -42,12 +42,26 @@ void sq_quote_buf_pretty(struct strbuf *, const char *src);\n void sq_quote_argv_pretty(struct strbuf *, const char **argv);\n void sq_append_quote_argv_pretty(struct strbuf *dst, const char **argv);\n \n-/* This unwraps what sq_quote() produces in place, but returns\n+/*\n+ * This unwraps what sq_quote() produces in place, but returns\n  * NULL if the input does not look like what sq_quote would have\n- * produced.\n+ * produced (the full string must be a single quoted item).\n  */\n char *sq_dequote(char *);\n \n+/*\n+ * Like sq_dequote(), but dequote a single item, and leave \"next\" pointing to\n+ * the next character. E.g., in the string:\n+ *\n+ *   'one' 'two' 'three'\n+ *\n+ * after the first call, the return value would be the unquoted string \"one\",\n+ * with \"next\" pointing to the space between \"one\" and \"two\"). The caller is\n+ * responsible for advancing the pointer to the start of the next item before\n+ * calling sq_dequote_step() again.\n+ */\n+char *sq_dequote_step(char *src, char **next);\n+\n /*\n  * Same as the above, but can be used to unwrap many arguments in the\n  * same string separated by space. Like sq_quote, it works in place,\n-- \n2.30.0\n\n"},{"id":"414139","messageId":"d67a3c0f9f37288e2d5e2ab6dbe88c2bb8971fc2.1610453228.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610453228.git.ps@pks.im","subject":"[PATCH v8 6/8] config: parse more robust format in GIT_CONFIG_PARAMETERS","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-12T12:27:06Z","receivedAt":"2021-01-12T12:28:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"From: Jeff King <peff@peff.net>\n\nWhen we stuff config options into GIT_CONFIG_PARAMETERS, we shell-quote\neach one as a single unit, like:\n\n  'section.one=value1' 'section.two=value2'\n\nOn the reading side, we de-quote to get the individual strings, and then\nparse them by splitting on the first \"=\" we find. This format is\nambiguous, because an \"=\" may appear in a subsection. So the config\nrepresented in a file by both:\n\n  [section \"subsection=with=equals\"]\n  key = value\n\nand:\n\n  [section]\n  subsection = with=equals.key=value\n\nends up in this flattened format like:\n\n  'section.subsection=with=equals.key=value'\n\nand we can't tell which was desired. We have traditionally resolved this\nby taking the first \"=\" we see starting from the left, meaning that we\nallowed arbitrary content in the value, but not in the subsection.\n\nLet's make our environment format a bit more robust by separately\nquoting the key and value. That turns those examples into:\n\n  'section.subsection=with=equals.key'='value'\n\nand:\n\n  'section.subsection'='with=equals.key=value'\n\nrespectively, and we can tell the difference between them. We can detect\nwhich format is in use for any given element of the list based on the\npresence of the unquoted \"=\". That means we can continue to allow the\nold format to work to support any callers which manually used the old\nformat, and we can even intermingle the two formats. The old format\nwasn't documented, and nobody was supposed to be using it. But it's\nlikely that such callers exist in the wild, so it's nice if we can avoid\nbreaking them. Likewise, it may be possible to trigger an older version\nof \"git -c\" that runs a script that calls into a newer version of \"git\n-c\"; that new version would see the intermingled format.\n\nThis does create one complication, which is that the obvious format in\nthe new scheme for\n\n  [section]\n  some-bool\n\nis:\n\n  'section.some-bool'\n\nwith no equals. We'd mistake that for an old-style variable. And it even\nhas the same meaning in the old style, but:\n\n  [section \"with=equals\"]\n  some-bool\n\ndoes not. It would be:\n\n  'section.with=equals=some-bool'\n\nwhich we'd take to mean:\n\n  [section]\n  with = equals=some-bool\n\nin the old, ambiguous style. Likewise, we can't use:\n\n  'section.some-bool'=''\n\nbecause that's ambiguous with an actual empty string. Instead, we'll\nagain use the shell-quoting to give us a hint, and use:\n\n  'section.some-bool'=\n\nto show that we have no value.\n\nNote that this commit just expands the reading side. We'll start writing\nthe new format via \"git -c\" in a future patch. In the meantime, the\nexisting \"git -c\" tests will make sure we didn't break reading the old\nformat. But we'll also add some explicit coverage of the two formats to\nmake sure we continue to handle the old one after we move the writing\nside over.\n\nAnd one final note: since we're now using the shell-quoting as a\nsemantically meaningful hint, this closes the door to us ever allowing\narbitrary shell quoting, like:\n\n  'a'shell'would'be'ok'with'this'.key=value\n\nBut we have never supported that (only what sq_quote() would produce),\nand we are probably better off keeping things simple, robust, and\nbackwards-compatible, than trying to make it easier for humans. We'll\ncontinue not to advertise the format of the variable to users, and\ninstead keep \"git -c\" as the recommended mechanism for setting config\n(even if we are trying to be kind not to break users who may be relying\non the current undocumented format).\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n config.c          | 69 +++++++++++++++++++++++++++++++++++------------\n t/t1300-config.sh | 52 +++++++++++++++++++++++++++++++++++\n 2 files changed, 104 insertions(+), 17 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 7f7da60574..99062915d7 100644\n--- a/config.c\n+++ b/config.c\n@@ -545,14 +545,62 @@ int git_config_parse_parameter(const char *text,\n \treturn ret;\n }\n \n+static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n+{\n+\tchar *cur = env;\n+\twhile (cur && *cur) {\n+\t\tconst char *key = sq_dequote_step(cur, &cur);\n+\t\tif (!key)\n+\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\n+\t\tif (!cur || isspace(*cur)) {\n+\t\t\t/* old-style 'key=value' */\n+\t\t\tif (git_config_parse_parameter(key, fn, data) < 0)\n+\t\t\t\treturn -1;\n+\t\t}\n+\t\telse if (*cur == '=') {\n+\t\t\t/* new-style 'key'='value' */\n+\t\t\tconst char *value;\n+\n+\t\t\tcur++;\n+\t\t\tif (*cur == '\\'') {\n+\t\t\t\t/* quoted value */\n+\t\t\t\tvalue = sq_dequote_step(cur, &cur);\n+\t\t\t\tif (!value || (cur && !isspace(*cur))) {\n+\t\t\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t\t\t}\n+\t\t\t} else if (!*cur || isspace(*cur)) {\n+\t\t\t\t/* implicit bool: 'key'= */\n+\t\t\t\tvalue = NULL;\n+\t\t\t} else {\n+\t\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t\t}\n+\n+\t\t\tif (config_parse_pair(key, value, fn, data) < 0)\n+\t\t\t\treturn -1;\n+\t\t}\n+\t\telse {\n+\t\t\t/* unknown format */\n+\t\t\treturn error(_(\"bogus format in %s\"),\n+\t\t\t\t     CONFIG_DATA_ENVIRONMENT);\n+\t\t}\n+\n+\t\tif (cur) {\n+\t\t\twhile (isspace(*cur))\n+\t\t\t\tcur++;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n \tconst char *env = getenv(CONFIG_DATA_ENVIRONMENT);\n \tint ret = 0;\n \tchar *envw;\n-\tconst char **argv = NULL;\n-\tint nr = 0, alloc = 0;\n-\tint i;\n \tstruct config_source source;\n \n \tif (!env)\n@@ -565,21 +613,8 @@ int git_config_from_parameters(config_fn_t fn, void *data)\n \n \t/* sq_dequote will write over it */\n \tenvw = xstrdup(env);\n+\tret = parse_config_env_list(envw, fn, data);\n \n-\tif (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {\n-\t\tret = error(_(\"bogus format in %s\"), CONFIG_DATA_ENVIRONMENT);\n-\t\tgoto out;\n-\t}\n-\n-\tfor (i = 0; i < nr; i++) {\n-\t\tif (git_config_parse_parameter(argv[i], fn, data) < 0) {\n-\t\t\tret = -1;\n-\t\t\tgoto out;\n-\t\t}\n-\t}\n-\n-out:\n-\tfree(argv);\n \tfree(envw);\n \tcf = source.prev;\n \treturn ret;\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 25437324c1..3f6778d474 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1294,6 +1294,58 @@ test_expect_success 'git -c is not confused by empty environment' '\n \tGIT_CONFIG_PARAMETERS=\"\" git -c x.one=1 config --list\n '\n \n+test_expect_success 'GIT_CONFIG_PARAMETERS handles old-style entries' '\n+\tv=\"${SQ}key.one=foo${SQ}\" &&\n+\tv=\"$v  ${SQ}key.two=bar${SQ}\" &&\n+\tv=\"$v ${SQ}key.ambiguous=section.whatever=value${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.one foo\n+\tkey.two bar\n+\tkey.ambiguous section.whatever=value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'GIT_CONFIG_PARAMETERS handles new-style entries' '\n+\tv=\"${SQ}key.one${SQ}=${SQ}foo${SQ}\" &&\n+\tv=\"$v  ${SQ}key.two${SQ}=${SQ}bar${SQ}\" &&\n+\tv=\"$v ${SQ}key.ambiguous=section.whatever${SQ}=${SQ}value${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.one foo\n+\tkey.two bar\n+\tkey.ambiguous=section.whatever value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'old and new-style entries can mix' '\n+\tv=\"${SQ}key.oldone=oldfoo${SQ}\" &&\n+\tv=\"$v ${SQ}key.newone${SQ}=${SQ}newfoo${SQ}\" &&\n+\tv=\"$v ${SQ}key.oldtwo=oldbar${SQ}\" &&\n+\tv=\"$v ${SQ}key.newtwo${SQ}=${SQ}newbar${SQ}\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.oldone oldfoo\n+\tkey.newone newfoo\n+\tkey.oldtwo oldbar\n+\tkey.newtwo newbar\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'old and new bools with ambiguous subsection' '\n+\tv=\"${SQ}key.with=equals.oldbool${SQ}\" &&\n+\tv=\"$v ${SQ}key.with=equals.newbool${SQ}=\" &&\n+\tGIT_CONFIG_PARAMETERS=$v git config --get-regexp \"key.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tkey.with equals.oldbool\n+\tkey.with=equals.newbool\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '\n \tcat >expect <<-\\EOF &&\n \tenv.one one\n-- \n2.30.0\n\n"},{"id":"414140","messageId":"07697b0c21db06c9a88cc54cf671db48aa8a2f8f.1610453228.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610453228.git.ps@pks.im","subject":"[PATCH v8 8/8] config: allow specifying config entries via envvar pairs","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-12T12:27:14Z","receivedAt":"2021-01-12T12:28:30Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While we currently have the `GIT_CONFIG_PARAMETERS` environment variable\nwhich can be used to pass runtime configuration data to git processes,\nit's an internal implementation detail and not supposed to be used by\nend users.\n\nNext to being for internal use only, this way of passing config entries\nhas a major downside: the config keys need to be parsed as they contain\nboth key and value in a single variable. As such, it is left to the user\nto escape any potentially harmful characters in the value, which is\nquite hard to do if values are controlled by a third party.\n\nThis commit thus adds a new way of adding config entries via the\nenvironment which gets rid of this shortcoming. If the user passes the\n`GIT_CONFIG_COUNT=$n` environment variable, Git will parse environment\nvariable pairs `GIT_CONFIG_KEY_$i` and `GIT_CONFIG_VALUE_$i` for each\n`i` in `[0,n)`.\n\nWhile the same can be achieved with `git -c <name>=<value>`, one may\nwish to not do so for potentially sensitive information. E.g. if one\nwants to set `http.extraHeader` to contain an authentication token,\ndoing so via `-c` would trivially leak those credentials via e.g. ps(1),\nwhich typically also shows command arguments.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n Documentation/git-config.txt |  16 +++++\n cache.h                      |   1 +\n config.c                     |  67 +++++++++++++++++---\n environment.c                |   1 +\n t/t1300-config.sh            | 115 ++++++++++++++++++++++++++++++++++-\n 5 files changed, 191 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-config.txt b/Documentation/git-config.txt\nindex 0e9351d3cb..4b4cc5c5e8 100644\n--- a/Documentation/git-config.txt\n+++ b/Documentation/git-config.txt\n@@ -346,6 +346,22 @@ GIT_CONFIG_NOSYSTEM::\n \n See also <<FILES>>.\n \n+GIT_CONFIG_COUNT::\n+GIT_CONFIG_KEY_<n>::\n+GIT_CONFIG_VALUE_<n>::\n+\tIf GIT_CONFIG_COUNT is set to a positive number, all environment pairs\n+\tGIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> up to that number will be\n+\tadded to the process's runtime configuration. The config pairs are\n+\tzero-indexed. Any missing key or value is treated as an error. An empty\n+\tGIT_CONFIG_COUNT is treated the same as GIT_CONFIG_COUNT=0, namely no\n+\tpairs are processed. These environment variables will override values\n+\tin configuration files, but will be overridden by any explicit options\n+\tpassed via `git -c`.\n++\n+This is useful for cases where you want to spawn multiple git commands\n+with a common configuration but cannot depend on a configuration file,\n+for example when writing scripts.\n+\n \n [[EXAMPLES]]\n EXAMPLES\ndiff --git a/cache.h b/cache.h\nindex 7109765748..a2e318c62b 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -472,6 +472,7 @@ static inline enum object_type object_type(unsigned int mode)\n #define TEMPLATE_DIR_ENVIRONMENT \"GIT_TEMPLATE_DIR\"\n #define CONFIG_ENVIRONMENT \"GIT_CONFIG\"\n #define CONFIG_DATA_ENVIRONMENT \"GIT_CONFIG_PARAMETERS\"\n+#define CONFIG_COUNT_ENVIRONMENT \"GIT_CONFIG_COUNT\"\n #define EXEC_PATH_ENVIRONMENT \"GIT_EXEC_PATH\"\n #define CEILING_DIRECTORIES_ENVIRONMENT \"GIT_CEILING_DIRECTORIES\"\n #define NO_REPLACE_OBJECTS_ENVIRONMENT \"GIT_NO_REPLACE_OBJECTS\"\ndiff --git a/config.c b/config.c\nindex 99062915d7..a32569438a 100644\n--- a/config.c\n+++ b/config.c\n@@ -8,6 +8,7 @@\n #include \"cache.h\"\n #include \"branch.h\"\n #include \"config.h\"\n+#include \"environment.h\"\n #include \"repository.h\"\n #include \"lockfile.h\"\n #include \"exec-cmd.h\"\n@@ -598,23 +599,73 @@ static int parse_config_env_list(char *env, config_fn_t fn, void *data)\n \n int git_config_from_parameters(config_fn_t fn, void *data)\n {\n-\tconst char *env = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tconst char *env;\n+\tstruct strbuf envvar = STRBUF_INIT;\n+\tstruct strvec to_free = STRVEC_INIT;\n \tint ret = 0;\n-\tchar *envw;\n+\tchar *envw = NULL;\n \tstruct config_source source;\n \n-\tif (!env)\n-\t\treturn 0;\n-\n \tmemset(&source, 0, sizeof(source));\n \tsource.prev = cf;\n \tsource.origin_type = CONFIG_ORIGIN_CMDLINE;\n \tcf = &source;\n \n-\t/* sq_dequote will write over it */\n-\tenvw = xstrdup(env);\n-\tret = parse_config_env_list(envw, fn, data);\n+\tenv = getenv(CONFIG_COUNT_ENVIRONMENT);\n+\tif (env) {\n+\t\tunsigned long count;\n+\t\tchar *endp;\n+\t\tint i;\n \n+\t\tcount = strtoul(env, &endp, 10);\n+\t\tif (*endp) {\n+\t\t\tret = error(_(\"bogus count in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (count > INT_MAX) {\n+\t\t\tret = error(_(\"too many entries in %s\"), CONFIG_COUNT_ENVIRONMENT);\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tfor (i = 0; i < count; i++) {\n+\t\t\tconst char *key, *value;\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_KEY_%d\", i);\n+\t\t\tkey = getenv_safe(&to_free, envvar.buf);\n+\t\t\tif (!key) {\n+\t\t\t\tret = error(_(\"missing config key %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tstrbuf_addf(&envvar, \"GIT_CONFIG_VALUE_%d\", i);\n+\t\t\tvalue = getenv_safe(&to_free, envvar.buf);\n+\t\t\tif (!value) {\n+\t\t\t\tret = error(_(\"missing config value %s\"), envvar.buf);\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t\tstrbuf_reset(&envvar);\n+\n+\t\t\tif (config_parse_pair(key, value, fn, data) < 0) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tenv = getenv(CONFIG_DATA_ENVIRONMENT);\n+\tif (env) {\n+\t\t/* sq_dequote will write over it */\n+\t\tenvw = xstrdup(env);\n+\t\tif (parse_config_env_list(envw, fn, data) < 0) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n+\n+out:\n+\tstrbuf_release(&envvar);\n+\tstrvec_clear(&to_free);\n \tfree(envw);\n \tcf = source.prev;\n \treturn ret;\ndiff --git a/environment.c b/environment.c\nindex 2234af462c..2f27008424 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -117,6 +117,7 @@ const char * const local_repo_env[] = {\n \tALTERNATE_DB_ENVIRONMENT,\n \tCONFIG_ENVIRONMENT,\n \tCONFIG_DATA_ENVIRONMENT,\n+\tCONFIG_COUNT_ENVIRONMENT,\n \tDB_ENVIRONMENT,\n \tGIT_DIR_ENVIRONMENT,\n \tGIT_WORK_TREE_ENVIRONMENT,\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 3f6778d474..89b47bf5bd 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1424,6 +1424,117 @@ test_expect_success '--config-env handles keys with equals' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'git config handles environment config pairs' '\n+\tGIT_CONFIG_COUNT=2 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"foo\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"bar\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one foo\n+\tpair.two bar\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs without count' '\n+\ttest_must_fail env GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one 2>error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one\n+'\n+\n+test_expect_success 'git config ignores pairs exceeding count' '\n+\tGIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tGIT_CONFIG_KEY_1=\"pair.two\" GIT_CONFIG_VALUE_1=\"value\" \\\n+\t\tgit config --get-regexp \"pair.*\" >actual &&\n+\tcat >expect <<-EOF &&\n+\tpair.one value\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git config ignores pairs with zero count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT=0 GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config ignores pairs with empty count' '\n+\ttest_must_fail env \\\n+\t\tGIT_CONFIG_COUNT= GIT_CONFIG_KEY_0=\"pair.one\" GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config pair.one >error &&\n+\ttest_must_be_empty error\n+'\n+\n+test_expect_success 'git config fails with invalid count' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=10a git config --list 2>error &&\n+\ttest_i18ngrep \"bogus count\" error &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=9999999999999999 git config --list 2>error &&\n+\ttest_i18ngrep \"too many entries\" error\n+'\n+\n+test_expect_success 'git config fails with missing config key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_VALUE_0=\"value\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config key\" error\n+'\n+\n+test_expect_success 'git config fails with missing config value' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=\"pair.one\" \\\n+\t\tgit config --list 2>error &&\n+\ttest_i18ngrep \"missing config value\" error\n+'\n+\n+test_expect_success 'git config fails with invalid config pair key' '\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0= GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list &&\n+\ttest_must_fail env GIT_CONFIG_COUNT=1 \\\n+\t\tGIT_CONFIG_KEY_0=missing-section GIT_CONFIG_VALUE_0=value \\\n+\t\tgit config --list\n+'\n+\n+test_expect_success 'environment overrides config file' '\n+\ttest_when_finished \"rm -f .git/config\" &&\n+\tcat >.git/config <<-EOF &&\n+\t[pair]\n+\tone = value\n+\tEOF\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=override \\\n+\t\tgit config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'GIT_CONFIG_PARAMETERS overrides environment config' '\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n+\t\tGIT_CONFIG_PARAMETERS=\"${SQ}pair.one=override${SQ}\" \\\n+\t\tgit config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'command line overrides environment config' '\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=pair.one GIT_CONFIG_VALUE_0=value \\\n+\t\tgit -c pair.one=override config pair.one >actual &&\n+\tcat >expect <<-EOF &&\n+\toverride\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n@@ -1769,9 +1880,11 @@ test_expect_success '--show-origin with --list' '\n \tfile:.git/config\tuser.override=local\n \tfile:.git/config\tinclude.path=../include/relative.include\n \tfile:.git/../include/relative.include\tuser.relative=include\n+\tcommand line:\tuser.environ=true\n \tcommand line:\tuser.cmdline=true\n \tEOF\n-\tgit -c user.cmdline=true config --list --show-origin >output &&\n+\tGIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=user.environ GIT_CONFIG_VALUE_0=true\\\n+\t\tgit -c user.cmdline=true config --list --show-origin >output &&\n \ttest_cmp expect output\n '\n \n-- \n2.30.0\n\n"},{"id":"414144","messageId":"36c2a51b13e463a4aa8e5316447336927153d99d.1610453228.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610453228.git.ps@pks.im","subject":"[PATCH v8 5/8] config: store \"git -c\" variables using more robust format","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-12T12:27:01Z","receivedAt":"2021-01-12T12:28:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The previous commit added a new format for $GIT_CONFIG_PARAMETERS which\nis able to robustly handle subsections with \"=\" in them. Let's start\nwriting the new format. Unfortunately, this does much less than you'd\nhope, because \"git -c\" itself has the same ambiguity problem! But it's\nstill worth doing:\n\n  - we've now pushed the problem from the inter-process communication\n    into the \"-c\" command-line parser. This would free us up to later\n    add an unambiguous format there (e.g., separate arguments like \"git\n    --config key value\", etc).\n\n  - for --config-env, the parser already disallows \"=\" in the\n    environment variable name. So:\n\n      git --config-env section.with=equals.key=ENVVAR\n\n    will robustly set section.with=equals.key to the contents of\n    $ENVVAR.\n\nThe new test shows the improvement for --config-env.\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n config.c          | 52 ++++++++++++++++++++++++++++++++++++++++-------\n t/t1300-config.sh |  8 ++++++++\n 2 files changed, 53 insertions(+), 7 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex b7a8129f6c..7f7da60574 100644\n--- a/config.c\n+++ b/config.c\n@@ -332,7 +332,7 @@ int git_config_include(const char *var, const char *value, void *data)\n \treturn ret;\n }\n \n-void git_config_push_parameter(const char *text)\n+static void git_config_push_split_parameter(const char *key, const char *value)\n {\n \tstruct strbuf env = STRBUF_INIT;\n \tconst char *old = getenv(CONFIG_DATA_ENVIRONMENT);\n@@ -340,20 +340,60 @@ void git_config_push_parameter(const char *text)\n \t\tstrbuf_addstr(&env, old);\n \t\tstrbuf_addch(&env, ' ');\n \t}\n-\tsq_quote_buf(&env, text);\n+\tsq_quote_buf(&env, key);\n+\tstrbuf_addch(&env, '=');\n+\tif (value)\n+\t\tsq_quote_buf(&env, value);\n \tsetenv(CONFIG_DATA_ENVIRONMENT, env.buf, 1);\n \tstrbuf_release(&env);\n }\n \n+void git_config_push_parameter(const char *text)\n+{\n+\tconst char *value;\n+\n+\t/*\n+\t * When we see:\n+\t *\n+\t *   section.subsection=with=equals.key=value\n+\t *\n+\t * we cannot tell if it means:\n+\t *\n+\t *   [section \"subsection=with=equals\"]\n+\t *   key = value\n+\t *\n+\t * or:\n+\t *\n+\t *   [section]\n+\t *   subsection = with=equals.key=value\n+\t *\n+\t * We parse left-to-right for the first \"=\", meaning we'll prefer to\n+\t * keep the value intact over the subsection. This is historical, but\n+\t * also sensible since values are more likely to contain odd or\n+\t * untrusted input than a section name.\n+\t *\n+\t * A missing equals is explicitly allowed (as a bool-only entry).\n+\t */\n+\tvalue = strchr(text, '=');\n+\tif (value) {\n+\t\tchar *key = xmemdupz(text, value - text);\n+\t\tgit_config_push_split_parameter(key, value + 1);\n+\t\tfree(key);\n+\t} else {\n+\t\tgit_config_push_split_parameter(text, NULL);\n+\t}\n+}\n+\n void git_config_push_env(const char *spec)\n {\n-\tstruct strbuf buf = STRBUF_INIT;\n+\tchar *key;\n \tconst char *env_name;\n \tconst char *env_value;\n \n \tenv_name = strrchr(spec, '=');\n \tif (!env_name)\n \t\tdie(_(\"invalid config format: %s\"), spec);\n+\tkey = xmemdupz(spec, env_name - spec);\n \tenv_name++;\n \tif (!*env_name)\n \t\tdie(_(\"missing environment variable name for configuration '%.*s'\"),\n@@ -364,10 +404,8 @@ void git_config_push_env(const char *spec)\n \t\tdie(_(\"missing environment variable '%s' for configuration '%.*s'\"),\n \t\t    env_name, (int)(env_name - spec - 1), spec);\n \n-\tstrbuf_add(&buf, spec, env_name - spec);\n-\tstrbuf_addstr(&buf, env_value);\n-\tgit_config_push_parameter(buf.buf);\n-\tstrbuf_release(&buf);\n+\tgit_config_push_split_parameter(key, env_value);\n+\tfree(key);\n }\n \n static inline int iskeychar(int c)\ndiff --git a/t/t1300-config.sh b/t/t1300-config.sh\nindex 853f2509c5..25437324c1 100755\n--- a/t/t1300-config.sh\n+++ b/t/t1300-config.sh\n@@ -1364,6 +1364,14 @@ test_expect_success 'git -c and --config-env override each other' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--config-env handles keys with equals' '\n+\techo value=with=equals >expect &&\n+\tENVVAR=value=with=equals git \\\n+\t\t--config-env=section.subsection=with=equals.key=ENVVAR \\\n+\t\tconfig section.subsection=with=equals.key >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git config --edit works' '\n \tgit config -f tmp test.value no &&\n \techo test.value=yes >expect &&\n-- \n2.30.0\n\n"},{"id":"414146","messageId":"28cc229adeb4eaf8994821f0312ba7b84a6d618e.1610453228.git.ps@pks.im","threadId":"54704","inReplyTo":"cover.1610453228.git.ps@pks.im","subject":"[PATCH v8 7/8] environment: make `getenv_safe()` a public function","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-12T12:27:10Z","receivedAt":"2021-01-12T12:29:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `getenv_safe()` helper function helps to safely retrieve multiple\nenvironment values without the need to depend on platform-specific\nbehaviour for the return value's lifetime. We'll make use of this\nfunction in a following patch, so let's make it available by making it\nnon-static and adding a declaration.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n environment.c |  7 ++-----\n environment.h | 12 ++++++++++++\n 2 files changed, 14 insertions(+), 5 deletions(-)\n create mode 100644 environment.h\n\ndiff --git a/environment.c b/environment.c\nindex bb518c61cd..2234af462c 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -9,6 +9,7 @@\n  */\n #include \"cache.h\"\n #include \"branch.h\"\n+#include \"environment.h\"\n #include \"repository.h\"\n #include \"config.h\"\n #include \"refs.h\"\n@@ -152,11 +153,7 @@ static char *expand_namespace(const char *raw_namespace)\n \treturn strbuf_detach(&buf, NULL);\n }\n \n-/*\n- * Wrapper of getenv() that returns a strdup value. This value is kept\n- * in argv to be freed later.\n- */\n-static const char *getenv_safe(struct strvec *argv, const char *name)\n+const char *getenv_safe(struct strvec *argv, const char *name)\n {\n \tconst char *value = getenv(name);\n \ndiff --git a/environment.h b/environment.h\nnew file mode 100644\nindex 0000000000..d438b5c8f3\n--- /dev/null\n+++ b/environment.h\n@@ -0,0 +1,12 @@\n+#ifndef ENVIRONMENT_H\n+#define ENVIRONMENT_H\n+\n+#include \"strvec.h\"\n+\n+/*\n+ * Wrapper of getenv() that returns a strdup value. This value is kept\n+ * in argv to be freed later.\n+ */\n+const char *getenv_safe(struct strvec *argv, const char *name);\n+\n+#endif\n-- \n2.30.0\n\n"},{"id":"414462","messageId":"YAHqHmGOUl53mfPa@coredump.intra.peff.net","threadId":"54704","inReplyTo":"36c2a51b13e463a4aa8e5316447336927153d99d.1610453228.git.ps@pks.im","subject":"Re: [PATCH v8 5/8] config: store \"git -c\" variables using more robust format","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-01-15T19:16:46Z","receivedAt":"2021-01-15T19:17:44Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jan 12, 2021 at 01:27:01PM +0100, Patrick Steinhardt wrote:\n\n> The previous commit added a new format for $GIT_CONFIG_PARAMETERS which\n> is able to robustly handle subsections with \"=\" in them. Let's start\n\nIt looks like this commit and 6 got flipped from the original ordering\n(it's the \"previous commit\" talked about here). And indeed, running the\ntests on the individual commits in this series shows that we fail at\nthis step (because we are writing the new format, but the reader is too\nstrict to accept it).\n\nThat doesn't matter to the end result, of course, but it hurts later\nbisecting. Just flipping patches 5 and 6 makes it all work.\n\n-Peff\n"},{"id":"414775","messageId":"YAfNrX1KNhHRbHmM@ncase","threadId":"54704","inReplyTo":"YAHqHmGOUl53mfPa@coredump.intra.peff.net","subject":"Re: [PATCH v8 5/8] config: store \"git -c\" variables using more robust format","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-20T06:29:01Z","receivedAt":"2021-01-20T06:43:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Jan 15, 2021 at 02:16:46PM -0500, Jeff King wrote:\n> On Tue, Jan 12, 2021 at 01:27:01PM +0100, Patrick Steinhardt wrote:\n> \n> > The previous commit added a new format for $GIT_CONFIG_PARAMETERS which\n> > is able to robustly handle subsections with \"=\" in them. Let's start\n> \n> It looks like this commit and 6 got flipped from the original ordering\n> (it's the \"previous commit\" talked about here). And indeed, running the\n> tests on the individual commits in this series shows that we fail at\n> this step (because we are writing the new format, but the reader is too\n> strict to accept it).\n> \n> That doesn't matter to the end result, of course, but it hurts later\n> bisecting. Just flipping patches 5 and 6 makes it all work.\n> \n> -Peff\n\nOops, yes. That always happens to me when I start using git-am(1). I see\nthat the patch series has been applied to \"next\" already, so does it\nmake any sense to resend with patches 5 and 6 flipped?\n\nPatrick\n"},{"id":"414777","messageId":"xmqqk0s8gkqz.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"YAfNrX1KNhHRbHmM@ncase","subject":"Re: [PATCH v8 5/8] config: store \"git -c\" variables using more robust format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-20T06:55:48Z","receivedAt":"2021-01-20T06:56:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Fri, Jan 15, 2021 at 02:16:46PM -0500, Jeff King wrote:\n>> On Tue, Jan 12, 2021 at 01:27:01PM +0100, Patrick Steinhardt wrote:\n>> \n>> > The previous commit added a new format for $GIT_CONFIG_PARAMETERS which\n>> > is able to robustly handle subsections with \"=\" in them. Let's start\n>> \n>> It looks like this commit and 6 got flipped from the original ordering\n>> (it's the \"previous commit\" talked about here). And indeed, running the\n>> tests on the individual commits in this series shows that we fail at\n>> this step (because we are writing the new format, but the reader is too\n>> strict to accept it).\n>> \n>> That doesn't matter to the end result, of course, but it hurts later\n>> bisecting. Just flipping patches 5 and 6 makes it all work.\n>> \n>> -Peff\n>\n> Oops, yes. That always happens to me when I start using git-am(1). I see\n> that the patch series has been applied to \"next\" already, so does it\n> make any sense to resend with patches 5 and 6 flipped?\n\nI recall saying that I'd \"rebase -i\" before merging it to \"next\".\nDid I forget to do so?\n\nDisecting 4ed03412 (Merge branch 'ps/config-env-pairs' into next,\n2021-01-15), we see:\n\n$ git log --oneline --reverse master..4ed03412^2 | cat -n\n     1\tb0812b6ac0 git: add `--super-prefix` to usage string\n     2\tce81b1da23 config: add new way to pass config via `--config-env`\n     3\t13c44953fb quote: make sq_dequote_step() a public function\n     4\tb342ae61b3 config: extract function to parse config pairs\n     5\tf9dbb64fad config: parse more robust format in GIT_CONFIG_PARAMETERS\n     6\t1ff21c05ba config: store \"git -c\" variables using more robust format\n     7\tb9d147fb15 environment: make `getenv_safe()` a public function\n     8\td8d77153ea config: allow specifying config entries via envvar pairs\n\nThe 5/8 that needs to come after 6/8 has title \"store ... using more\nrebust format\" and that is the 6th patch in the series merged to\n'next'.  The 6/8 that needs to come before that one was called\n\"parse more robust format\" and it now appears as the 5th patch.\n\nSo it seems all is well?\n"},{"id":"414780","messageId":"YAfe1laqK+bfHKVm@ncase","threadId":"54704","inReplyTo":"xmqqk0s8gkqz.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v8 5/8] config: store \"git -c\" variables using more robust format","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-01-20T07:42:14Z","receivedAt":"2021-01-20T07:43:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Jan 19, 2021 at 10:55:48PM -0800, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > On Fri, Jan 15, 2021 at 02:16:46PM -0500, Jeff King wrote:\n> >> On Tue, Jan 12, 2021 at 01:27:01PM +0100, Patrick Steinhardt wrote:\n> >> \n> >> > The previous commit added a new format for $GIT_CONFIG_PARAMETERS which\n> >> > is able to robustly handle subsections with \"=\" in them. Let's start\n> >> \n> >> It looks like this commit and 6 got flipped from the original ordering\n> >> (it's the \"previous commit\" talked about here). And indeed, running the\n> >> tests on the individual commits in this series shows that we fail at\n> >> this step (because we are writing the new format, but the reader is too\n> >> strict to accept it).\n> >> \n> >> That doesn't matter to the end result, of course, but it hurts later\n> >> bisecting. Just flipping patches 5 and 6 makes it all work.\n> >> \n> >> -Peff\n> >\n> > Oops, yes. That always happens to me when I start using git-am(1). I see\n> > that the patch series has been applied to \"next\" already, so does it\n> > make any sense to resend with patches 5 and 6 flipped?\n> \n> I recall saying that I'd \"rebase -i\" before merging it to \"next\".\n> Did I forget to do so?\n> \n> Disecting 4ed03412 (Merge branch 'ps/config-env-pairs' into next,\n> 2021-01-15), we see:\n> \n> $ git log --oneline --reverse master..4ed03412^2 | cat -n\n>      1\tb0812b6ac0 git: add `--super-prefix` to usage string\n>      2\tce81b1da23 config: add new way to pass config via `--config-env`\n>      3\t13c44953fb quote: make sq_dequote_step() a public function\n>      4\tb342ae61b3 config: extract function to parse config pairs\n>      5\tf9dbb64fad config: parse more robust format in GIT_CONFIG_PARAMETERS\n>      6\t1ff21c05ba config: store \"git -c\" variables using more robust format\n>      7\tb9d147fb15 environment: make `getenv_safe()` a public function\n>      8\td8d77153ea config: allow specifying config entries via envvar pairs\n> \n> The 5/8 that needs to come after 6/8 has title \"store ... using more\n> rebust format\" and that is the 6th patch in the series merged to\n> 'next'.  The 6/8 that needs to come before that one was called\n> \"parse more robust format\" and it now appears as the 5th patch.\n> \n> So it seems all is well?\n\nIndeed, I missed your message about the interactive rebase. Thanks!\n\nPatrick\n"},{"id":"414865","messageId":"xmqqy2gndz0d.fsf@gitster.c.googlers.com","threadId":"54704","inReplyTo":"YAfe1laqK+bfHKVm@ncase","subject":"Re: [PATCH v8 5/8] config: store \"git -c\" variables using more robust format","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-20T22:28:18Z","receivedAt":"2021-01-20T23:42:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> The 5/8 that needs to come after 6/8 has title \"store ... using more\n>> rebust format\" and that is the 6th patch in the series merged to\n>> 'next'.  The 6/8 that needs to come before that one was called\n>> \"parse more robust format\" and it now appears as the 5th patch.\n>> \n>> So it seems all is well?\n>\n> Indeed, I missed your message about the interactive rebase. Thanks!\n\nThanks for contributing in the first place, and thanks for double\nchecking.  Very much appreciated.\n\n"},{"id":"422102","messageId":"87o8eeteyz.fsf@evledraar.gmail.com","threadId":"54704","inReplyTo":"470396d36f938f0070b8c849a85b1a30949056e3.1610453228.git.ps@pks.im","subject":"Re: [PATCH v8 2/8] config: add new way to pass config via `--config-env`","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-04-16T15:40:36Z","receivedAt":"2021-04-16T15:40:42Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Jan 12 2021, Patrick Steinhardt wrote:\n\nA minor doc bug that wasn't spotted before landing. Here we say\n\"--config-env foo=bar\" will work:\n\n> diff --git a/Documentation/git.txt b/Documentation/git.txt\n> index a6d4ad0818..d36e6fd482 100644\n> --- a/Documentation/git.txt\n> +++ b/Documentation/git.txt\n> @@ -13,7 +13,7 @@ SYNOPSIS\n>      [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]\n>      [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]\n>      [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]\n> -    [--super-prefix=<path>]\n> +    [--super-prefix=<path>] [--config-env <name>=<envvar>]\n>      <command> [<args>]\n>  \n>  DESCRIPTION\n> @@ -80,6 +80,28 @@ config file). Including the equals but with an empty value (like `git -c\n>  foo.bar= ...`) sets `foo.bar` to the empty string which `git config\n>  --type=bool` will convert to `false`.\n\nBut not here, we ask for \"--config-env=\" (note the \"=\"):\n\n> +--config-env=<name>=<envvar>::\n> +\tLike `-c <name>=<value>`, give configuration variable\n> +\t'<name>' a value, where <envvar> is the name of an\n> +\tenvironment variable from which to retrieve the value. Unlike\n> +\t`-c` there is no shortcut for directly setting the value to an\n> +\tempty string, instead the environment variable itself must be\n> +\tset to the empty string.  It is an error if the `<envvar>` does not exist\n> +\tin the environment. `<envvar>` may not contain an equals sign\n> +\tto avoid ambiguity with `<name>`s which contain one.\n> ++\n> [...]\n> +\t\t} else if (skip_prefix(cmd, \"--config-env=\", &cmd)) {\n> +\t\t\tgit_config_push_env(cmd);\n\nBut as this...\n\n> +test_expect_success 'git --config-env=key=envvar support' '\n> +\tcat >expect <<-\\EOF &&\n> +\tvalue\n> +\tvalue\n> +\tfalse\n> +\tEOF\n> +\t{\n> +\t\tENVVAR=value git --config-env=core.name=ENVVAR config core.name &&\n> +\t\tENVVAR=value git --config-env=foo.CamelCase=ENVVAR config foo.camelcase &&\n> +\t\tENVVAR= git --config-env=foo.flag=ENVVAR config --bool foo.flag\n> +\t} >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git --config-env fails with invalid parameters' '\n> +\ttest_must_fail git --config-env=foo.flag config --bool foo.flag 2>error &&\n> +\ttest_i18ngrep \"invalid config format: foo.flag\" error &&\n> +\ttest_must_fail git --config-env=foo.flag= config --bool foo.flag 2>error &&\n> +\ttest_i18ngrep \"missing environment variable name for configuration ${SQ}foo.flag${SQ}\" error &&\n> +\tsane_unset NONEXISTENT &&\n> +\ttest_must_fail git --config-env=foo.flag=NONEXISTENT config --bool foo.flag 2>error &&\n> +\ttest_i18ngrep \"missing environment variable ${SQ}NONEXISTENT${SQ} for configuration ${SQ}foo.flag${SQ}\" error\n> +'\n> +\n> +test_expect_success 'git -c and --config-env work together' '\n> +\tcat >expect <<-\\EOF &&\n> +\tbar.cmd cmd-value\n> +\tbar.env env-value\n> +\tEOF\n> +\tENVVAR=env-value git \\\n> +\t\t-c bar.cmd=cmd-value \\\n> +\t\t--config-env=bar.env=ENVVAR \\\n> +\t\tconfig --get-regexp \"^bar.*\" >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'git -c and --config-env override each other' '\n> +\tcat >expect <<-\\EOF &&\n> +\tenv\n> +\tcmd\n> +\tEOF\n> +\t{\n> +\t\tENVVAR=env git -c bar.bar=cmd --config-env=bar.bar=ENVVAR config bar.bar &&\n> +\t\tENVVAR=env git --config-env=bar.bar=ENVVAR -c bar.bar=cmd config bar.bar\n> +\t} >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_expect_success 'git config --edit works' '\n>  \tgit config -f tmp test.value no &&\n>  \techo test.value=yes >expect &&\n\n...and the tests show we just support the --opt=foo=bar form, not --opt\nfoo=bar.\n\nBonus points to anyone sorting out some of the existing inconsistencies\nwhen fixing this, i.e. --exec-path supports either the \"=\" form, or not,\nbut various other skip_prefix() in the same function don't, seemingly\n(but I have not tested) for no good reason.\n\nIt seems to me that having a skip_prefix_opt() or something would be a\ngood fix for this, i.e. a \"maybe trim the last '='\" version of\nskip_prefix. Then we could just consistently use that.\n\nOr maybe there's some reason we don't want to be as lax as --exec-path\nwith any other option...\n\n"},{"id":"422153","messageId":"YHqeh9MeRDADviU0@coredump.intra.peff.net","threadId":"54704","inReplyTo":"87o8eeteyz.fsf@evledraar.gmail.com","subject":"Re: [PATCH v8 2/8] config: add new way to pass config via `--config-env`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-04-17T08:38:31Z","receivedAt":"2021-04-17T08:38:35Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Apr 16, 2021 at 05:40:36PM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> Bonus points to anyone sorting out some of the existing inconsistencies\n> when fixing this, i.e. --exec-path supports either the \"=\" form, or not,\n> but various other skip_prefix() in the same function don't, seemingly\n> (but I have not tested) for no good reason.\n\nI suspect just because it's more (per-option) work to support both\ntypes, and nobody really cared enough to do so.\n\n> It seems to me that having a skip_prefix_opt() or something would be a\n> good fix for this, i.e. a \"maybe trim the last '='\" version of\n> skip_prefix. Then we could just consistently use that.\n\nThere's a similar situation in the revision parser (which does not use\nour regular parse-options). There we have a parse_long_opt() helper\nwhich does the right thing. We could use that more widely.\n\nI also wouldn't be surprised if we could leverage one of the\nsub-functions of parse-options, but it might turn into a rabbit hole.\nConverting the whole thing to the usual parse_options() might get\nawkward, since many of the options operate at time-of-parse, not after\nwe've seen everything (I suspect many of them don't care either way, but\nyou're always risking subtle regressions there).\n\n> Or maybe there's some reason we don't want to be as lax as --exec-path\n> with any other option...\n\nI can't think of one.\n\n-Peff\n"},{"id":"422315","messageId":"YH2hqhHh1eh+U+6h@tanuki","threadId":"54704","inReplyTo":"YHqeh9MeRDADviU0@coredump.intra.peff.net","subject":"Re: [PATCH v8 2/8] config: add new way to pass config via `--config-env`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2021-04-19T15:28:42Z","receivedAt":"2021-04-19T15:27:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sat, Apr 17, 2021 at 04:38:31AM -0400, Jeff King wrote:\n> On Fri, Apr 16, 2021 at 05:40:36PM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> > Bonus points to anyone sorting out some of the existing inconsistencies\n> > when fixing this, i.e. --exec-path supports either the \"=\" form, or not,\n> > but various other skip_prefix() in the same function don't, seemingly\n> > (but I have not tested) for no good reason.\n> \n> I suspect just because it's more (per-option) work to support both\n> types, and nobody really cared enough to do so.\n> \n> > It seems to me that having a skip_prefix_opt() or something would be a\n> > good fix for this, i.e. a \"maybe trim the last '='\" version of\n> > skip_prefix. Then we could just consistently use that.\n> \n> There's a similar situation in the revision parser (which does not use\n> our regular parse-options). There we have a parse_long_opt() helper\n> which does the right thing. We could use that more widely.\n> \n> I also wouldn't be surprised if we could leverage one of the\n> sub-functions of parse-options, but it might turn into a rabbit hole.\n> Converting the whole thing to the usual parse_options() might get\n> awkward, since many of the options operate at time-of-parse, not after\n> we've seen everything (I suspect many of them don't care either way, but\n> you're always risking subtle regressions there).\n> \n> > Or maybe there's some reason we don't want to be as lax as --exec-path\n> > with any other option...\n> \n> I can't think of one.\n> \n> -Peff\n\n`--exec-path` does two different things based on whether you pass a \"=\"\nor not: either you tell git where its binaries are, or you ask it where\nit thinks they're. It's still true for some (most?) of the other options\nthough.\n\nPatrick\n"},{"id":"422382","messageId":"87y2dd2pdn.fsf@evledraar.gmail.com","threadId":"54704","inReplyTo":"YHqeh9MeRDADviU0@coredump.intra.peff.net","subject":"Re: [PATCH v8 2/8] config: add new way to pass config via `--config-env`","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-04-20T10:59:16Z","receivedAt":"2021-04-20T10:59:22Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, Apr 17 2021, Jeff King wrote:\n\n> On Fri, Apr 16, 2021 at 05:40:36PM +0200, Ævar Arnfjörð Bjarmason wrote:\n>\n>> Bonus points to anyone sorting out some of the existing inconsistencies\n>> when fixing this, i.e. --exec-path supports either the \"=\" form, or not,\n>> but various other skip_prefix() in the same function don't, seemingly\n>> (but I have not tested) for no good reason.\n>\n> I suspect just because it's more (per-option) work to support both\n> types, and nobody really cared enough to do so.\n>\n>> It seems to me that having a skip_prefix_opt() or something would be a\n>> good fix for this, i.e. a \"maybe trim the last '='\" version of\n>> skip_prefix. Then we could just consistently use that.\n>\n> There's a similar situation in the revision parser (which does not use\n> our regular parse-options). There we have a parse_long_opt() helper\n> which does the right thing. We could use that more widely.\n>\n> I also wouldn't be surprised if we could leverage one of the\n> sub-functions of parse-options, but it might turn into a rabbit hole.\n> Converting the whole thing to the usual parse_options() might get\n> awkward, since many of the options operate at time-of-parse, not after\n> we've seen everything (I suspect many of them don't care either way, but\n> you're always risking subtle regressions there).\n\nSo we could use parse_options() and guarantee the existing behavior if\nthey were all OPT_CALLBACK?\n"},{"id":"422383","messageId":"87v98h2p9a.fsf@evledraar.gmail.com","threadId":"54704","inReplyTo":"YH2hqhHh1eh+U+6h@tanuki","subject":"Re: [PATCH v8 2/8] config: add new way to pass config via `--config-env`","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-04-20T11:01:53Z","receivedAt":"2021-04-20T11:01:57Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Apr 19 2021, Patrick Steinhardt wrote:\n\n> On Sat, Apr 17, 2021 at 04:38:31AM -0400, Jeff King wrote:\n>> On Fri, Apr 16, 2021 at 05:40:36PM +0200, Ævar Arnfjörð Bjarmason wrote:\n>> \n>> > Bonus points to anyone sorting out some of the existing inconsistencies\n>> > when fixing this, i.e. --exec-path supports either the \"=\" form, or not,\n>> > but various other skip_prefix() in the same function don't, seemingly\n>> > (but I have not tested) for no good reason.\n>> \n>> I suspect just because it's more (per-option) work to support both\n>> types, and nobody really cared enough to do so.\n>> \n>> > It seems to me that having a skip_prefix_opt() or something would be a\n>> > good fix for this, i.e. a \"maybe trim the last '='\" version of\n>> > skip_prefix. Then we could just consistently use that.\n>> \n>> There's a similar situation in the revision parser (which does not use\n>> our regular parse-options). There we have a parse_long_opt() helper\n>> which does the right thing. We could use that more widely.\n>> \n>> I also wouldn't be surprised if we could leverage one of the\n>> sub-functions of parse-options, but it might turn into a rabbit hole.\n>> Converting the whole thing to the usual parse_options() might get\n>> awkward, since many of the options operate at time-of-parse, not after\n>> we've seen everything (I suspect many of them don't care either way, but\n>> you're always risking subtle regressions there).\n>> \n>> > Or maybe there's some reason we don't want to be as lax as --exec-path\n>> > with any other option...\n>> \n>> I can't think of one.\n>> \n>> -Peff\n>\n> `--exec-path` does two different things based on whether you pass a \"=\"\n> or not: either you tell git where its binaries are, or you ask it where\n> it thinks they're. It's still true for some (most?) of the other options\n> though.\n\nI don't know how I got those conflated, but FWIW I meant (or at least\nthink I did) the likes of --namespace[=], --git-dir[=] and\n--work-tree[=] where the \"=\" is really optional, but otherwise the\noption behaves the same.\n"},{"id":"422744","messageId":"YIKb7qoeLtGjgsHr@coredump.intra.peff.net","threadId":"54704","inReplyTo":"87y2dd2pdn.fsf@evledraar.gmail.com","subject":"Re: [PATCH v8 2/8] config: add new way to pass config via `--config-env`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-04-23T10:05:34Z","receivedAt":"2021-04-23T10:05:41Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Apr 20, 2021 at 12:59:16PM +0200, Ævar Arnfjörð Bjarmason wrote:\n\n> >> It seems to me that having a skip_prefix_opt() or something would be a\n> >> good fix for this, i.e. a \"maybe trim the last '='\" version of\n> >> skip_prefix. Then we could just consistently use that.\n> >\n> > There's a similar situation in the revision parser (which does not use\n> > our regular parse-options). There we have a parse_long_opt() helper\n> > which does the right thing. We could use that more widely.\n> >\n> > I also wouldn't be surprised if we could leverage one of the\n> > sub-functions of parse-options, but it might turn into a rabbit hole.\n> > Converting the whole thing to the usual parse_options() might get\n> > awkward, since many of the options operate at time-of-parse, not after\n> > we've seen everything (I suspect many of them don't care either way, but\n> > you're always risking subtle regressions there).\n> \n> So we could use parse_options() and guarantee the existing behavior if\n> they were all OPT_CALLBACK?\n\nI _think_ so, but the result might be quite hard to read (the logic\nwould be scattered all over a bunch of tiny callbacks). But it might not\nbe too bad. Especially if you figure out which ones actually need the\ntime-of-parse logic and use more vanilla OPT_* for the others (that's\nthe rabbit hole I alluded to).\n\nI think things like the \"--exec-path\" behavior that Patrick mentioned\nwould still work (I think it's just a stock OPTARG).\n\n-Peff\n"},{"id":"424941","messageId":"87fsyjq7f8.fsf@evledraar.gmail.com","threadId":"54704","inReplyTo":"YIKb7qoeLtGjgsHr@coredump.intra.peff.net","subject":"Re: [PATCH v8 2/8] config: add new way to pass config via `--config-env`","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-05-19T11:36:17Z","receivedAt":"2021-05-19T11:39:32Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Apr 23 2021, Jeff King wrote:\n\n> On Tue, Apr 20, 2021 at 12:59:16PM +0200, Ævar Arnfjörð Bjarmason wrote:\n>\n>> >> It seems to me that having a skip_prefix_opt() or something would be a\n>> >> good fix for this, i.e. a \"maybe trim the last '='\" version of\n>> >> skip_prefix. Then we could just consistently use that.\n>> >\n>> > There's a similar situation in the revision parser (which does not use\n>> > our regular parse-options). There we have a parse_long_opt() helper\n>> > which does the right thing. We could use that more widely.\n>> >\n>> > I also wouldn't be surprised if we could leverage one of the\n>> > sub-functions of parse-options, but it might turn into a rabbit hole.\n>> > Converting the whole thing to the usual parse_options() might get\n>> > awkward, since many of the options operate at time-of-parse, not after\n>> > we've seen everything (I suspect many of them don't care either way, but\n>> > you're always risking subtle regressions there).\n>> \n>> So we could use parse_options() and guarantee the existing behavior if\n>> they were all OPT_CALLBACK?\n>\n> I _think_ so, but the result might be quite hard to read (the logic\n> would be scattered all over a bunch of tiny callbacks). But it might not\n> be too bad. Especially if you figure out which ones actually need the\n> time-of-parse logic and use more vanilla OPT_* for the others (that's\n> the rabbit hole I alluded to).\n>\n> I think things like the \"--exec-path\" behavior that Patrick mentioned\n> would still work (I think it's just a stock OPTARG).\n\n[Mostly for my own future reference]: There's also the\nparse_options_step() API to process options one at a time, which AFAICT\ncould be used in this case.\n\nBut having glanced at it again (but not come up with a patch) I think it\ncould be handled with OPT_CALLBACK + not caring about the order,\nmostly. The \"envchanged\" could be passed as a custom flag probably...\n"}]}