diff mbox series

[v4,2/6] config: add new way to pass config via `--config-env`

Message ID 766ffe31a6f14c55d1b58a8f53edbb7f731b1b24.1607514692.git.ps@pks.im (mailing list archive)
State Superseded
Headers show
Series config: allow specifying config entries via env | expand

Commit Message

Patrick Steinhardt Dec. 9, 2020, 11:52 a.m. UTC
While it's already possible to pass runtime configuration via `git -c
<key>=<value>`, it may be undesirable to use when the value contains
sensitive information. E.g. if one wants to set `http.extraHeader` to
contain an authentication token, doing so via `-c` would trivially leak
those credentials via e.g. ps(1), which typically also shows command
arguments.

To enable this usecase without leaking credentials, this commit
introduces a new switch `--config-env=<key>=<envvar>`. Instead of
directly passing a value for the given key, it instead allows the user
to specify the name of an environment variable. The value of that
variable will then be used as value of the key.

Co-authored-by: Jeff King <peff@peff.net>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git.txt | 11 ++++++++++-
 config.c              | 21 ++++++++++++++++++++
 config.h              |  1 +
 git.c                 |  4 +++-
 t/t1300-config.sh     | 45 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 80 insertions(+), 2 deletions(-)

Comments

Ævar Arnfjörð Bjarmason Dec. 9, 2020, 2:40 p.m. UTC | #1
On Wed, Dec 09 2020, Patrick Steinhardt wrote:

> While it's already possible to pass runtime configuration via `git -c
> <key>=<value>`, it may be undesirable to use when the value contains
> sensitive information. E.g. if one wants to set `http.extraHeader` to
> contain an authentication token, doing so via `-c` would trivially leak
> those credentials via e.g. ps(1), which typically also shows command
> arguments.
>
> To enable this usecase without leaking credentials, this commit
> introduces a new switch `--config-env=<key>=<envvar>`. Instead of
> directly passing a value for the given key, it instead allows the user
> to specify the name of an environment variable. The value of that
> variable will then be used as value of the key.
> [...]
> +--config-env=<name>=<envvar>::
> +	Pass a configuration parameter to the command. The <envvar>
> +	given will be replaced with the contents of the environment
> +	variable of that name. In contrast to `-c`, an envvar must
> +	always be given and exist in the environment. Passing an
> +	environment variable with empty value will set <name> to the
> +	empty string which `git config --type=bool` will convert to
> +	`false`.

Okey, because "-c foo.bar" (true) "-c foo.bar=" is the empty string, but
that doesn't make sene with "--config-env". Also the whole part about
--type=bool is just confusing, because it's referring to `-c`'s magic
behavior when it comes to `bool` which we don't have here.

I think it's also worth describing what this is for & what the
limitations are. Maybe:

    --config-env=<name>=<envvar>

        Like `-c <name>=<var>` except the value is the name of an
        environment variable from which to retrieve the value. Unlike
        `-c` there is no shortcut for directly setting the value to an
        empty string, instead the environment variable itself must be
        set to the empty strin. Errors if the `<envvar>` does not exist
        in the environment.

        This is useful for cases where you want to pass transitory
        configuration options to git, but are doing so on OS's where
        other processes might be able to read your cmdline
        (e.g. `/proc/self/cmdline`), but not your environ
        (e.g. `/proc/self/environ`). That behavior is the default on
        Linux, but may not be on your system.

	Note that this might add security for variables such as
	`http.extraHeader` where the sensitive information is part of
	the value, but not e.g. `url.<base.insteadOf` where the
	sensitive information can be part of the key.

> +void git_config_push_env(const char *spec)
> +{
> +	struct strbuf buf = STRBUF_INIT;
> +	const char *env_name;
> +	const char *env_value;
> +
> +	env_name = strrchr(spec, '=');
> +	if (!env_name)
> +		die("invalid config format: %s", spec);
> +	env_name++;

Not something new, and maybe not something for this series, but I wish
-c and --config-env would document this limitation that we support "="
in keys in config, but not via those parameters.
Jeff King Dec. 9, 2020, 4:10 p.m. UTC | #2
On Wed, Dec 09, 2020 at 12:52:26PM +0100, Patrick Steinhardt wrote:

> Co-authored-by: Jeff King <peff@peff.net>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>

In case we want it, this is also:

  Signed-off-by: Jeff King <peff@peff.net>

> +--config-env=<name>=<envvar>::
> +	Pass a configuration parameter to the command. The <envvar>
> +	given will be replaced with the contents of the environment
> +	variable of that name. In contrast to `-c`, an envvar must
> +	always be given and exist in the environment. Passing an
> +	environment variable with empty value will set <name> to the
> +	empty string which `git config --type=bool` will convert to
> +	`false`.

I agree with Ævar that we probably should keep an empty variable as the
empty string. I think some options use an empty string to clear a list
(e.g., push.pushOption), and I'm not sure how they'd react to a bool
instead. It would be nice to also have a way to do the implicit-bool
thing, but I don't think it's strictly necessary (it's always correct to
put the string "true" into the variable instead).

I think we should also document that <envvar> can't contain an "=" sign.
Of course using strrchr() here doesn't help much with just this patch,
because we flatten the string before stuffing it into
$GIT_CONFIG_PARAMETERS, so the reading side would mis-parse it.

But here's a fix for that. I built it on top of your whole series, since
you touched some of the related functions, but it could easily be
rebased onto just this part.

  [1/3]: quote: make sq_dequote_step() a public function
  [2/3]: config: parse more robust format in GIT_CONFIG_PARAMETERS
  [3/3]: config: store "git -c" variables using more robust format

 config.c          | 118 +++++++++++++++++++++++++++++++++++++---------
 quote.c           |  15 ++++--
 quote.h           |  18 ++++++-
 t/t1300-config.sh |  60 +++++++++++++++++++++++
 4 files changed, 183 insertions(+), 28 deletions(-)

-Peff
Jeff King Dec. 9, 2020, 4:24 p.m. UTC | #3
On Wed, Dec 09, 2020 at 03:40:17PM +0100, Ævar Arnfjörð Bjarmason wrote:

> > +--config-env=<name>=<envvar>::
> > +	Pass a configuration parameter to the command. The <envvar>
> > +	given will be replaced with the contents of the environment
> > +	variable of that name. In contrast to `-c`, an envvar must
> > +	always be given and exist in the environment. Passing an
> > +	environment variable with empty value will set <name> to the
> > +	empty string which `git config --type=bool` will convert to
> > +	`false`.
> 
> Okey, because "-c foo.bar" (true) "-c foo.bar=" is the empty string, but
> that doesn't make sene with "--config-env". Also the whole part about
> --type=bool is just confusing, because it's referring to `-c`'s magic
> behavior when it comes to `bool` which we don't have here.

Yeah, I agree.

> I think it's also worth describing what this is for & what the
> limitations are. Maybe:

Agreed, and the text you gave looks reasonable. Another reason to use it
is that it will (if we add the patches I just sent on top) avoid the
key/value ambiguity with equals in the section name.

> Not something new, and maybe not something for this series, but I wish
> -c and --config-env would document this limitation that we support "="
> in keys in config, but not via those parameters.

Yeah. If we add in my patches, then the limitation is gone here (but we
should mention the limitation on the environment variable name).

I stopped short of adding a variant of "-c" that avoids the ambiguity.
I'm certainly not opposed to one if somebody wants to do it, but I think
documenting the current limitation makes sense in the meantime (and we
should do it in this series while we're thinking about it).

-Peff
Junio C Hamano Dec. 10, 2020, midnight UTC | #4
Jeff King <peff@peff.net> writes:

> On Wed, Dec 09, 2020 at 12:52:26PM +0100, Patrick Steinhardt wrote:
>
>> Co-authored-by: Jeff King <peff@peff.net>
>> Signed-off-by: Patrick Steinhardt <ps@pks.im>
>
> In case we want it, this is also:
>
>   Signed-off-by: Jeff King <peff@peff.net>
>
>> +--config-env=<name>=<envvar>::
>> +	Pass a configuration parameter to the command. The <envvar>
>> +	given will be replaced with the contents of the environment
>> +	variable of that name. In contrast to `-c`, an envvar must
>> +	always be given and exist in the environment. Passing an
>> +	environment variable with empty value will set <name> to the
>> +	empty string which `git config --type=bool` will convert to
>> +	`false`.
>
> I agree with Ævar that we probably should keep an empty variable as the
> empty string. I think some options use an empty string to clear a list
> (e.g., push.pushOption), and I'm not sure how they'd react to a bool
> instead. It would be nice to also have a way to do the implicit-bool
> thing, but I don't think it's strictly necessary (it's always correct to
> put the string "true" into the variable instead).
>
> I think we should also document that <envvar> can't contain an "=" sign.
> Of course using strrchr() here doesn't help much with just this patch,
> because we flatten the string before stuffing it into
> $GIT_CONFIG_PARAMETERS, so the reading side would mis-parse it.
>
> But here's a fix for that. I built it on top of your whole series, since
> you touched some of the related functions, but it could easily be
> rebased onto just this part.

Hmph, so 

 (1) Patrick's 1 & 2 are about adding --config-env,

 (2) These three patches can come on top to make it more robust to
     pass key=value with GIT_CONFIG_PARAMETERS (including what is
     added via the --config-env=name=envvar), and

 (3) The remainder of Patrick's 6-patch series is to further add the
     pairs of environment variables to pass keys and values?

I am still not sure if we want the last part, but whether we take
(1) or (3) or neither or both, (2) sounds like a good thing to do.
And (2) would not shine without (1).  In the traditional use of -c,
we do not know which = from the end user separates key and value,
but when (1) places a = to separate the <name> and the value in the
environment variable, we know where that = is and can quote
accordingly.

But these three patches are done on top of (1) and (3), at least for
now.

The above is my understanding of the state of these patches.  Am I
getting it right?

Thanks.

>   [1/3]: quote: make sq_dequote_step() a public function
>   [2/3]: config: parse more robust format in GIT_CONFIG_PARAMETERS
>   [3/3]: config: store "git -c" variables using more robust format
>
>  config.c          | 118 +++++++++++++++++++++++++++++++++++++---------
>  quote.c           |  15 ++++--
>  quote.h           |  18 ++++++-
>  t/t1300-config.sh |  60 +++++++++++++++++++++++
>  4 files changed, 183 insertions(+), 28 deletions(-)
>
> -Peff
Jeff King Dec. 10, 2020, 12:09 a.m. UTC | #5
On Wed, Dec 09, 2020 at 04:00:08PM -0800, Junio C Hamano wrote:

> > I think we should also document that <envvar> can't contain an "=" sign.
> > Of course using strrchr() here doesn't help much with just this patch,
> > because we flatten the string before stuffing it into
> > $GIT_CONFIG_PARAMETERS, so the reading side would mis-parse it.
> >
> > But here's a fix for that. I built it on top of your whole series, since
> > you touched some of the related functions, but it could easily be
> > rebased onto just this part.
> 
> Hmph, so 
> 
>  (1) Patrick's 1 & 2 are about adding --config-env,
> 
>  (2) These three patches can come on top to make it more robust to
>      pass key=value with GIT_CONFIG_PARAMETERS (including what is
>      added via the --config-env=name=envvar), and

Yep, exactly.

>  (3) The remainder of Patrick's 6-patch series is to further add the
>      pairs of environment variables to pass keys and values?

More or less. I did use the config_parse_pair() helper from patch 4, and
there's some textual dependency on his patch 5 (but it would be easy to
rebase).

> I am still not sure if we want the last part, but whether we take
> (1) or (3) or neither or both, (2) sounds like a good thing to do.
> And (2) would not shine without (1).  In the traditional use of -c,
> we do not know which = from the end user separates key and value,
> but when (1) places a = to separate the <name> and the value in the
> environment variable, we know where that = is and can quote
> accordingly.

Exactly. Without (1), then (2) is not nearly as exciting. We could also
implement a non-ambiguous version of "-c", like:

  git --config key.with=equals.foo some-value ...

which would also benefit from (2). But I think Patrick's main goal was
to get secret values off the command-line, so he'd want either (1) or
(3) for that.

> But these three patches are done on top of (1) and (3), at least for
> now.

Yes. I'd be happy to rebase them if we're not going to do the
GIT_CONFIG_{KEY,VALUE}_<n> parts.

> The above is my understanding of the state of these patches.  Am I
> getting it right?

Yep.

-Peff
Junio C Hamano Dec. 10, 2020, 12:57 a.m. UTC | #6
Jeff King <peff@peff.net> writes:

> Yes. I'd be happy to rebase them if we're not going to do the
> GIT_CONFIG_{KEY,VALUE}_<n> parts.
>
>> The above is my understanding of the state of these patches.  Am I
>> getting it right?
>
> Yep.

Thanks.  

I have no strong feeling for or against the env-pair feature, so the
way these three parts are structured (i.e. more robust parsing of
'=' comes at the end) is fine by me.
Patrick Steinhardt Dec. 11, 2020, 1:24 p.m. UTC | #7
On Wed, Dec 09, 2020 at 11:24:03AM -0500, Jeff King wrote:
> On Wed, Dec 09, 2020 at 03:40:17PM +0100, Ævar Arnfjörð Bjarmason wrote:
> 
> > > +--config-env=<name>=<envvar>::
> > > +	Pass a configuration parameter to the command. The <envvar>
> > > +	given will be replaced with the contents of the environment
> > > +	variable of that name. In contrast to `-c`, an envvar must
> > > +	always be given and exist in the environment. Passing an
> > > +	environment variable with empty value will set <name> to the
> > > +	empty string which `git config --type=bool` will convert to
> > > +	`false`.
> > 
> > Okey, because "-c foo.bar" (true) "-c foo.bar=" is the empty string, but
> > that doesn't make sene with "--config-env". Also the whole part about
> > --type=bool is just confusing, because it's referring to `-c`'s magic
> > behavior when it comes to `bool` which we don't have here.
> 
> Yeah, I agree.
> 
> > I think it's also worth describing what this is for & what the
> > limitations are. Maybe:
> 
> Agreed, and the text you gave looks reasonable. Another reason to use it
> is that it will (if we add the patches I just sent on top) avoid the
> key/value ambiguity with equals in the section name.

Yeah, I'll pick up the explanation by Ævar, it's a lot better compared
to what I had. Thanks!

> > Not something new, and maybe not something for this series, but I wish
> > -c and --config-env would document this limitation that we support "="
> > in keys in config, but not via those parameters.
> 
> Yeah. If we add in my patches, then the limitation is gone here (but we
> should mention the limitation on the environment variable name).
> 
> I stopped short of adding a variant of "-c" that avoids the ambiguity.
> I'm certainly not opposed to one if somebody wants to do it, but I think
> documenting the current limitation makes sense in the meantime (and we
> should do it in this series while we're thinking about it).
> 
> -Peff

Do you want me to adopt your patches as part of this series?

Patrick
Patrick Steinhardt Dec. 11, 2020, 1:24 p.m. UTC | #8
On Wed, Dec 09, 2020 at 11:10:04AM -0500, Jeff King wrote:
> On Wed, Dec 09, 2020 at 12:52:26PM +0100, Patrick Steinhardt wrote:
> 
> > Co-authored-by: Jeff King <peff@peff.net>
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> 
> In case we want it, this is also:
> 
>   Signed-off-by: Jeff King <peff@peff.net>
> 
> > +--config-env=<name>=<envvar>::
> > +	Pass a configuration parameter to the command. The <envvar>
> > +	given will be replaced with the contents of the environment
> > +	variable of that name. In contrast to `-c`, an envvar must
> > +	always be given and exist in the environment. Passing an
> > +	environment variable with empty value will set <name> to the
> > +	empty string which `git config --type=bool` will convert to
> > +	`false`.
> 
> I agree with Ævar that we probably should keep an empty variable as the
> empty string. I think some options use an empty string to clear a list
> (e.g., push.pushOption), and I'm not sure how they'd react to a bool
> instead. It would be nice to also have a way to do the implicit-bool
> thing, but I don't think it's strictly necessary (it's always correct to
> put the string "true" into the variable instead).

I think this is just weirdly worded in the `-c` case, which I mostly
copied. We _do_ keep the empty string, which effectively means that `git
config --type=bool` will return `false`.

Or do you mean that we should allow `--config-env=foo.bar=`?

> I think we should also document that <envvar> can't contain an "=" sign.
> Of course using strrchr() here doesn't help much with just this patch,
> because we flatten the string before stuffing it into
> $GIT_CONFIG_PARAMETERS, so the reading side would mis-parse it.

Makes sense.

Patrick

> But here's a fix for that. I built it on top of your whole series, since
> you touched some of the related functions, but it could easily be
> rebased onto just this part.
> 
>   [1/3]: quote: make sq_dequote_step() a public function
>   [2/3]: config: parse more robust format in GIT_CONFIG_PARAMETERS
>   [3/3]: config: store "git -c" variables using more robust format
> 
>  config.c          | 118 +++++++++++++++++++++++++++++++++++++---------
>  quote.c           |  15 ++++--
>  quote.h           |  18 ++++++-
>  t/t1300-config.sh |  60 +++++++++++++++++++++++
>  4 files changed, 183 insertions(+), 28 deletions(-)
> 
> -Peff
Jeff King Dec. 11, 2020, 2:20 p.m. UTC | #9
On Fri, Dec 11, 2020 at 02:24:35PM +0100, Patrick Steinhardt wrote:

> > > +--config-env=<name>=<envvar>::
> > > +	Pass a configuration parameter to the command. The <envvar>
> > > +	given will be replaced with the contents of the environment
> > > +	variable of that name. In contrast to `-c`, an envvar must
> > > +	always be given and exist in the environment. Passing an
> > > +	environment variable with empty value will set <name> to the
> > > +	empty string which `git config --type=bool` will convert to
> > > +	`false`.
> > 
> > I agree with Ævar that we probably should keep an empty variable as the
> > empty string. I think some options use an empty string to clear a list
> > (e.g., push.pushOption), and I'm not sure how they'd react to a bool
> > instead. It would be nice to also have a way to do the implicit-bool
> > thing, but I don't think it's strictly necessary (it's always correct to
> > put the string "true" into the variable instead).
> 
> I think this is just weirdly worded in the `-c` case, which I mostly
> copied. We _do_ keep the empty string, which effectively means that `git
> config --type=bool` will return `false`.

Oh indeed, I misread what you wrote in the documentation. I think it is
doing the right thing, then. IMHO it is not worth even calling out
specially, since there is no matching implicit-bool form. I.e., I'd
probably just cut the final sentence.

> Or do you mean that we should allow `--config-env=foo.bar=`?

Hmm, yeah, that would work as an "implicit bool". But it's sufficiently
ugly and non-intuitive (and weirdly overlapping with "-c") that I'm not
sure it is worth supporting.

-Peff
Jeff King Dec. 11, 2020, 2:21 p.m. UTC | #10
On Fri, Dec 11, 2020 at 02:24:33PM +0100, Patrick Steinhardt wrote:

> Do you want me to adopt your patches as part of this series?

Yeah, if you're willing to. I don't mind spinning it off into its own
series if you don't want to (the tricky part is that we're touching a
couple of the same spots, though, so if you're willing to pick them up,
I think that makes coordination easier).

-Peff
Patrick Steinhardt Dec. 11, 2020, 2:54 p.m. UTC | #11
On Fri, Dec 11, 2020 at 09:21:38AM -0500, Jeff King wrote:
> On Fri, Dec 11, 2020 at 02:24:33PM +0100, Patrick Steinhardt wrote:
> 
> > Do you want me to adopt your patches as part of this series?
> 
> Yeah, if you're willing to. I don't mind spinning it off into its own
> series if you don't want to (the tricky part is that we're touching a
> couple of the same spots, though, so if you're willing to pick them up,
> I think that makes coordination easier).
> 
> -Peff

I can do so. The only question that I have is whether I should rebase it
on top of 6/6 or on top of 2/6. It's hard for me to gauge whether 6/6 is
going to make it in or not due to the conflicting opinions on it. It
currently seems to me like we tend towards a "no", which is also what
the "What's cooking" report said. But there were also some opinions in
favor of it, which made me wonder. If this is a definitive "no", then
I'm happy to stop bothering with them to make the patch series easier to
manage.

Patrick
Jeff King Dec. 11, 2020, 4:10 p.m. UTC | #12
On Fri, Dec 11, 2020 at 03:54:03PM +0100, Patrick Steinhardt wrote:

> > Yeah, if you're willing to. I don't mind spinning it off into its own
> > series if you don't want to (the tricky part is that we're touching a
> > couple of the same spots, though, so if you're willing to pick them up,
> > I think that makes coordination easier).
> > 
> 
> I can do so. The only question that I have is whether I should rebase it
> on top of 6/6 or on top of 2/6. It's hard for me to gauge whether 6/6 is
> going to make it in or not due to the conflicting opinions on it. It
> currently seems to me like we tend towards a "no", which is also what
> the "What's cooking" report said. But there were also some opinions in
> favor of it, which made me wonder. If this is a definitive "no", then
> I'm happy to stop bothering with them to make the patch series easier to
> manage.

I'd probably do it on top of 2/6 (well, perhaps shuffling 4/6 forward is
needed, then, I think). And then that punts the decision.

As for the general idea of 6/6, I think I'm a soft "no" there. Normally
my opinion for things I wouldn't use myself is "hey, go to town, if
you're willing to write the patch and it won't hurt anybody else". My
only reservation is that it's a public-facing interface, so we'll have
to support it forever. And I don't love the interface.

That's not a reflection on how you did the series, btw. I think you've
done a very good job of trying to address everyone's concerns, and
balance them with having a way to get data through the environment that
doesn't require error-prone quoting and parsing. But at the end, I think
we are left with a fundamental tradeoff: an interface that is clunky
because of the counted variables, or one if that is clunky because of
the quoting.

(And by "soft no", I just mean that I wouldn't pursue it further in your
shoes, but I'm not going to strenuously object if you and others want to
go forward).

-Peff
diff mbox series

Patch

diff --git a/Documentation/git.txt b/Documentation/git.txt
index c463b937a8..9061c54792 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -13,7 +13,7 @@  SYNOPSIS
     [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
     [-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
     [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
-    [--super-prefix=<path>]
+    [--super-prefix=<path>] [--config-env <name>=<envvar>]
     <command> [<args>]
 
 DESCRIPTION
@@ -80,6 +80,15 @@  config file). Including the equals but with an empty value (like `git -c
 foo.bar= ...`) sets `foo.bar` to the empty string which `git config
 --type=bool` will convert to `false`.
 
+--config-env=<name>=<envvar>::
+	Pass a configuration parameter to the command. The <envvar>
+	given will be replaced with the contents of the environment
+	variable of that name. In contrast to `-c`, an envvar must
+	always be given and exist in the environment. Passing an
+	environment variable with empty value will set <name> to the
+	empty string which `git config --type=bool` will convert to
+	`false`.
+
 --exec-path[=<path>]::
 	Path to wherever your core Git programs are installed.
 	This can also be controlled by setting the GIT_EXEC_PATH
diff --git a/config.c b/config.c
index 1137bd73af..cde3511110 100644
--- a/config.c
+++ b/config.c
@@ -345,6 +345,27 @@  void git_config_push_parameter(const char *text)
 	strbuf_release(&env);
 }
 
+void git_config_push_env(const char *spec)
+{
+	struct strbuf buf = STRBUF_INIT;
+	const char *env_name;
+	const char *env_value;
+
+	env_name = strrchr(spec, '=');
+	if (!env_name)
+		die("invalid config format: %s", spec);
+	env_name++;
+
+	env_value = getenv(env_name);
+	if (!env_value)
+		die("config variable missing for '%s'", env_name);
+
+	strbuf_add(&buf, spec, env_name - spec);
+	strbuf_addstr(&buf, env_value);
+	git_config_push_parameter(buf.buf);
+	strbuf_release(&buf);
+}
+
 static inline int iskeychar(int c)
 {
 	return isalnum(c) || c == '-';
diff --git a/config.h b/config.h
index c1449bb790..19a9adbaa9 100644
--- a/config.h
+++ b/config.h
@@ -138,6 +138,7 @@  int git_config_from_mem(config_fn_t fn,
 int git_config_from_blob_oid(config_fn_t fn, const char *name,
 			     const struct object_id *oid, void *data);
 void git_config_push_parameter(const char *text);
+void git_config_push_env(const char *spec);
 int git_config_from_parameters(config_fn_t fn, void *data);
 void read_early_config(config_fn_t cb, void *data);
 void read_very_early_config(config_fn_t cb, void *data);
diff --git a/git.c b/git.c
index 5a8ff12f87..b5f63d346b 100644
--- a/git.c
+++ b/git.c
@@ -29,7 +29,7 @@  const char git_usage_string[] =
 	   "           [--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>]");
 
 const char git_more_info_string[] =
@@ -255,6 +255,8 @@  static int handle_options(const char ***argv, int *argc, int *envchanged)
 			git_config_push_parameter((*argv)[1]);
 			(*argv)++;
 			(*argc)--;
+		} else if (skip_prefix(cmd, "--config-env=", &cmd)) {
+			git_config_push_env(cmd);
 		} else if (!strcmp(cmd, "--literal-pathspecs")) {
 			setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "1", 1);
 			if (envchanged)
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 97a04c6cc2..46a94814d5 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1316,6 +1316,51 @@  test_expect_success 'detect bogus GIT_CONFIG_PARAMETERS' '
 		git config --get-regexp "env.*"
 '
 
+test_expect_success 'git --config-env=key=envvar support' '
+	cat >expect <<-\EOF &&
+	value
+	value
+	false
+	EOF
+	{
+		env ENVVAR=value git --config-env=core.name=ENVVAR config core.name &&
+		env ENVVAR=value git --config-env=foo.CamelCase=ENVVAR config foo.camelcase &&
+		env ENVVAR= git --config-env=foo.flag=ENVVAR config --bool foo.flag
+	} >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git --config-env fails with invalid parameters' '
+	test_must_fail git --config-env=foo.flag config --bool foo.flag 2>error &&
+	test_i18ngrep "invalid config format" error &&
+	test_must_fail git --config-env=foo.flag=NONEXISTENT config --bool foo.flag 2>error &&
+	test_i18ngrep "config variable missing" error
+'
+
+test_expect_success 'git -c and --config-env work together' '
+	cat >expect <<-\EOF &&
+	bar.cmd cmd-value
+	bar.env env-value
+	EOF
+	env ENVVAR=env-value git \
+		-c bar.cmd=cmd-value \
+		--config-env=bar.env=ENVVAR \
+		config --get-regexp "^bar.*" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'git -c and --config-env override each other' '
+	cat >expect <<-\EOF &&
+	env
+	cmd
+	EOF
+	{
+		env ENVVAR=env git -c bar.bar=cmd --config-env=bar.bar=ENVVAR config bar.bar &&
+		env ENVVAR=env git --config-env=bar.bar=ENVVAR -c bar.bar=cmd config bar.bar
+	} >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'git config --edit works' '
 	git config -f tmp test.value no &&
 	echo test.value=yes >expect &&