Skip to content

Commit 294e949

Browse files
committed
Merge branch 'ps/config-env-pairs'
Introduce two new ways to feed configuration variable-value pairs via environment variables, and tweak the way GIT_CONFIG_PARAMETERS encodes variable/value pairs to make it more robust. * ps/config-env-pairs: config: allow specifying config entries via envvar pairs environment: make `getenv_safe()` a public function config: store "git -c" variables using more robust format config: parse more robust format in GIT_CONFIG_PARAMETERS config: extract function to parse config pairs quote: make sq_dequote_step() a public function config: add new way to pass config via `--config-env` git: add `--super-prefix` to usage string
2 parents 7eefa13 + d8d7715 commit 294e949

File tree

11 files changed

+491
-39
lines changed

11 files changed

+491
-39
lines changed

Documentation/git-config.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,22 @@ GIT_CONFIG_NOSYSTEM::
346346

347347
See also <<FILES>>.
348348

349+
GIT_CONFIG_COUNT::
350+
GIT_CONFIG_KEY_<n>::
351+
GIT_CONFIG_VALUE_<n>::
352+
If GIT_CONFIG_COUNT is set to a positive number, all environment pairs
353+
GIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> up to that number will be
354+
added to the process's runtime configuration. The config pairs are
355+
zero-indexed. Any missing key or value is treated as an error. An empty
356+
GIT_CONFIG_COUNT is treated the same as GIT_CONFIG_COUNT=0, namely no
357+
pairs are processed. These environment variables will override values
358+
in configuration files, but will be overridden by any explicit options
359+
passed via `git -c`.
360+
+
361+
This is useful for cases where you want to spawn multiple git commands
362+
with a common configuration but cannot depend on a configuration file,
363+
for example when writing scripts.
364+
349365

350366
[[EXAMPLES]]
351367
EXAMPLES

Documentation/git.txt

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ SYNOPSIS
1313
[--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
1414
[-p|--paginate|-P|--no-pager] [--no-replace-objects] [--bare]
1515
[--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
16-
[--super-prefix=<path>]
16+
[--super-prefix=<path>] [--config-env <name>=<envvar>]
1717
<command> [<args>]
1818

1919
DESCRIPTION
@@ -80,6 +80,28 @@ config file). Including the equals but with an empty value (like `git -c
8080
foo.bar= ...`) sets `foo.bar` to the empty string which `git config
8181
--type=bool` will convert to `false`.
8282

83+
--config-env=<name>=<envvar>::
84+
Like `-c <name>=<value>`, give configuration variable
85+
'<name>' a value, where <envvar> is the name of an
86+
environment variable from which to retrieve the value. Unlike
87+
`-c` there is no shortcut for directly setting the value to an
88+
empty string, instead the environment variable itself must be
89+
set to the empty string. It is an error if the `<envvar>` does not exist
90+
in the environment. `<envvar>` may not contain an equals sign
91+
to avoid ambiguity with `<name>`s which contain one.
92+
+
93+
This is useful for cases where you want to pass transitory
94+
configuration options to git, but are doing so on OS's where
95+
other processes might be able to read your cmdline
96+
(e.g. `/proc/self/cmdline`), but not your environ
97+
(e.g. `/proc/self/environ`). That behavior is the default on
98+
Linux, but may not be on your system.
99+
+
100+
Note that this might add security for variables such as
101+
`http.extraHeader` where the sensitive information is part of
102+
the value, but not e.g. `url.<base>.insteadOf` where the
103+
sensitive information can be part of the key.
104+
83105
--exec-path[=<path>]::
84106
Path to wherever your core Git programs are installed.
85107
This can also be controlled by setting the GIT_EXEC_PATH

cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,7 @@ static inline enum object_type object_type(unsigned int mode)
472472
#define TEMPLATE_DIR_ENVIRONMENT "GIT_TEMPLATE_DIR"
473473
#define CONFIG_ENVIRONMENT "GIT_CONFIG"
474474
#define CONFIG_DATA_ENVIRONMENT "GIT_CONFIG_PARAMETERS"
475+
#define CONFIG_COUNT_ENVIRONMENT "GIT_CONFIG_COUNT"
475476
#define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
476477
#define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES"
477478
#define NO_REPLACE_OBJECTS_ENVIRONMENT "GIT_NO_REPLACE_OBJECTS"

config.c

Lines changed: 184 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "cache.h"
99
#include "branch.h"
1010
#include "config.h"
11+
#include "environment.h"
1112
#include "repository.h"
1213
#include "lockfile.h"
1314
#include "exec-cmd.h"
@@ -332,19 +333,82 @@ int git_config_include(const char *var, const char *value, void *data)
332333
return ret;
333334
}
334335

335-
void git_config_push_parameter(const char *text)
336+
static void git_config_push_split_parameter(const char *key, const char *value)
336337
{
337338
struct strbuf env = STRBUF_INIT;
338339
const char *old = getenv(CONFIG_DATA_ENVIRONMENT);
339340
if (old && *old) {
340341
strbuf_addstr(&env, old);
341342
strbuf_addch(&env, ' ');
342343
}
343-
sq_quote_buf(&env, text);
344+
sq_quote_buf(&env, key);
345+
strbuf_addch(&env, '=');
346+
if (value)
347+
sq_quote_buf(&env, value);
344348
setenv(CONFIG_DATA_ENVIRONMENT, env.buf, 1);
345349
strbuf_release(&env);
346350
}
347351

352+
void git_config_push_parameter(const char *text)
353+
{
354+
const char *value;
355+
356+
/*
357+
* When we see:
358+
*
359+
* section.subsection=with=equals.key=value
360+
*
361+
* we cannot tell if it means:
362+
*
363+
* [section "subsection=with=equals"]
364+
* key = value
365+
*
366+
* or:
367+
*
368+
* [section]
369+
* subsection = with=equals.key=value
370+
*
371+
* We parse left-to-right for the first "=", meaning we'll prefer to
372+
* keep the value intact over the subsection. This is historical, but
373+
* also sensible since values are more likely to contain odd or
374+
* untrusted input than a section name.
375+
*
376+
* A missing equals is explicitly allowed (as a bool-only entry).
377+
*/
378+
value = strchr(text, '=');
379+
if (value) {
380+
char *key = xmemdupz(text, value - text);
381+
git_config_push_split_parameter(key, value + 1);
382+
free(key);
383+
} else {
384+
git_config_push_split_parameter(text, NULL);
385+
}
386+
}
387+
388+
void git_config_push_env(const char *spec)
389+
{
390+
char *key;
391+
const char *env_name;
392+
const char *env_value;
393+
394+
env_name = strrchr(spec, '=');
395+
if (!env_name)
396+
die(_("invalid config format: %s"), spec);
397+
key = xmemdupz(spec, env_name - spec);
398+
env_name++;
399+
if (!*env_name)
400+
die(_("missing environment variable name for configuration '%.*s'"),
401+
(int)(env_name - spec - 1), spec);
402+
403+
env_value = getenv(env_name);
404+
if (!env_value)
405+
die(_("missing environment variable '%s' for configuration '%.*s'"),
406+
env_name, (int)(env_name - spec - 1), spec);
407+
408+
git_config_push_split_parameter(key, env_value);
409+
free(key);
410+
}
411+
348412
static inline int iskeychar(int c)
349413
{
350414
return isalnum(c) || c == '-';
@@ -437,11 +501,26 @@ int git_config_key_is_valid(const char *key)
437501
return !git_config_parse_key_1(key, NULL, NULL, 1);
438502
}
439503

504+
static int config_parse_pair(const char *key, const char *value,
505+
config_fn_t fn, void *data)
506+
{
507+
char *canonical_name;
508+
int ret;
509+
510+
if (!strlen(key))
511+
return error(_("empty config key"));
512+
if (git_config_parse_key(key, &canonical_name, NULL))
513+
return -1;
514+
515+
ret = (fn(canonical_name, value, data) < 0) ? -1 : 0;
516+
free(canonical_name);
517+
return ret;
518+
}
519+
440520
int git_config_parse_parameter(const char *text,
441521
config_fn_t fn, void *data)
442522
{
443523
const char *value;
444-
char *canonical_name;
445524
struct strbuf **pair;
446525
int ret;
447526

@@ -462,51 +541,131 @@ int git_config_parse_parameter(const char *text,
462541
return error(_("bogus config parameter: %s"), text);
463542
}
464543

465-
if (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {
466-
ret = -1;
467-
} else {
468-
ret = (fn(canonical_name, value, data) < 0) ? -1 : 0;
469-
free(canonical_name);
470-
}
544+
ret = config_parse_pair(pair[0]->buf, value, fn, data);
471545
strbuf_list_free(pair);
472546
return ret;
473547
}
474548

549+
static int parse_config_env_list(char *env, config_fn_t fn, void *data)
550+
{
551+
char *cur = env;
552+
while (cur && *cur) {
553+
const char *key = sq_dequote_step(cur, &cur);
554+
if (!key)
555+
return error(_("bogus format in %s"),
556+
CONFIG_DATA_ENVIRONMENT);
557+
558+
if (!cur || isspace(*cur)) {
559+
/* old-style 'key=value' */
560+
if (git_config_parse_parameter(key, fn, data) < 0)
561+
return -1;
562+
}
563+
else if (*cur == '=') {
564+
/* new-style 'key'='value' */
565+
const char *value;
566+
567+
cur++;
568+
if (*cur == '\'') {
569+
/* quoted value */
570+
value = sq_dequote_step(cur, &cur);
571+
if (!value || (cur && !isspace(*cur))) {
572+
return error(_("bogus format in %s"),
573+
CONFIG_DATA_ENVIRONMENT);
574+
}
575+
} else if (!*cur || isspace(*cur)) {
576+
/* implicit bool: 'key'= */
577+
value = NULL;
578+
} else {
579+
return error(_("bogus format in %s"),
580+
CONFIG_DATA_ENVIRONMENT);
581+
}
582+
583+
if (config_parse_pair(key, value, fn, data) < 0)
584+
return -1;
585+
}
586+
else {
587+
/* unknown format */
588+
return error(_("bogus format in %s"),
589+
CONFIG_DATA_ENVIRONMENT);
590+
}
591+
592+
if (cur) {
593+
while (isspace(*cur))
594+
cur++;
595+
}
596+
}
597+
return 0;
598+
}
599+
475600
int git_config_from_parameters(config_fn_t fn, void *data)
476601
{
477-
const char *env = getenv(CONFIG_DATA_ENVIRONMENT);
602+
const char *env;
603+
struct strbuf envvar = STRBUF_INIT;
604+
struct strvec to_free = STRVEC_INIT;
478605
int ret = 0;
479-
char *envw;
480-
const char **argv = NULL;
481-
int nr = 0, alloc = 0;
482-
int i;
606+
char *envw = NULL;
483607
struct config_source source;
484608

485-
if (!env)
486-
return 0;
487-
488609
memset(&source, 0, sizeof(source));
489610
source.prev = cf;
490611
source.origin_type = CONFIG_ORIGIN_CMDLINE;
491612
cf = &source;
492613

493-
/* sq_dequote will write over it */
494-
envw = xstrdup(env);
614+
env = getenv(CONFIG_COUNT_ENVIRONMENT);
615+
if (env) {
616+
unsigned long count;
617+
char *endp;
618+
int i;
495619

496-
if (sq_dequote_to_argv(envw, &argv, &nr, &alloc) < 0) {
497-
ret = error(_("bogus format in %s"), CONFIG_DATA_ENVIRONMENT);
498-
goto out;
620+
count = strtoul(env, &endp, 10);
621+
if (*endp) {
622+
ret = error(_("bogus count in %s"), CONFIG_COUNT_ENVIRONMENT);
623+
goto out;
624+
}
625+
if (count > INT_MAX) {
626+
ret = error(_("too many entries in %s"), CONFIG_COUNT_ENVIRONMENT);
627+
goto out;
628+
}
629+
630+
for (i = 0; i < count; i++) {
631+
const char *key, *value;
632+
633+
strbuf_addf(&envvar, "GIT_CONFIG_KEY_%d", i);
634+
key = getenv_safe(&to_free, envvar.buf);
635+
if (!key) {
636+
ret = error(_("missing config key %s"), envvar.buf);
637+
goto out;
638+
}
639+
strbuf_reset(&envvar);
640+
641+
strbuf_addf(&envvar, "GIT_CONFIG_VALUE_%d", i);
642+
value = getenv_safe(&to_free, envvar.buf);
643+
if (!value) {
644+
ret = error(_("missing config value %s"), envvar.buf);
645+
goto out;
646+
}
647+
strbuf_reset(&envvar);
648+
649+
if (config_parse_pair(key, value, fn, data) < 0) {
650+
ret = -1;
651+
goto out;
652+
}
653+
}
499654
}
500655

501-
for (i = 0; i < nr; i++) {
502-
if (git_config_parse_parameter(argv[i], fn, data) < 0) {
656+
env = getenv(CONFIG_DATA_ENVIRONMENT);
657+
if (env) {
658+
/* sq_dequote will write over it */
659+
envw = xstrdup(env);
660+
if (parse_config_env_list(envw, fn, data) < 0) {
503661
ret = -1;
504662
goto out;
505663
}
506664
}
507665

508666
out:
509-
free(argv);
667+
strbuf_release(&envvar);
668+
strvec_clear(&to_free);
510669
free(envw);
511670
cf = source.prev;
512671
return ret;

config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ int git_config_from_mem(config_fn_t fn,
138138
int git_config_from_blob_oid(config_fn_t fn, const char *name,
139139
const struct object_id *oid, void *data);
140140
void git_config_push_parameter(const char *text);
141+
void git_config_push_env(const char *spec);
141142
int git_config_from_parameters(config_fn_t fn, void *data);
142143
void read_early_config(config_fn_t cb, void *data);
143144
void read_very_early_config(config_fn_t cb, void *data);

environment.c

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010
#include "cache.h"
1111
#include "branch.h"
12+
#include "environment.h"
1213
#include "repository.h"
1314
#include "config.h"
1415
#include "refs.h"
@@ -116,6 +117,7 @@ const char * const local_repo_env[] = {
116117
ALTERNATE_DB_ENVIRONMENT,
117118
CONFIG_ENVIRONMENT,
118119
CONFIG_DATA_ENVIRONMENT,
120+
CONFIG_COUNT_ENVIRONMENT,
119121
DB_ENVIRONMENT,
120122
GIT_DIR_ENVIRONMENT,
121123
GIT_WORK_TREE_ENVIRONMENT,
@@ -152,11 +154,7 @@ static char *expand_namespace(const char *raw_namespace)
152154
return strbuf_detach(&buf, NULL);
153155
}
154156

155-
/*
156-
* Wrapper of getenv() that returns a strdup value. This value is kept
157-
* in argv to be freed later.
158-
*/
159-
static const char *getenv_safe(struct strvec *argv, const char *name)
157+
const char *getenv_safe(struct strvec *argv, const char *name)
160158
{
161159
const char *value = getenv(name);
162160

environment.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#ifndef ENVIRONMENT_H
2+
#define ENVIRONMENT_H
3+
4+
#include "strvec.h"
5+
6+
/*
7+
* Wrapper of getenv() that returns a strdup value. This value is kept
8+
* in argv to be freed later.
9+
*/
10+
const char *getenv_safe(struct strvec *argv, const char *name);
11+
12+
#endif

0 commit comments

Comments
 (0)