summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2026-09-10 08:38:37 -0700
committerJunio C Hamano <gitster@pobox.com>2026-09-10 08:38:37 -0700
commita6f2bea1acc73fcf34154756f043594ace6e3ec8 (patch)
tree949ddd8e7b1041fc15babdfa2cfb860a99c57c5b
parentc073a070363222dcdc24b2cde692f8ca1bd65233 (diff)
parenta190cc5ca59f0143cb28abb57a5c5f520fbc0afa (diff)
Merge branch 'ap/var-broken-down-idents' into seenseen
* ap/var-broken-down-idents: var: support broken-down idents, signing key, multiple args, and -z
-rw-r--r--Documentation/git-var.adoc64
-rw-r--r--builtin/var.c252
-rwxr-xr-xt/t0007-git-var.sh127
3 files changed, 383 insertions, 60 deletions
diff --git a/Documentation/git-var.adoc b/Documentation/git-var.adoc
index 697c10aded..ccbb9f8e8d 100644
--- a/Documentation/git-var.adoc
+++ b/Documentation/git-var.adoc
@@ -9,12 +9,16 @@ git-var - Show a Git logical variable
SYNOPSIS
--------
[synopsis]
-git var (-l | <variable>)
+git var [-z] -l
+git var [-z] <variable>...
DESCRIPTION
-----------
-Prints a Git logical variable. Exits with code 1 if the variable has
-no value.
+Prints Git logical variables. Exits with code 1 if any requested
+variable has no value. When multiple variables are requested, an empty
+record (a blank line, or an empty NUL-terminated record when `-z` is given)
+is printed for any variable that has no value, and the command continues
+processing the remaining variables.
OPTIONS
-------
@@ -24,19 +28,55 @@ OPTIONS
as well. (However, the configuration variables listing functionality
is deprecated in favor of `git config list`.)
+`-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.
+
EXAMPLES
--------
- $ git var GIT_AUTHOR_IDENT
- Eric W. Biederman <ebiederm@lnxi.com> 1121223278 -0600
+* Get the author identity:
++
+------------
+$ git var GIT_AUTHOR_IDENT
+Eric W. Biederman <ebiederm@lnxi.com> 1121223278 -0600
+------------
+* Get the author name and email:
++
+------------
+$ git var GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
+Eric W. Biederman
+ebiederm@lnxi.com
+------------
VARIABLES
---------
`GIT_AUTHOR_IDENT`::
- The author of a piece of code.
+`GIT_AUTHOR_NAME`::
+`GIT_AUTHOR_EMAIL`::
+`GIT_AUTHOR_DATE`::
+ The authorship information that would be recorded in the
+ resulting commit object if you ran `git commit` right now.
+ `GIT_AUTHOR_IDENT` consists of the author's name, e-mail
+ address, and timestamp+timezone. These three pieces of
+ information are available separately as `GIT_AUTHOR_NAME`,
+ `GIT_AUTHOR_EMAIL`, and `GIT_AUTHOR_DATE`.
`GIT_COMMITTER_IDENT`::
- The person who put a piece of code into Git.
+`GIT_COMMITTER_NAME`::
+`GIT_COMMITTER_EMAIL`::
+`GIT_COMMITTER_DATE`::
+ The committer information that would be recorded in the
+ resulting commit object if you ran `git commit` right now.
+ `GIT_COMMITTER_IDENT` consists of the committer's name, e-mail
+ address, and timestamp+timezone. These three pieces of
+ information are available separately as `GIT_COMMITTER_NAME`,
+ `GIT_COMMITTER_EMAIL`, and `GIT_COMMITTER_DATE`.
+
+`GIT_SIGNING_KEY`::
+ The key that would be used to sign the resulting commit if you were
+ to run `git commit` right now.
`GIT_EDITOR`::
Text editor for use by Git commands. The value is meant to be
@@ -85,9 +125,13 @@ endif::git-default-pager[]
The path to the global (per-user) configuration files, if any.
Most path values contain only one value. However, some can contain multiple
-values, which are separated by newlines, 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, an extra newline (or an extra NUL byte if `-z` is
+given) is printed after the values of a multi-valued variable to mark the
+end of its list. (Single-variable queries and `git var -l` do not print
+an extra delimiter). 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.
diff --git a/builtin/var.c b/builtin/var.c
index cc3a43cde2..e8472ac722 100644
--- a/builtin/var.c
+++ b/builtin/var.c
@@ -12,25 +12,109 @@
#include "config.h"
#include "editor.h"
#include "environment.h"
+#include "gpg-interface.h"
#include "ident.h"
#include "pager.h"
-#include "refs.h"
+#include "parse-options.h"
#include "path.h"
-#include "strbuf.h"
+#include "refs.h"
#include "run-command.h"
+#include "strbuf.h"
+#include "string-list.h"
-static const char var_usage[] = "git var (-l | <variable>)";
+static const char * const var_usage[] = {
+ N_("git var [-z] -l"),
+ N_("git var [-z] <variable>..."),
+ NULL
+};
+
+enum ident_part {
+ IDENT_NAME,
+ IDENT_MAIL,
+ IDENT_DATE,
+};
static char *committer(int ident_flag)
{
return xstrdup_or_null(git_committer_info(ident_flag));
}
+static char *ident_part(const char *ident, enum ident_part part)
+{
+ struct ident_split split;
+
+ if (!ident)
+ return NULL;
+ if (split_ident_line(&split, ident, strlen(ident)))
+ return NULL;
+
+ switch (part) {
+ case IDENT_NAME:
+ if (!split.name_begin || !split.name_end)
+ BUG("split_ident_line() gave NULL names???");
+ return xmemdupz(split.name_begin,
+ split.name_end - split.name_begin);
+ case IDENT_MAIL:
+ if (!split.mail_begin || !split.mail_end)
+ BUG("split_ident_line() gave NULL mail???");
+ return xmemdupz(split.mail_begin,
+ split.mail_end - split.mail_begin);
+ case IDENT_DATE:
+ if (!split.date_begin || !split.tz_end)
+ BUG("split_ident_line() gave NULL date/tz???");
+ return xmemdupz(split.date_begin,
+ split.tz_end - split.date_begin);
+ default:
+ BUG("unknown ident_part %d", part);
+ }
+}
+
+static char *committer_name(int ident_flag)
+{
+ return ident_part(git_committer_info(ident_flag), IDENT_NAME);
+}
+
+static char *committer_email(int ident_flag)
+{
+ return ident_part(git_committer_info(ident_flag), IDENT_MAIL);
+}
+
+static char *committer_date(int ident_flag)
+{
+ return ident_part(git_committer_info(ident_flag), IDENT_DATE);
+}
+
static char *author(int ident_flag)
{
return xstrdup_or_null(git_author_info(ident_flag));
}
+static char *author_name(int ident_flag)
+{
+ return ident_part(git_author_info(ident_flag), IDENT_NAME);
+}
+
+static char *author_email(int ident_flag)
+{
+ return ident_part(git_author_info(ident_flag), IDENT_MAIL);
+}
+
+static char *author_date(int ident_flag)
+{
+ return ident_part(git_author_info(ident_flag), IDENT_DATE);
+}
+
+static char *git_signing_key(int ident_flag UNUSED)
+{
+ char *signing_key = get_signing_key();
+
+ if (signing_key && !*signing_key) {
+ free(signing_key);
+ return NULL;
+ }
+ return signing_key;
+}
+
static char *editor(int ident_flag UNUSED)
{
return xstrdup_or_null(git_editor());
@@ -90,35 +174,27 @@ static char *git_config_val_system(int ident_flag UNUSED)
return NULL;
}
-static char *git_config_val_global(int ident_flag UNUSED)
+static void git_config_val_global(struct string_list *list)
{
- struct strbuf buf = STRBUF_INIT;
char *user, *xdg;
- size_t unused;
git_global_config_paths(&user, &xdg);
if (xdg && *xdg) {
normalize_path_copy(xdg, xdg);
- strbuf_addf(&buf, "%s\n", xdg);
+ string_list_append(list, xdg);
}
if (user && *user) {
normalize_path_copy(user, user);
- strbuf_addf(&buf, "%s\n", user);
+ string_list_append(list, user);
}
free(xdg);
free(user);
- strbuf_trim_trailing_newline(&buf);
- if (buf.len == 0) {
- strbuf_release(&buf);
- return NULL;
- }
- return strbuf_detach(&buf, &unused);
}
struct git_var {
const char *name;
char *(*read)(int);
- int multivalued;
+ void (*multiread)(struct string_list *);
};
static struct git_var git_vars[] = {
{
@@ -126,10 +202,34 @@ static struct git_var git_vars[] = {
.read = committer,
},
{
+ .name = "GIT_COMMITTER_NAME",
+ .read = committer_name,
+ },
+ {
+ .name = "GIT_COMMITTER_EMAIL",
+ .read = committer_email,
+ },
+ {
+ .name = "GIT_COMMITTER_DATE",
+ .read = committer_date,
+ },
+ {
.name = "GIT_AUTHOR_IDENT",
.read = author,
},
{
+ .name = "GIT_AUTHOR_NAME",
+ .read = author_name,
+ },
+ {
+ .name = "GIT_AUTHOR_EMAIL",
+ .read = author_email,
+ },
+ {
+ .name = "GIT_AUTHOR_DATE",
+ .read = author_date,
+ },
+ {
.name = "GIT_EDITOR",
.read = editor,
},
@@ -146,6 +246,10 @@ static struct git_var git_vars[] = {
.read = default_branch,
},
{
+ .name = "GIT_SIGNING_KEY",
+ .read = git_signing_key,
+ },
+ {
.name = "GIT_SHELL_PATH",
.read = shell_path,
},
@@ -163,8 +267,7 @@ static struct git_var git_vars[] = {
},
{
.name = "GIT_CONFIG_GLOBAL",
- .read = git_config_val_global,
- .multivalued = 1,
+ .multiread = git_config_val_global,
},
{
.name = "",
@@ -172,31 +275,37 @@ static struct git_var git_vars[] = {
},
};
-static void list_vars(void)
+static void list_vars(int nul_term)
{
struct git_var *ptr;
- char *val;
+ char delim = nul_term ? '\n' : '=';
+ char term = nul_term ? '\0' : '\n';
- for (ptr = git_vars; ptr->read; ptr++)
- if ((val = ptr->read(0))) {
- if (ptr->multivalued && *val) {
- struct string_list list = STRING_LIST_INIT_DUP;
+ for (ptr = git_vars; ptr->read || ptr->multiread; ptr++) {
+ if (ptr->read) {
+ char *val = ptr->read(0);
- string_list_split(&list, val, "\n", -1);
- for (size_t i = 0; i < list.nr; i++)
- printf("%s=%s\n", ptr->name, list.items[i].string);
- string_list_clear(&list, 0);
- } else {
- printf("%s=%s\n", ptr->name, val);
+ if (val) {
+ printf("%s%c%s%c", ptr->name, delim, val, term);
+ free(val);
}
- free(val);
+ } else {
+ struct string_list list = STRING_LIST_INIT_DUP;
+ size_t i;
+
+ ptr->multiread(&list);
+ for (i = 0; i < list.nr; i++)
+ printf("%s%c%s%c", ptr->name, delim,
+ list.items[i].string, term);
+ string_list_clear(&list, 0);
}
+ }
}
static const struct git_var *get_git_var(const char *var)
{
struct git_var *ptr;
- for (ptr = git_vars; ptr->read; ptr++) {
+ for (ptr = git_vars; ptr->read || ptr->multiread; ptr++) {
if (strcmp(var, ptr->name) == 0) {
return ptr;
}
@@ -207,42 +316,85 @@ static const struct git_var *get_git_var(const char *var)
static int show_config(const char *var, const char *value,
const struct config_context *ctx, void *cb)
{
+ int *nul_term = cb;
+ char delim = *nul_term ? '\n' : '=';
+ char term = *nul_term ? '\0' : '\n';
+
if (value)
- printf("%s=%s\n", var, value);
+ printf("%s%c%s%c", var, delim, value, term);
else
- printf("%s\n", var);
+ printf("%s%c", var, term);
return git_default_config(var, value, ctx, cb);
}
int cmd_var(int argc,
const char **argv,
- const char *prefix UNUSED,
+ const char *prefix,
struct repository *repo UNUSED)
{
- const struct git_var *git_var;
- char *val;
+ int list = 0;
+ int nul_term = 0;
+ int i;
+ char term;
+ struct option options[] = {
+ OPT_BOOL('l', NULL, &list,
+ N_("list all variables")),
+ OPT_BOOL('z', NULL, &nul_term,
+ N_("terminate entries with NUL")),
+ OPT_END(),
+ };
- show_usage_if_asked(argc, argv, var_usage);
- if (argc != 2)
- usage(var_usage);
+ argc = parse_options(argc, argv, prefix, options,
+ var_usage, PARSE_OPT_STOP_AT_NON_OPTION);
- if (strcmp(argv[1], "-l") == 0) {
- repo_config(the_repository, show_config, NULL);
- list_vars();
+ if (list) {
+ if (argc)
+ usage_with_options(var_usage, options);
+ repo_config(the_repository, show_config, &nul_term);
+ list_vars(nul_term);
return 0;
}
+
+ if (!argc)
+ usage_with_options(var_usage, options);
+
repo_config(the_repository, git_default_config, NULL);
- git_var = get_git_var(argv[1]);
- if (!git_var)
- usage(var_usage);
+ term = nul_term ? '\0' : '\n';
+
+ for (i = 0; i < argc; i++) {
+ const struct git_var *git_var = get_git_var(argv[i]);
- val = git_var->read(IDENT_STRICT);
- if (!val)
- return 1;
+ if (!git_var)
+ usage_with_options(var_usage, options);
- printf("%s\n", val);
- free(val);
+ if (git_var->read) {
+ char *val = git_var->read(IDENT_STRICT);
+
+ if (!val) {
+ if (argc == 1)
+ return 1;
+ putc(term, stdout);
+ continue;
+ }
+ printf("%s%c", val, term);
+ free(val);
+ } else {
+ struct string_list list = STRING_LIST_INIT_DUP;
+ size_t j;
+
+ git_var->multiread(&list);
+ if (argc == 1 && !list.nr) {
+ string_list_clear(&list, 0);
+ return 1;
+ }
+ for (j = 0; j < list.nr; j++)
+ printf("%s%c", list.items[j].string, term);
+ if (argc > 1)
+ putc(term, stdout);
+ string_list_clear(&list, 0);
+ }
+ }
return 0;
}
diff --git a/t/t0007-git-var.sh b/t/t0007-git-var.sh
index 2b60317758..75f688a1b1 100755
--- a/t/t0007-git-var.sh
+++ b/t/t0007-git-var.sh
@@ -276,4 +276,131 @@ test_expect_success '`git var -l` works even without HOME' '
)
'
+test_expect_success 'get author identity components' '
+ test_tick &&
+ echo "$GIT_AUTHOR_NAME" >expect.name &&
+ echo "$GIT_AUTHOR_EMAIL" >expect.email &&
+ echo "$GIT_AUTHOR_DATE" >expect.date &&
+ git var GIT_AUTHOR_NAME >actual.name &&
+ git var GIT_AUTHOR_EMAIL >actual.email &&
+ git var GIT_AUTHOR_DATE >actual.date &&
+ test_cmp expect.name actual.name &&
+ test_cmp expect.email actual.email &&
+ test_cmp expect.date actual.date
+'
+
+test_expect_success 'get committer identity components' '
+ test_tick &&
+ echo "$GIT_COMMITTER_NAME" >expect.name &&
+ echo "$GIT_COMMITTER_EMAIL" >expect.email &&
+ echo "$GIT_COMMITTER_DATE" >expect.date &&
+ git var GIT_COMMITTER_NAME >actual.name &&
+ git var GIT_COMMITTER_EMAIL >actual.email &&
+ git var GIT_COMMITTER_DATE >actual.date &&
+ test_cmp expect.name actual.name &&
+ test_cmp expect.email actual.email &&
+ test_cmp expect.date actual.date
+'
+
+test_expect_success 'get multiple variables' '
+ test_tick &&
+ cat >expect <<-EOF &&
+ $GIT_AUTHOR_NAME
+ $GIT_AUTHOR_EMAIL
+ $GIT_COMMITTER_NAME
+ $GIT_COMMITTER_EMAIL
+ EOF
+ git var GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'get multiple variables with -z' '
+ test_tick &&
+ printf "%sQ%sQ" "$GIT_AUTHOR_NAME" "$GIT_AUTHOR_EMAIL" >expect &&
+ git var -z GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL >actual.raw &&
+ nul_to_q <actual.raw >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'get multi-valued variable with -z' '
+ TRASHDIR="$(test-tool path-utils normalize_path_copy "$(pwd)")" &&
+ HOME="$TRASHDIR" XDG_CONFIG_HOME="$TRASHDIR/foo" git var -z GIT_CONFIG_GLOBAL >actual.raw &&
+ printf "%sQ%sQ" "$TRASHDIR/foo/git/config" "$TRASHDIR/.gitconfig" >expect &&
+ nul_to_q <actual.raw >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git var -l -z' '
+ git var -l -z >actual &&
+ tr "\0" "\n" <actual >actual.lines &&
+ echo "$GIT_AUTHOR_NAME" >expect &&
+ sed -n "/^GIT_AUTHOR_NAME$/{n;p;}" actual.lines >actual.author &&
+ test_cmp expect actual.author &&
+ echo false >expect &&
+ sed -n "/^core\.bare$/{n;p;}" actual.lines >actual.bare &&
+ test_cmp expect actual.bare
+'
+
+test_expect_success 'get GIT_SIGNING_KEY with user.signingkey configured' '
+ test_config user.signingkey "TEST_KEY_ID" &&
+ echo "TEST_KEY_ID" >expect &&
+ git var GIT_SIGNING_KEY >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'get GIT_SIGNING_KEY fails when unset' '
+ test_config user.signingkey "" &&
+ test_must_fail git var GIT_SIGNING_KEY
+'
+
+test_expect_success 'git var -l lists new variables' '
+ git var -l >actual &&
+ test_grep "^GIT_AUTHOR_NAME=" actual &&
+ test_grep "^GIT_AUTHOR_EMAIL=" actual &&
+ test_grep "^GIT_AUTHOR_DATE=" actual &&
+ test_grep "^GIT_COMMITTER_NAME=" actual &&
+ test_grep "^GIT_COMMITTER_EMAIL=" actual &&
+ test_grep "^GIT_COMMITTER_DATE=" actual
+'
+
+test_expect_success 'git var -l lists GIT_SIGNING_KEY when configured' '
+ test_config user.signingkey "TEST_KEY_ID" &&
+ git var -l >actual &&
+ test_grep "^GIT_SIGNING_KEY=TEST_KEY_ID" actual
+'
+
+test_expect_success 'options must precede variable arguments' '
+ test_must_fail git var GIT_AUTHOR_NAME -z
+'
+
+test_expect_success 'get multiple variables with unset variable outputs blank record' '
+ test_config user.signingkey "" &&
+ cat >expect <<-EOF &&
+ $GIT_AUTHOR_NAME
+
+ $GIT_COMMITTER_NAME
+ EOF
+ git var GIT_AUTHOR_NAME GIT_SIGNING_KEY GIT_COMMITTER_NAME >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'get multiple variables with -z and unset variable' '
+ test_config user.signingkey "" &&
+ printf "%sQ%sQ" "$GIT_AUTHOR_NAME" "Q$GIT_COMMITTER_NAME" >expect &&
+ git var -z GIT_AUTHOR_NAME GIT_SIGNING_KEY GIT_COMMITTER_NAME >actual.raw &&
+ nul_to_q <actual.raw >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'get multiple variables including multi-valued variable with -z' '
+ TRASHDIR="$(test-tool path-utils normalize_path_copy "$(pwd)")" &&
+ printf "%sQ%sQ%sQQ%sQ" "$GIT_AUTHOR_NAME" \
+ "$TRASHDIR/foo/git/config" "$TRASHDIR/.gitconfig" \
+ "$GIT_AUTHOR_EMAIL" >expect &&
+ HOME="$TRASHDIR" XDG_CONFIG_HOME="$TRASHDIR/foo" \
+ git var -z GIT_AUTHOR_NAME GIT_CONFIG_GLOBAL GIT_AUTHOR_EMAIL >actual.raw &&
+ nul_to_q <actual.raw >actual &&
+ test_cmp expect actual
+'
+
test_done