[PATCH v9 2/4] var: add "-z" output mode
- From
- Andrew Pleeter <andrewpleeter@gmail.com>
- Date
- Sep 26, 2026, 16:20 UTC
- Message-ID
- <20260926162048.30853-3-andrewpleeter@gmail.com>
- In-Reply-To
- <xmqq33va1lcg.fsf@gitster.g>
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 <andrewpleeter@gmail.com> --- Documentation/git-var.adoc | 19 ++++++++--- builtin/var.c | 67 +++++++++++++++++++++++++++----------- t/t0007-git-var.sh | 30 +++++++++++++++++ 3 files changed, 93 insertions(+), 23 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 | <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. +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 | <variable>)"; +static const char * const var_usage[] = { + N_("git var [-z] -l"), + N_("git var [-z] <variable>"), + 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; - - 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(); + 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(), + }; + + argc = parse_options(argc, argv, prefix, options, + var_usage, PARSE_OPT_STOP_AT_NON_OPTION); + + 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.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 && + 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
-- 2.54.0 (Apple Git-157)