git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v9 2/4] var: add "-z" output mode

From
APAndrew 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)
Previous: Andrew PleeterNext: Andrew Pleeter
Message 31 of 35 in “builtin/whoami: add new 'whoami' command”
  1. builtin/whoami: add new 'whoami' commandAndrew Pleeter via GitGitGadget, Aug 25, 2026
  2. brian m. carlsonAug 25, 2026
  3. Junio C HamanoAug 25, 2026
  4. builtin/ident: add new 'ident' commandAndrew Pleeter via GitGitGadget, Aug 31, 2026
  5. Jeff KingSep 1, 2026
  6. Junio C HamanoSep 1, 2026
  7. var: support broken-down idents, default key, multiple args, and -zAndrew Pleeter via GitGitGadget, Sep 3, 2026
  8. Junio C HamanoSep 3, 2026
  9. Ben KnobleSep 3, 2026
  10. Phillip WoodSep 4, 2026
  11. Junio C HamanoSep 4, 2026
  12. Phillip WoodSep 8, 2026
  13. var: support broken-down idents, signing key, multiple args, and -zAndrew Pleeter via GitGitGadget, Sep 8, 2026
  14. Phillip WoodSep 8, 2026
  15. var: support broken-down idents, signing key, multiple args, and -zAndrew Pleeter via GitGitGadget, Sep 8, 2026
  16. Junio C HamanoSep 8, 2026
  17. var: support broken-down idents, signing key, multiple args, and -zAndrew Pleeter via GitGitGadget, Sep 9, 2026
  18. Phillip WoodSep 9, 2026
  19. Junio C HamanoSep 9, 2026
  20. var: support broken-down idents, signing key, multiple args, and -zAndrew Pleeter via GitGitGadget, Sep 10, 2026
  21. Junio C HamanoSep 14, 2026
  22. var: support broken-down idents, signing key, multiple args, and -zAndrew Pleeter via GitGitGadget, Sep 14, 2026
  23. Junio C HamanoSep 15, 2026
  24. Andrew PleeterSep 15, 2026
  25. Phillip WoodSep 22, 2026
  26. Andrew PleeterSep 26, 2026
  27. 0/4 var: -z output, multiple variables, and broken-out identsAndrew Pleeter, Sep 26, 2026
  28. Phillip WoodOct 2, 2026
  29. Andrew PleeterOct 2, 2026
  30. 1/4 var: represent multi-valued variables with a string_listAndrew Pleeter, Sep 26, 2026
  31. 2/4 var: add "-z" output modeAndrew Pleeter, Sep 26, 2026
  32. 3/4 var: accept more than one variableAndrew Pleeter, Sep 26, 2026
  33. Phillip WoodOct 2, 2026
  34. 4/4 var: add broken-out identity variablesAndrew Pleeter, Sep 26, 2026
  35. Phillip WoodOct 2, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.