diff options
| author | Junio C Hamano <gitster@pobox.com> | 2026-09-10 08:38:37 -0700 |
|---|---|---|
| committer | Junio C Hamano <gitster@pobox.com> | 2026-09-10 08:38:37 -0700 |
| commit | a6f2bea1acc73fcf34154756f043594ace6e3ec8 (patch) | |
| tree | 949ddd8e7b1041fc15babdfa2cfb860a99c57c5b | |
| parent | c073a070363222dcdc24b2cde692f8ca1bd65233 (diff) | |
| parent | a190cc5ca59f0143cb28abb57a5c5f520fbc0afa (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.adoc | 64 | ||||
| -rw-r--r-- | builtin/var.c | 252 | ||||
| -rwxr-xr-x | t/t0007-git-var.sh | 127 |
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 |
