[PATCH v3] var: support broken-down idents, default key, multiple args, and -z
From: Andrew Pleeter via GitGitGadget <hidden>
Date: 2026-09-03 02:49:55
Subsystem:
documentation, the rest · Maintainers:
Jonathan Corbet, Linus Torvalds
From: Andrew Pleeter <redacted>
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_DEFAULT_KEY (with GIT_SIGNING_KEY alias) to resolve the
configured or default commit signing key ID / fingerprint.
- 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 and 'git var -l -z'
entries with NUL bytes.
- Update Documentation/git-var.adoc and t/t0007-git-var.sh.
Signed-off-by: Andrew Pleeter <redacted>
---
var: support broken-down idents, default 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.
Following discussion on v1/v2 with Jeff King and Junio C Hamano, rather
than introducing a new standalone subcommand (whoami or ident), this
version enhances git var:
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_DEFAULT_KEY (with GIT_SIGNING_KEY alias) 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-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2388/anpl1623/master-v3
Pull-Request: https://github.com/git/git/pull/2388
Range-diff vs v2:
1: f322e7fedd < -: ---------- builtin/ident: add new 'ident' command
-: ---------- > 1: b86340dd0e var: support broken-down idents, default key, multiple args, and -z
Documentation/git-var.adoc | 43 ++++++++-
builtin/var.c | 189 ++++++++++++++++++++++++++++++++-----
t/t0007-git-var.sh | 77 +++++++++++++++
3 files changed, 284 insertions(+), 25 deletions(-)
diff --git a/Documentation/git-var.adoc b/Documentation/git-var.adoc
index 697c10aded..30bf2c12a8 100644
--- a/Documentation/git-var.adoc
+++ b/Documentation/git-var.adoc@@ -9,7 +9,7 @@ git-var - Show a Git logical variable SYNOPSIS -------- [synopsis] -git var (-l | <variable>) +git var (-l [-z] | [-z] <variable>...) DESCRIPTION -----------
@@ -24,20 +24,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. + 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`:: + The name of the author of a piece of code. + +`GIT_AUTHOR_EMAIL`:: + The email of the author of a piece of code. + +`GIT_AUTHOR_DATE`:: + The date and timezone of the author of a piece of code. + `GIT_COMMITTER_IDENT`:: The person who put a piece of code into Git. +`GIT_COMMITTER_NAME`:: + The name of the person who put a piece of code into Git. + +`GIT_COMMITTER_EMAIL`:: + The email of the person who put a piece of code into Git. + +`GIT_COMMITTER_DATE`:: + The date and timezone of the person who put a piece of code into Git. + +`GIT_DEFAULT_KEY`:: + The default commit signing key ID or fingerprint, if configured or enabled. + `GIT_EDITOR`:: Text editor for use by Git commands. The value is meant to be interpreted by the shell when it is used. Examples: `~/bin/vi`,
diff --git a/builtin/var.c b/builtin/var.c
index cc3a43cde2..94207cbc7c 100644
--- a/builtin/var.c
+++ b/builtin/var.c@@ -12,25 +12,103 @@ #include "config.h" #include "editor.h" #include "environment.h" +#include "gpg-interface.h" #include "ident.h" #include "pager.h" #include "refs.h" #include "path.h" #include "strbuf.h" +#include "strvec.h" #include "run-command.h" -static const char var_usage[] = "git var (-l | <variable>)"; +static const char var_usage[] = "git var (-l [-z] | [-z] <variable>...)"; static char *committer(int ident_flag) { return xstrdup_or_null(git_committer_info(ident_flag)); } +static char *ident_part(const char *ident, char part) +{ + struct ident_split split; + + if (!ident) + return NULL; + if (split_ident_line(&split, ident, strlen(ident))) + return NULL; + + switch (part) { + case 'n': + if (!split.name_begin || !split.name_end) + return NULL; + return xmemdupz(split.name_begin, split.name_end - split.name_begin); + case 'e': + if (!split.mail_begin || !split.mail_end) + return NULL; + return xmemdupz(split.mail_begin, split.mail_end - split.mail_begin); + case 'd': + 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), 'n'); +} + +static char *committer_email(int ident_flag) +{ + return ident_part(git_committer_info(ident_flag), 'e'); +} + +static char *committer_date(int ident_flag) +{ + return ident_part(git_committer_info(ident_flag), 'd'); +} + 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), 'n'); +} + +static char *author_email(int ident_flag) +{ + return ident_part(git_author_info(ident_flag), 'e'); +} + +static char *author_date(int ident_flag) +{ + return ident_part(git_author_info(ident_flag), 'd'); +} + +static char *default_key(int ident_flag UNUSED) +{ + int gpgsign = 0; + char *signing_key = NULL; + + if (repo_config_get_string(the_repository, "user.signingkey", &signing_key) == 0 && signing_key && *signing_key) + return signing_key; + free(signing_key); + + if (repo_config_get_bool(the_repository, "commit.gpgsign", &gpgsign) == 0 && gpgsign) + return get_signing_key_id(); + + return NULL; +} + static char *editor(int ident_flag UNUSED) { return xstrdup_or_null(git_editor());
@@ -125,10 +203,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 +247,10 @@ static struct git_var git_vars[] = { .name = "GIT_DEFAULT_BRANCH", .read = default_branch, }, + { + .name = "GIT_DEFAULT_KEY", + .read = default_key, + }, { .name = "GIT_SHELL_PATH", .read = shell_path,
@@ -172,10 +278,11 @@ static struct git_var git_vars[] = { }, }; -static void list_vars(void) +static void list_vars(int null_term) { struct git_var *ptr; char *val; + char eol = null_term ? '\0' : '\n'; for (ptr = git_vars; ptr->read; ptr++) if ((val = ptr->read(0))) {
@@ -184,10 +291,10 @@ static void list_vars(void) 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); + printf("%s=%s%c", ptr->name, list.items[i].string, eol); string_list_clear(&list, 0); } else { - printf("%s=%s\n", ptr->name, val); + printf("%s=%s%c", ptr->name, val, eol); } free(val); }
@@ -196,6 +303,8 @@ static void list_vars(void) static const struct git_var *get_git_var(const char *var) { struct git_var *ptr; + if (!strcmp(var, "GIT_SIGNING_KEY")) + var = "GIT_DEFAULT_KEY"; for (ptr = git_vars; ptr->read; ptr++) { if (strcmp(var, ptr->name) == 0) { return ptr;
@@ -207,10 +316,13 @@ 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; + char eol = null_term ? '\0' : '\n'; + if (value) - printf("%s=%s\n", var, value); + printf("%s=%s%c", var, value, eol); else - printf("%s\n", var); + printf("%s%c", var, eol); return git_default_config(var, value, ctx, cb); }
@@ -219,30 +331,65 @@ int cmd_var(int argc, const char *prefix UNUSED, struct repository *repo UNUSED) { - const struct git_var *git_var; - char *val; + struct strvec vars = STRVEC_INIT; + int list = 0; + int null_term = 0; + int i; show_usage_if_asked(argc, argv, var_usage); - if (argc != 2) - usage(var_usage); - if (strcmp(argv[1], "-l") == 0) { - repo_config(the_repository, show_config, NULL); - list_vars(); + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + + if (!strcmp(arg, "-l")) { + list = 1; + } else if (!strcmp(arg, "-z")) { + null_term = 1; + } else if (!strcmp(arg, "--")) { + for (i = i + 1; i < argc; i++) + strvec_push(&vars, argv[i]); + break; + } else if (arg[0] == '-') { + usage(var_usage); + } else { + strvec_push(&vars, arg); + } + } + + if (list) { + if (vars.nr > 0) { + strvec_clear(&vars); + usage(var_usage); + } + repo_config(the_repository, show_config, &null_term); + list_vars(null_term); return 0; } - repo_config(the_repository, git_default_config, NULL); - git_var = get_git_var(argv[1]); - if (!git_var) + if (!vars.nr) usage(var_usage); - val = git_var->read(IDENT_STRICT); - if (!val) - return 1; + repo_config(the_repository, git_default_config, NULL); + + for (size_t j = 0; j < vars.nr; j++) { + const struct git_var *git_var = get_git_var(vars.v[j]); + char *val; + + if (!git_var) { + strvec_clear(&vars); + usage(var_usage); + } + + val = git_var->read(IDENT_STRICT); + if (!val) { + strvec_clear(&vars); + return 1; + } - printf("%s\n", val); - free(val); + printf("%s%c", val, null_term ? '\0' : '\n'); + free(val); + } + strvec_clear(&vars); return 0; }
diff --git a/t/t0007-git-var.sh b/t/t0007-git-var.sh
index 2b60317758..c437c968bb 100755
--- a/t/t0007-git-var.sh
+++ b/t/t0007-git-var.sh@@ -276,4 +276,81 @@ 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%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 'git var -l -z' ' + git var -l -z >actual && + tr "\0" "\n" <actual | grep "^GIT_AUTHOR_NAME=" >filtered && + echo "GIT_AUTHOR_NAME=$GIT_AUTHOR_NAME" >expect && + test_cmp expect filtered +' + +test_expect_success 'get GIT_DEFAULT_KEY with user.signingkey configured' ' + test_config user.signingkey "TEST_KEY_ID" && + echo "TEST_KEY_ID" >expect && + git var GIT_DEFAULT_KEY >actual && + test_cmp expect actual && + git var GIT_SIGNING_KEY >actual.alias && + test_cmp expect actual.alias +' + +test_expect_success 'get GIT_DEFAULT_KEY fails when unset and signing disabled' ' + test_config user.signingkey "" && + test_config commit.gpgsign false && + test_must_fail git var GIT_DEFAULT_KEY +' + +test_expect_success 'git var -l lists new variables' ' + git var -l >actual && + grep "^GIT_AUTHOR_NAME=" actual && + grep "^GIT_AUTHOR_EMAIL=" actual && + grep "^GIT_AUTHOR_DATE=" actual && + grep "^GIT_COMMITTER_NAME=" actual && + grep "^GIT_COMMITTER_EMAIL=" actual && + grep "^GIT_COMMITTER_DATE=" actual +' + test_done
base-commit: 2c3adbb2c475981e340c79fdc5e7f4f9b5d9054e -- gitgitgadget