From 5e54624a8edfa8cba1b636c0b34dd58e4b13bf7e Mon Sep 17 00:00:00 2001 From: Andrew Pleeter Date: Sat, 26 Sep 2026 12:20:45 -0400 Subject: [PATCH 1/4] var: represent multi-valued variables with a string_list A variable whose value is a list, of which GIT_CONFIG_GLOBAL is currently the only one, is read into a single string whose elements are joined with newlines, and "git var -l" splits that string apart again before showing each element. Round-tripping through a delimiter that can legitimately appear in a value is fragile, and it forces every future caller to know which variables need splitting. Give "struct git_var" a separate "multiread" function that appends the elements to a string_list, and let the single "read" function stay for the variables that have exactly one value. A variable uses one or the other, never both, so which one is set also says whether the variable is multi-valued. The output is unchanged. While at it, say in the documentation that GIT_CONFIG_GLOBAL can have multiple values, so that callers know when to expect a list. Signed-off-by: Andrew Pleeter Signed-off-by: Junio C Hamano --- Documentation/git-var.adoc | 1 + builtin/var.c | 75 +++++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/Documentation/git-var.adoc b/Documentation/git-var.adoc index 697c10aded..de3007732d 100644 --- a/Documentation/git-var.adoc +++ b/Documentation/git-var.adoc @@ -83,6 +83,7 @@ endif::git-default-pager[] `GIT_CONFIG_GLOBAL`:: The path to the global (per-user) configuration files, if any. + This variable can have multiple values. 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 diff --git a/builtin/var.c b/builtin/var.c index cc3a43cde2..9f7c8a6113 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -14,10 +14,11 @@ #include "environment.h" #include "ident.h" #include "pager.h" -#include "refs.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 | )"; @@ -90,35 +91,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[] = { { @@ -163,8 +156,7 @@ static struct git_var git_vars[] = { }, { .name = "GIT_CONFIG_GLOBAL", - .read = git_config_val_global, - .multivalued = 1, + .multiread = git_config_val_global, }, { .name = "", @@ -175,28 +167,30 @@ static struct git_var git_vars[] = { static void list_vars(void) { 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; + 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 { + if (val) { printf("%s=%s\n", ptr->name, val); + free(val); } - free(val); + } else { + struct string_list list = STRING_LIST_INIT_DUP; + + ptr->multiread(&list); + for (size_t i = 0; i < list.nr; i++) + printf("%s=%s\n", ptr->name, list.items[i].string); + 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; } @@ -220,7 +214,6 @@ int cmd_var(int argc, struct repository *repo UNUSED) { const struct git_var *git_var; - char *val; show_usage_if_asked(argc, argv, var_usage); if (argc != 2) @@ -237,12 +230,26 @@ int cmd_var(int argc, if (!git_var) usage(var_usage); - val = git_var->read(IDENT_STRICT); - if (!val) - return 1; + if (git_var->read) { + char *val = git_var->read(IDENT_STRICT); - printf("%s\n", val); - free(val); + if (!val) + return 1; + + printf("%s\n", val); + free(val); + } else { + struct string_list list = STRING_LIST_INIT_DUP; + + git_var->multiread(&list); + if (!list.nr) { + string_list_clear(&list, 0); + return 1; + } + for (size_t i = 0; i < list.nr; i++) + printf("%s\n", list.items[i].string); + string_list_clear(&list, 0); + } return 0; } From e19f0779c67f0a003907d275b9b66f9b8003806d Mon Sep 17 00:00:00 2001 From: Andrew Pleeter Date: Sat, 26 Sep 2026 12:20:46 -0400 Subject: [PATCH 2/4] var: add "-z" output mode Scripts that read from "git var" have no unambiguous way to parse its output. A value that itself contains a newline, and a variable that has more than one value, both run together with the newline that terminates each entry. Teach "git var" a "-z" option that terminates each entry with a NUL byte instead. With "-l", the variable name and its value are separated by a newline and each entry is terminated by NUL, which is the same convention "git config list -z" already uses. When a single variable is asked for, its bare value is terminated by NUL. Parsing of the command line is switched over to parse_options() in order to accept the new option; as a side effect, options now have to come before the variable name. Signed-off-by: Andrew Pleeter Signed-off-by: Junio C Hamano --- Documentation/git-var.adoc | 19 +++++++++--- builtin/var.c | 63 ++++++++++++++++++++++++++++---------- t/t0007-git-var.sh | 30 ++++++++++++++++++ 3 files changed, 91 insertions(+), 21 deletions(-) diff --git a/Documentation/git-var.adoc b/Documentation/git-var.adoc index de3007732d..9052fbc549 100644 --- a/Documentation/git-var.adoc +++ b/Documentation/git-var.adoc @@ -9,13 +9,19 @@ git-var - Show a Git logical variable SYNOPSIS -------- [synopsis] -git var (-l | ) +git var [-z] -l +git var [-z] DESCRIPTION ----------- Prints a Git logical variable. Exits with code 1 if the variable has no value. +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`. + OPTIONS ------- `-l`:: @@ -24,6 +30,11 @@ 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 @@ -86,9 +97,9 @@ 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, 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. 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 9f7c8a6113..c9691070b8 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -14,13 +14,18 @@ #include "environment.h" #include "ident.h" #include "pager.h" +#include "parse-options.h" #include "path.h" #include "refs.h" #include "run-command.h" #include "strbuf.h" #include "string-list.h" -static const char var_usage[] = "git var (-l | )"; +static const char * const var_usage[] = { + N_("git var [-z] -l"), + N_("git var [-z] "), + NULL +}; static char *committer(int ident_flag) { @@ -164,16 +169,18 @@ static struct git_var git_vars[] = { }, }; -static void list_vars(void) +static void list_vars(int nul_term) { struct git_var *ptr; + char delim = nul_term ? '\n' : '='; + char term = nul_term ? '\0' : '\n'; for (ptr = git_vars; ptr->read || ptr->multiread; ptr++) { if (ptr->read) { char *val = ptr->read(0); if (val) { - printf("%s=%s\n", ptr->name, val); + printf("%s%c%s%c", ptr->name, delim, val, term); free(val); } } else { @@ -181,7 +188,8 @@ static void list_vars(void) ptr->multiread(&list); for (size_t i = 0; i < list.nr; i++) - printf("%s=%s\n", ptr->name, list.items[i].string); + printf("%s%c%s%c", ptr->name, delim, + list.items[i].string, term); string_list_clear(&list, 0); } } @@ -201,34 +209,55 @@ 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; + int list = 0; + int nul_term = 0; + 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 != 1) + usage_with_options(var_usage, options); + repo_config(the_repository, git_default_config, NULL); - git_var = get_git_var(argv[1]); + term = nul_term ? '\0' : '\n'; + + git_var = get_git_var(argv[0]); if (!git_var) - usage(var_usage); + usage_with_options(var_usage, options); if (git_var->read) { char *val = git_var->read(IDENT_STRICT); @@ -236,7 +265,7 @@ int cmd_var(int argc, if (!val) return 1; - printf("%s\n", val); + printf("%s%c", val, term); free(val); } else { struct string_list list = STRING_LIST_INIT_DUP; @@ -247,7 +276,7 @@ int cmd_var(int argc, return 1; } for (size_t i = 0; i < list.nr; i++) - printf("%s\n", list.items[i].string); + printf("%s%c", list.items[i].string, term); string_list_clear(&list, 0); } diff --git a/t/t0007-git-var.sh b/t/t0007-git-var.sh index 2b60317758..661d0539c0 100755 --- a/t/t0007-git-var.sh +++ b/t/t0007-git-var.sh @@ -276,4 +276,34 @@ test_expect_success '`git var -l` works even without HOME' ' ) ' +test_expect_success 'get a variable with -z' ' + printf "%sQ" "$(git var GIT_AUTHOR_IDENT)" >expect && + git var -z GIT_AUTHOR_IDENT >actual.raw && + nul_to_q 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 && + test_cmp expect actual +' + +test_expect_success 'git var -l -z' ' + git var -l -z >actual && + tr "\0" "\n" actual.lines && + git var GIT_AUTHOR_IDENT >expect && + sed -n "/^GIT_AUTHOR_IDENT$/{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 'options must precede variable arguments' ' + test_must_fail git var GIT_AUTHOR_IDENT -z +' + test_done From 09726e777e98ef1c9f5b025321dd60ff751a392a Mon Sep 17 00:00:00 2001 From: Andrew Pleeter Date: Sat, 26 Sep 2026 12:20:47 -0400 Subject: [PATCH 3/4] 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 Signed-off-by: Junio C Hamano --- Documentation/git-var.adoc | 32 +++++++++++------- builtin/var.c | 56 +++++++++++++++++++------------ t/t0007-git-var.sh | 67 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 32 deletions(-) diff --git a/Documentation/git-var.adoc b/Documentation/git-var.adoc index 9052fbc549..d0772d9743 100644 --- a/Documentation/git-var.adoc +++ b/Documentation/git-var.adoc @@ -10,17 +10,22 @@ SYNOPSIS -------- [synopsis] git var [-z] -l -git var [-z] +git var [-z] ... 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. diff --git a/builtin/var.c b/builtin/var.c index c9691070b8..5fbebc62e2 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -23,7 +23,7 @@ static const char * const var_usage[] = { N_("git var [-z] -l"), - N_("git var [-z] "), + N_("git var [-z] ..."), 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; diff --git a/t/t0007-git-var.sh b/t/t0007-git-var.sh index 661d0539c0..593610300a 100755 --- a/t/t0007-git-var.sh +++ b/t/t0007-git-var.sh @@ -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 && + 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 && + 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 From 598588c15584a28c62fbb12d4f6bba57f159fbb0 Mon Sep 17 00:00:00 2001 From: Andrew Pleeter Date: Sat, 26 Sep 2026 12:20:48 -0400 Subject: [PATCH 4/4] var: add broken-out identity variables A script that wants only the author's name has to ask for GIT_AUTHOR_IDENT and take the result apart itself, which means reimplementing the rules split_ident_line() already knows, and getting them subtly wrong for names that contain an e-mail address or other awkward characters. Add GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL and GIT_AUTHOR_DATE, together with their GIT_COMMITTER_* counterparts, which give the individual pieces of the corresponding *_IDENT variable. They are derived from the same ident string, so they are strict in the same way: asking for one of them fails if the identity cannot be determined. Signed-off-by: Andrew Pleeter Signed-off-by: Junio C Hamano --- Documentation/git-var.adoc | 36 +++++++++++++-- builtin/var.c | 90 ++++++++++++++++++++++++++++++++++++++ t/t0007-git-var.sh | 56 ++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 4 deletions(-) diff --git a/Documentation/git-var.adoc b/Documentation/git-var.adoc index d0772d9743..6982228f97 100644 --- a/Documentation/git-var.adoc +++ b/Documentation/git-var.adoc @@ -43,17 +43,45 @@ OPTIONS EXAMPLES -------- - $ git var GIT_AUTHOR_IDENT - Eric W. Biederman 1121223278 -0600 +* Get the author identity: ++ +------------ +$ git var GIT_AUTHOR_IDENT +Eric W. Biederman 1121223278 -0600 +------------ + +* Get the author name and email: ++ +------------ +$ git var GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL +GIT_AUTHOR_NAME=Eric W. Biederman +GIT_AUTHOR_EMAIL=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_EDITOR`:: Text editor for use by Git commands. The value is meant to be diff --git a/builtin/var.c b/builtin/var.c index 5fbebc62e2..dd4325e5b5 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -27,16 +27,82 @@ static const char * const var_usage[] = { NULL }; +enum ident_part { + IDENT_NAME, + IDENT_MAIL, + IDENT_DATE, +}; + +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(int ident_flag) { return xstrdup_or_null(git_committer_info(ident_flag)); } +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 *editor(int ident_flag UNUSED) { return xstrdup_or_null(git_editor()); @@ -123,10 +189,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, diff --git a/t/t0007-git-var.sh b/t/t0007-git-var.sh index 593610300a..e43becff1c 100755 --- a/t/t0007-git-var.sh +++ b/t/t0007-git-var.sh @@ -373,4 +373,60 @@ test_expect_success 'unknown variable is a usage error' ' test_grep usage err ' +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 !FAIL_PREREQS,!AUTOIDENT 'identity components are strict' ' + ( + sane_unset GIT_COMMITTER_NAME && + sane_unset GIT_COMMITTER_EMAIL && + test_must_fail git var GIT_COMMITTER_NAME + ) +' + +test_expect_success 'get several identity components at once' ' + test_tick && + cat >expect <<-EOF && + GIT_AUTHOR_NAME=$GIT_AUTHOR_NAME + GIT_AUTHOR_EMAIL=$GIT_AUTHOR_EMAIL + GIT_COMMITTER_NAME=$GIT_COMMITTER_NAME + GIT_COMMITTER_EMAIL=$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 'git var -l lists the identity components' ' + 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_done