From: "Andrew Pleeter via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: "brian m. carlson" <sandals@crustytoothpaste.net>,
Jeff King <peff@peff.net>, Junio C Hamano <gitster@pobox.com>,
Ben Knoble <ben.knoble@gmail.com>,
Phillip Wood <phillip.wood123@gmail.com>,
Andrew Pleeter <andrewpleeter@gmail.com>,
Andrew Pleeter <andrewpleeter@gmail.com>
Subject: [PATCH v5] var: support broken-down idents, signing key, multiple args, and -z
Date: Tue, 08 Sep 2026 20:43:02 +0000 [thread overview]
Message-ID: <pull.2388.v5.git.git.1788900182711.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2388.git.git.1787690802942.gitgitgadget@gmail.com>
From: Andrew Pleeter <andrewpleeter@gmail.com>
While 'git var' exposes GIT_AUTHOR_IDENT and GIT_COMMITTER_IDENT,
extracting individual components (name, email, or date) currently
requires callers to manually parse the composite string. Furthermore,
there is no way to query the resolved commit signing key through
'git var', and the command only accepts a single variable at a time.
Teach 'git var' to expose individual identity components and commit
signing configuration, and allow querying multiple variables with
optional NUL-termination:
- Add GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and GIT_AUTHOR_DATE.
- Add GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL, and GIT_COMMITTER_DATE.
- Add GIT_SIGNING_KEY to resolve the key that would be used to sign
the resulting commit if you were to run 'git commit' right now.
- Allow passing multiple variable arguments (e.g., 'git var
GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL') to output each variable
sequentially.
- Support '-z' to terminate variable outputs with NUL bytes.
- Format 'git var -l -z' using the same convention as 'git config
list -z' (newline separating key and value, NUL separating entries).
- Delimit values of multi-valued variables with NUL when '-z' is given,
and output an extra delimiter after multi-valued variables when
querying multiple variables to disambiguate the stream.
- When querying multiple variables, print an empty record for any
variable that has no value and continue processing remaining variables.
- Use parse_options() to strictly require options before arguments.
- Update Documentation/git-var.adoc and t/t0007-git-var.sh.
Signed-off-by: Andrew Pleeter <andrewpleeter@gmail.com>
---
var: support broken-down idents, signing key, multiple args, and -z
Teach git var to expose individual identity components and commit
signing configuration, and allow querying multiple variables with
optional NUL-termination.
Changes since v4:
=================
* Simplified git_signing_key() to directly call get_signing_key() as
used throughout Git (in tag, send-pack, and sign_buffer()).
* Renamed null_term to nul_term across builtin/var.c, and simplified
show_config() callback handling.
* Replaced the redundant pre-validation loop in cmd_var() by validating
arguments directly in the main execution loop.
* When querying multiple variables, print an empty record (blank line
or \0 with -z) for any variable that has no value, and continue
printing remaining variables instead of terminating prematurely.
* Switched multi-valued variable storage (git_config_val_global()) to
internal \0 delimiters, iterating directly through string sequences
without allocating a temporary string_list.
* For multi-variable queries, output an extra delimiter (\n or \0)
after multi-valued variables to clearly mark the end of their list.
* Explicitly documented the git var -l -z format and multi-variable
handling in Documentation/git-var.adoc.
* Added comprehensive tests for unset variables and multi-valued stream
delimiters in t/t0007-git-var.sh.
Changes since v3:
=================
* Renamed GIT_DEFAULT_KEY to GIT_SIGNING_KEY per feedback from Phillip
Wood and Junio C Hamano; dropped the alias mechanism and
commit.gpgsign check.
* Used parse_options() with PARSE_OPT_STOP_AT_NON_OPTION in
builtin/var.c, strictly enforcing that options precede variable
arguments.
* Adopted git config list -z format (key\nvalue\0) for git var -l -z to
prevent ambiguity with = in config keys.
* Delimited multi-valued variable outputs (e.g. GIT_CONFIG_GLOBAL) with
NUL bytes under -z.
* Replaced char part in ident_part() with enum ident_part.
* Split synopsis in Documentation/git-var.adoc into separate lines for
-l and <variable>..., and removed awkward legacy phrasing ("of a
piece of code").
* Added tests in t/t0007-git-var.sh covering the new -z format,
multi-valued -z, and argument ordering.
Changes since v2:
=================
* Drop git ident / git whoami subcommand entirely.
* Add GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and GIT_AUTHOR_DATE.
* Add GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL, and GIT_COMMITTER_DATE.
* Add GIT_SIGNING_KEY to resolve commit signing keys.
* Teach git var to accept multiple variable arguments (git var <var1>
<var2> ...).
* Add -z option to terminate outputs with NUL bytes (including git var
-l -z).
* Update Documentation/git-var.adoc and t/t0007-git-var.sh.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2388%2Fanpl1623%2Fmaster-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2388/anpl1623/master-v5
Pull-Request: https://github.com/git/git/pull/2388
Range-diff vs v4:
1: 6fbf973e84 ! 1: 04833673ea var: support broken-down idents, signing key, multiple args, and -z
@@ Commit message
- Support '-z' to terminate variable outputs with NUL bytes.
- Format 'git var -l -z' using the same convention as 'git config
list -z' (newline separating key and value, NUL separating entries).
- - Delimit values of multi-valued variables with NUL when '-z' is given.
+ - Delimit values of multi-valued variables with NUL when '-z' is given,
+ and output an extra delimiter after multi-valued variables when
+ querying multiple variables to disambiguate the stream.
+ - When querying multiple variables, print an empty record for any
+ variable that has no value and continue processing remaining variables.
- Use parse_options() to strictly require options before arguments.
- Update Documentation/git-var.adoc and t/t0007-git-var.sh.
@@ Documentation/git-var.adoc: git-var - Show a Git logical 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
+ -------
@@ Documentation/git-var.adoc: 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.
++ 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
--------
@@ Documentation/git-var.adoc: endif::git-default-pager[]
-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. Callers should
-+be prepared for any such path value to contain multiple items.
++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. 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.
@@ builtin/var.c
#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
+};
-
--static const char var_usage[] = "git var (-l | <variable>)";
++
+enum ident_part {
+ IDENT_NAME,
+ IDENT_MAIL,
@@ builtin/var.c
+
+static char *git_signing_key(int ident_flag UNUSED)
+{
-+ char *signing_key = NULL;
-+
-+ /*
-+ * An empty string in user.signingkey allows overriding and
-+ * clearing a key defined in an outer (e.g. global) config.
-+ */
-+ if (!repo_config_get_string(the_repository,
-+ "user.signingkey", &signing_key)) {
-+ if (!signing_key || !*signing_key) {
-+ free(signing_key);
-+ return NULL;
-+ }
-+ return signing_key;
-+ }
++ char *signing_key = get_signing_key();
+
-+ signing_key = get_signing_key_id();
+ if (signing_key && !*signing_key) {
+ free(signing_key);
+ return NULL;
@@ builtin/var.c
{
return xstrdup_or_null(git_editor());
@@ builtin/var.c: static char *git_config_val_global(int ident_flag UNUSED)
+ git_global_config_paths(&user, &xdg);
+ if (xdg && *xdg) {
+ normalize_path_copy(xdg, xdg);
+- strbuf_addf(&buf, "%s\n", xdg);
++ strbuf_addstr(&buf, xdg);
++ strbuf_addch(&buf, '\0');
+ }
+ if (user && *user) {
+ normalize_path_copy(user, user);
+- strbuf_addf(&buf, "%s\n", user);
++ strbuf_addstr(&buf, user);
++ strbuf_addch(&buf, '\0');
+ }
free(xdg);
free(user);
- strbuf_trim_trailing_newline(&buf);
+- strbuf_trim_trailing_newline(&buf);
- if (buf.len == 0) {
+ if (!buf.len) {
strbuf_release(&buf);
return NULL;
}
++ strbuf_addch(&buf, '\0');
+ return strbuf_detach(&buf, &unused);
+ }
+
@@ builtin/var.c: static struct git_var git_vars[] = {
.name = "GIT_COMMITTER_IDENT",
.read = committer,
@@ builtin/var.c: static struct git_var git_vars[] = {
};
-static void list_vars(void)
-+static void list_vars(int null_term)
++static void list_vars(int nul_term)
{
struct git_var *ptr;
- char *val;
@@ builtin/var.c: static struct git_var git_vars[] = {
- printf("%s=%s\n", ptr->name, val);
- }
- free(val);
-+ char delim = null_term ? '\n' : '=';
-+ char eol = null_term ? '\0' : '\n';
++ char delim = nul_term ? '\n' : '=';
++ char term = nul_term ? '\0' : '\n';
+
+ for (ptr = git_vars; ptr->read; ptr++) {
+ char *val = ptr->read(0);
@@ builtin/var.c: static struct git_var git_vars[] = {
+ if (!val)
+ continue;
+
-+ if (ptr->multivalued && *val) {
-+ struct string_list list = STRING_LIST_INIT_DUP;
-+
-+ string_list_split(&list, val, "\n", -1);
-+ for (size_t i = 0; i < list.nr; i++)
-+ printf("%s%c%s%c", ptr->name, delim,
-+ list.items[i].string, eol);
-+ string_list_clear(&list, 0);
++ if (ptr->multivalued) {
++ for (const char *s = val; *s; s += strlen(s) + 1)
++ printf("%s%c%s%c", ptr->name, delim, s, term);
+ } else {
-+ printf("%s%c%s%c", ptr->name, delim, val, eol);
++ printf("%s%c%s%c", ptr->name, delim, val, term);
}
+ free(val);
+ }
@@ builtin/var.c: 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 null_term = cb ? *(int *)cb : 0;
++ 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, null_term ? '\n' : '=',
-+ value, null_term ? '\0' : '\n');
++ printf("%s%c%s%c", var, delim, value, term);
else
- printf("%s\n", var);
-+ printf("%s%c", var, null_term ? '\0' : '\n');
++ printf("%s%c", var, term);
return git_default_config(var, value, ctx, cb);
}
@@ builtin/var.c: static const struct git_var *get_git_var(const char *var)
- const struct git_var *git_var;
- char *val;
+ int list = 0;
-+ int null_term = 0;
++ int nul_term = 0;
++ int ret = 0;
+ int i;
++ char term;
+ struct option options[] = {
+ OPT_BOOL('l', NULL, &list,
+ N_("list all variables")),
-+ OPT_BOOL('z', NULL, &null_term,
++ OPT_BOOL('z', NULL, &nul_term,
+ N_("terminate entries with NUL")),
+ OPT_END(),
+ };
@@ builtin/var.c: static const struct git_var *get_git_var(const char *var)
+ if (list) {
+ if (argc)
+ usage_with_options(var_usage, options);
-+ repo_config(the_repository, show_config, &null_term);
-+ list_vars(null_term);
++ repo_config(the_repository, show_config, &nul_term);
++ list_vars(nul_term);
return 0;
}
+
+ if (!argc)
+ usage_with_options(var_usage, options);
-+
-+ for (i = 0; i < argc; i++) {
-+ if (!get_git_var(argv[i]))
-+ 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]);
+ char *val;
-+
-+ val = git_var->read(IDENT_STRICT);
-+ if (!val)
-+ return 1;
- val = git_var->read(IDENT_STRICT);
- if (!val)
- return 1;
-+ if (git_var->multivalued && null_term && *val) {
-+ struct string_list values = STRING_LIST_INIT_DUP;
++ if (!git_var)
++ usage_with_options(var_usage, options);
- printf("%s\n", val);
- free(val);
-+ string_list_split(&values, val, "\n", -1);
-+ for (size_t j = 0; j < values.nr; j++) {
-+ const char *s = values.items[j].string;
++ val = git_var->read(IDENT_STRICT);
++ if (!val) {
++ if (argc == 1)
++ return 1;
++ ret = 1;
++ printf("%c", term);
++ continue;
++ }
+
-+ printf("%s%c", s, '\0');
-+ }
-+ string_list_clear(&values, 0);
++ if (git_var->multivalued) {
++ for (const char *s = val; *s; s += strlen(s) + 1)
++ printf("%s%c", s, term);
++ if (argc > 1)
++ printf("%c", term);
+ } else {
-+ printf("%s%c", val, null_term ? '\0' : '\n');
++ printf("%s%c", val, term);
+ }
+ free(val);
+ }
- return 0;
+- return 0;
++ return ret;
}
## t/t0007-git-var.sh ##
@@ t/t0007-git-var.sh: test_expect_success '`git var -l` works even without HOME' '
+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
++ test_must_fail 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 "%s\0\0%s\0" "$GIT_AUTHOR_NAME" "$GIT_COMMITTER_NAME" >expect &&
++ test_must_fail git var -z GIT_AUTHOR_NAME GIT_SIGNING_KEY GIT_COMMITTER_NAME >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 "%s\0%s\0%s\0\0%s\0" "$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 &&
++ test_cmp expect actual
++'
+
test_done
Documentation/git-var.adoc | 67 ++++++++--
builtin/var.c | 249 ++++++++++++++++++++++++++++++-------
t/t0007-git-var.sh | 123 ++++++++++++++++++
3 files changed, 383 insertions(+), 56 deletions(-)
diff --git a/Documentation/git-var.adoc b/Documentation/git-var.adoc
index 697c10aded..40f9f320ef 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,57 @@ 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.
+ The author.
+
+`GIT_AUTHOR_NAME`::
+ The name of the author.
+
+`GIT_AUTHOR_EMAIL`::
+ The email of the author.
+
+`GIT_AUTHOR_DATE`::
+ The date and timezone of the author.
`GIT_COMMITTER_IDENT`::
- The person who put a piece of code into Git.
+ The committer.
+
+`GIT_COMMITTER_NAME`::
+ The name of the committer.
+
+`GIT_COMMITTER_EMAIL`::
+ The email of the committer.
+
+`GIT_COMMITTER_DATE`::
+ The date and timezone of the committer.
+
+`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 +127,12 @@ 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. 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..a6d14d7b35 100644
--- a/builtin/var.c
+++ b/builtin/var.c
@@ -12,25 +12,116 @@
#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)
+ return NULL;
+ return xmemdupz(split.name_begin,
+ split.name_end - split.name_begin);
+ case IDENT_MAIL:
+ if (!split.mail_begin || !split.mail_end)
+ return NULL;
+ return xmemdupz(split.mail_begin,
+ split.mail_end - split.mail_begin);
+ case IDENT_DATE:
+ if (!split.date_begin)
+ return NULL;
+ if (split.tz_end)
+ return xmemdupz(split.date_begin,
+ split.tz_end -
+ split.date_begin);
+ if (split.date_end)
+ return xmemdupz(split.date_begin,
+ split.date_end -
+ split.date_begin);
+ return NULL;
+ default:
+ return NULL;
+ }
+}
+
+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());
@@ -99,19 +190,21 @@ static char *git_config_val_global(int ident_flag UNUSED)
git_global_config_paths(&user, &xdg);
if (xdg && *xdg) {
normalize_path_copy(xdg, xdg);
- strbuf_addf(&buf, "%s\n", xdg);
+ strbuf_addstr(&buf, xdg);
+ strbuf_addch(&buf, '\0');
}
if (user && *user) {
normalize_path_copy(user, user);
- strbuf_addf(&buf, "%s\n", user);
+ strbuf_addstr(&buf, user);
+ strbuf_addch(&buf, '\0');
}
free(xdg);
free(user);
- strbuf_trim_trailing_newline(&buf);
- if (buf.len == 0) {
+ if (!buf.len) {
strbuf_release(&buf);
return NULL;
}
+ strbuf_addch(&buf, '\0');
return strbuf_detach(&buf, &unused);
}
@@ -125,10 +218,34 @@ static struct git_var git_vars[] = {
.name = "GIT_COMMITTER_IDENT",
.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,
@@ -145,6 +262,10 @@ static struct git_var git_vars[] = {
.name = "GIT_DEFAULT_BRANCH",
.read = default_branch,
},
+ {
+ .name = "GIT_SIGNING_KEY",
+ .read = git_signing_key,
+ },
{
.name = "GIT_SHELL_PATH",
.read = shell_path,
@@ -172,34 +293,35 @@ static struct git_var git_vars[] = {
},
};
-static void list_vars(void)
+static void list_vars(int nul_term)
{
struct git_var *ptr;
- char *val;
-
- for (ptr = git_vars; ptr->read; ptr++)
- if ((val = ptr->read(0))) {
- if (ptr->multivalued && *val) {
- struct string_list list = STRING_LIST_INIT_DUP;
-
- 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);
- }
- free(val);
+ char delim = nul_term ? '\n' : '=';
+ char term = nul_term ? '\0' : '\n';
+
+ for (ptr = git_vars; ptr->read; ptr++) {
+ char *val = ptr->read(0);
+
+ if (!val)
+ continue;
+
+ if (ptr->multivalued) {
+ for (const char *s = val; *s; s += strlen(s) + 1)
+ printf("%s%c%s%c", ptr->name, delim, s, term);
+ } else {
+ printf("%s%c%s%c", ptr->name, delim, val, term);
}
+ free(val);
+ }
}
static const struct git_var *get_git_var(const char *var)
{
struct git_var *ptr;
+
for (ptr = git_vars; ptr->read; ptr++) {
- if (strcmp(var, ptr->name) == 0) {
+ if (!strcmp(var, ptr->name))
return ptr;
- }
}
return NULL;
}
@@ -207,42 +329,79 @@ 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 ret = 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]);
+ char *val;
- 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);
+ val = git_var->read(IDENT_STRICT);
+ if (!val) {
+ if (argc == 1)
+ return 1;
+ ret = 1;
+ printf("%c", term);
+ continue;
+ }
+
+ if (git_var->multivalued) {
+ for (const char *s = val; *s; s += strlen(s) + 1)
+ printf("%s%c", s, term);
+ if (argc > 1)
+ printf("%c", term);
+ } else {
+ printf("%s%c", val, term);
+ }
+ free(val);
+ }
- return 0;
+ return ret;
}
diff --git a/t/t0007-git-var.sh b/t/t0007-git-var.sh
index 2b60317758..92b68b9ab4 100755
--- a/t/t0007-git-var.sh
+++ b/t/t0007-git-var.sh
@@ -276,4 +276,127 @@ 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 "%s\0" "$GIT_AUTHOR_NAME" "$GIT_AUTHOR_EMAIL" >expect &&
+ git var -z GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL >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 &&
+ printf "%s\0" "$TRASHDIR/foo/git/config" "$TRASHDIR/.gitconfig" >expected &&
+ test_cmp expected 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
+ test_must_fail 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 "%s\0\0%s\0" "$GIT_AUTHOR_NAME" "$GIT_COMMITTER_NAME" >expect &&
+ test_must_fail git var -z GIT_AUTHOR_NAME GIT_SIGNING_KEY GIT_COMMITTER_NAME >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 "%s\0%s\0%s\0\0%s\0" "$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 &&
+ test_cmp expect actual
+'
+
test_done
base-commit: 2c3adbb2c475981e340c79fdc5e7f4f9b5d9054e
--
gitgitgadget
next prev parent reply other threads:[~2026-09-08 20:43 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-25 20:46 [PATCH] builtin/whoami: add new 'whoami' command Andrew Pleeter via GitGitGadget
2026-08-25 21:24 ` brian m. carlson
2026-08-25 21:41 ` Junio C Hamano
2026-08-31 23:59 ` [PATCH v2] builtin/ident: add new 'ident' command Andrew Pleeter via GitGitGadget
2026-09-01 4:39 ` Jeff King
2026-09-01 5:00 ` Junio C Hamano
2026-09-03 2:49 ` [PATCH v3] var: support broken-down idents, default key, multiple args, and -z Andrew Pleeter via GitGitGadget
2026-09-03 17:40 ` Junio C Hamano
2026-09-03 18:22 ` Ben Knoble
2026-09-04 9:11 ` Phillip Wood
2026-09-04 15:57 ` Junio C Hamano
2026-09-08 9:07 ` Phillip Wood
2026-09-08 4:09 ` [PATCH v4] var: support broken-down idents, signing " Andrew Pleeter via GitGitGadget
2026-09-08 13:54 ` Phillip Wood
2026-09-08 20:43 ` Andrew Pleeter via GitGitGadget [this message]
2026-09-08 21:53 ` [PATCH v5] " Junio C Hamano
2026-09-09 1:24 ` [PATCH v6] " Andrew Pleeter via GitGitGadget
2026-09-09 15:36 ` Phillip Wood
2026-09-09 16:42 ` Junio C Hamano
2026-09-10 3:09 ` [PATCH v7] " Andrew Pleeter via GitGitGadget
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=pull.2388.v5.git.git.1788900182711.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=andrewpleeter@gmail.com \
--cc=ben.knoble@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=peff@peff.net \
--cc=phillip.wood123@gmail.com \
--cc=sandals@crustytoothpaste.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.