var: accept more than one variable

"git var" takes exactly one variable and shows its value, so a script
that needs several of them has to run the command once per variable,
paying process startup and configuration parsing each time.

Teach "git var" to take any number of variables and show them the way
"git var -l" does, i.e. as "VARIABLE=value" (or "VARIABLE\nvalue\0"
under "-z"), but limited to the variables the user asked for.  A single
variable still shows its bare value, and still exits with status 1 when
it has no value, so existing users are unaffected.

When more than one variable is asked for, a variable that has no value
is simply left out of the output and the rest are still shown.  That a
variable is unset is not an error in itself: a caller asking for both
GIT_CONFIG_SYSTEM and GIT_CONFIG_GLOBAL should not be told that
something went wrong merely because the user set GIT_CONFIG_NOSYSTEM.
Keeping a non-zero status for genuine errors, such as naming a variable
that does not exist, also lets callers detect those by looking at the
exit code alone.

Signed-off-by: Andrew Pleeter <andrewpleeter@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
next
Andrew Pleeter 2026-09-26 12:20:47 -04:00 committed by Junio C Hamano
parent e19f0779c6
commit 09726e777e
3 changed files with 123 additions and 32 deletions

View File

@ -10,17 +10,22 @@ SYNOPSIS
--------
[synopsis]
git var [-z] -l
git var [-z] <variable>
git var [-z] <variable>...

DESCRIPTION
-----------
Prints a Git logical variable. Exits with code 1 if the variable has
no value.
Prints Git logical variables. When a single variable is requested, its
bare value is printed, and the command exits with code 1 if it has no
value. When multiple variables are requested, they are printed as
`VARIABLE=value` pairs, separated by newlines; a variable that has no
value is omitted from the output, which is not by itself an error.

If `-z` is given, the value is terminated by a NUL byte instead of a
newline. With `-l`, the variable name and its value are separated by a
newline, and each entry is terminated by a NUL byte
(`VARIABLE\nvalue\0`), in the same way as `git config list -z`.
If `-z` is given, the output format changes depending on the mode:

* With a single variable, the bare value is terminated by a NUL byte.
* With multiple variables or with `-l`, the variable name and value are
separated by a newline, and each entry is terminated by a NUL byte
(`VARIABLE\nvalue\0`), in the same way as `git config list -z`.

OPTIONS
-------
@ -32,8 +37,9 @@ OPTIONS

`-z`::
Terminate entries with NUL instead of newline. When used with
`-l`, the variable name and its value are separated by a
newline, and each entry is terminated with a NUL byte.
`-l` or when multiple variables are requested, the variable name
and its value are separated by a newline, and each entry is
terminated with a NUL byte.

EXAMPLES
--------
@ -97,9 +103,11 @@ endif::git-default-pager[]
This variable can have multiple values.

Most path values contain only one value. However, some can contain multiple
values, which are separated by newlines (or NUL bytes if `-z` is given), and are
listed in order from highest to lowest priority. Callers should be prepared for
any such path value to contain multiple items.
values, which are separated by newlines (or NUL bytes if `-z` is given),
and are listed in order from highest to lowest priority. When querying
multiple variables (or using `-l`), each value is output as a separate
`VARIABLE=value` entry (or `VARIABLE\nvalue\0` with `-z`). Callers should
be prepared for any such path value to contain multiple items.

Note that paths are printed even if they do not exist, but not if they are
disabled by other environment variables.

View File

@ -23,7 +23,7 @@

static const char * const var_usage[] = {
N_("git var [-z] -l"),
N_("git var [-z] <variable>"),
N_("git var [-z] <variable>..."),
NULL
};

@ -225,9 +225,9 @@ int cmd_var(int argc,
const char *prefix,
struct repository *repo UNUSED)
{
const struct git_var *git_var;
int list = 0;
int nul_term = 0;
char delim;
char term;
struct option options[] = {
OPT_BOOL('l', NULL, &list,
@ -248,36 +248,52 @@ int cmd_var(int argc,
return 0;
}

if (argc != 1)
if (!argc)
usage_with_options(var_usage, options);

repo_config(the_repository, git_default_config, NULL);

delim = nul_term ? '\n' : '=';
term = nul_term ? '\0' : '\n';

git_var = get_git_var(argv[0]);
if (!git_var)
usage_with_options(var_usage, options);
for (int i = 0; i < argc; i++) {
const struct git_var *git_var = get_git_var(argv[i]);

if (git_var->read) {
char *val = git_var->read(IDENT_STRICT);
if (!git_var)
usage_with_options(var_usage, options);

if (!val)
return 1;
if (git_var->read) {
char *val = git_var->read(IDENT_STRICT);

printf("%s%c", val, term);
free(val);
} else {
struct string_list list = STRING_LIST_INIT_DUP;
if (!val) {
if (argc == 1)
return 1;
continue;
}
if (argc == 1)
printf("%s%c", val, term);
else
printf("%s%c%s%c", git_var->name, delim,
val, term);
free(val);
} else {
struct string_list list = STRING_LIST_INIT_DUP;

git_var->multiread(&list);
if (!list.nr) {
git_var->multiread(&list);
if (argc == 1 && !list.nr) {
string_list_clear(&list, 0);
return 1;
}
for (size_t j = 0; j < list.nr; j++) {
if (argc == 1)
printf("%s%c", list.items[j].string,
term);
else
printf("%s%c%s%c", git_var->name, delim,
list.items[j].string, term);
}
string_list_clear(&list, 0);
return 1;
}
for (size_t i = 0; i < list.nr; i++)
printf("%s%c", list.items[i].string, term);
string_list_clear(&list, 0);
}

return 0;

View File

@ -306,4 +306,71 @@ test_expect_success 'options must precede variable arguments' '
test_must_fail git var GIT_AUTHOR_IDENT -z
'

test_expect_success 'get multiple variables' '
test_tick &&
cat >expect <<-EOF &&
GIT_AUTHOR_IDENT=$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
GIT_COMMITTER_IDENT=$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
EOF
git var GIT_AUTHOR_IDENT GIT_COMMITTER_IDENT >actual &&
test_cmp expect actual
'

test_expect_success 'get multiple variables with -z' '
test_tick &&
printf "GIT_AUTHOR_IDENT\n%sQGIT_COMMITTER_IDENT\n%sQ" \
"$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" \
"$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" >expect &&
git var -z GIT_AUTHOR_IDENT GIT_COMMITTER_IDENT >actual.raw &&
nul_to_q <actual.raw >actual &&
test_cmp expect actual
'

test_expect_success 'get multiple variables including multi-valued variable' '
test_tick &&
TRASHDIR="$(test-tool path-utils normalize_path_copy "$(pwd)")" &&
cat >expect <<-EOF &&
GIT_AUTHOR_IDENT=$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
GIT_CONFIG_GLOBAL=$TRASHDIR/foo/git/config
GIT_CONFIG_GLOBAL=$TRASHDIR/.gitconfig
GIT_COMMITTER_IDENT=$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
EOF
HOME="$TRASHDIR" XDG_CONFIG_HOME="$TRASHDIR/foo" \
git var GIT_AUTHOR_IDENT GIT_CONFIG_GLOBAL GIT_COMMITTER_IDENT >actual &&
test_cmp expect actual
'

test_expect_success 'get multiple variables including multi-valued variable with -z' '
test_tick &&
TRASHDIR="$(test-tool path-utils normalize_path_copy "$(pwd)")" &&
printf "GIT_AUTHOR_IDENT\n%sQGIT_CONFIG_GLOBAL\n%sQGIT_CONFIG_GLOBAL\n%sQ" \
"$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE" \
"$TRASHDIR/foo/git/config" "$TRASHDIR/.gitconfig" >expect &&
HOME="$TRASHDIR" XDG_CONFIG_HOME="$TRASHDIR/foo" \
git var -z GIT_AUTHOR_IDENT GIT_CONFIG_GLOBAL >actual.raw &&
nul_to_q <actual.raw >actual &&
test_cmp expect actual
'

test_expect_success 'variable without a value is omitted but is not an error' '
test_tick &&
cat >expect <<-EOF &&
GIT_AUTHOR_IDENT=$GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
GIT_COMMITTER_IDENT=$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
EOF
test_env GIT_CONFIG_GLOBAL= \
git var GIT_AUTHOR_IDENT GIT_CONFIG_GLOBAL GIT_COMMITTER_IDENT >actual &&
test_cmp expect actual
'

test_expect_success 'a single variable without a value still exits with 1' '
test_env GIT_CONFIG_GLOBAL= test_expect_code 1 git var GIT_CONFIG_GLOBAL >out &&
test_must_be_empty out
'

test_expect_success 'unknown variable is a usage error' '
test_must_fail git var GIT_AUTHOR_IDENT NO_SUCH_VARIABLE 2>err &&
test_grep usage err
'

test_done