* [GSoC][RFC PATCH 0/2] Add refs list subcommand
@ 2025-06-27  7:49 Meet Soni
  2025-06-27  7:49 ` [GSoC][RFC PATCH 1/2] builtin/refs: add " Meet Soni
                   ` (3 more replies)
  0 siblings, 4 replies; 65+ messages in thread
From: Meet Soni @ 2025-06-27  7:49 UTC (permalink / raw)
  To: git; +Cc: ps, shejialuo, karthik.188, Meet Soni
This patch series introduces `git refs list` as a modern replacement for
`git for-each-ref`, as part of an effort to consolidate ref-related
functionality under a unified `git refs` command.
Git's ref-related operations are currently handled by several distinct
commands, such as `git show-ref`, `git for-each-ref`, `git update-ref`,
`git pack-refs`, etc. This distribution has a few practical drawbacks:
- Users need to rely on multiple commands for related tasks involving
  refs.
- The commands may differ slightly in behavior and option syntax,
  leading to inconsistency.
We propose a long-term consolidation effort to bring ref-related
subcommands under the umbrella of a single command: `git refs`.
The implementation of `git refs list` is functionally identical to `git
for-each-ref`. It reuses the same internal logic (cmd_for_each_ref) to
ensure complete backward compatibility. The purpose of this patch is not
to introduce new behavior but to provide an alternate entry point under
the consolidated `git refs` namespace.
The motivation behind this change is twofold:
- Consolidation: Centralizing ref-related operations makes them easier
  to discover, use, and maintain.
- Evolution: While the initial goal is parity with existing commands,
  this consolidation allows for careful reconsideration of which
  features are essential. Over time, we can:
  - Remove legacy or obscure options that are no longer needed.
  - Add improvements that wouldn't make sense to bolt onto legacy
    commands.
  - Offering a more consistent and user-friendly surface.
To verify backward compatibility, this patch also includes a test
`t/t1461-refs-list.sh`, which runs the full `t6300-for-each-ref.sh` test
using `git refs list`. The test uses ${GIT_REFS_LIST_CMD:-for-each-ref}
to allow substitution without duplicating tests.
This patch is deliberately conservative: it introduces no behavioral
changes and leaves `for-each-ref` untouched. The goal is to lay
groundwork and demonstrate viability of ref consolidation within `git
refs`.
Going forward, I'd like to initiate a discussion on what the ideal
surface of `git refs list` should look like. Which options and features
from `for-each-ref` should be carried over? Are there any that are
obsolete or overly niche? What improvements might be worth considering
now that we have a new, consolidated interface?
Feedback on this, especially from those who rely on `for-each-ref` in
scripts or tooling would be very helpful.
Meet Soni (2):
  builtin/refs: add list subcommand
  t: add test for git refs list subcommand
 Documentation/git-refs.adoc |  95 ++++++++++
 builtin/refs.c              | 110 ++++++++++++
 t/meson.build               |   1 +
 t/t1461-refs-list.sh        |   8 +
 t/t6300-for-each-ref.sh     | 333 ++++++++++++++++++------------------
 5 files changed, 384 insertions(+), 163 deletions(-)
 create mode 100755 t/t1461-refs-list.sh
base-commit: cb3b40381e1d5ee32dde96521ad7cfd68eb308a6
-- 
2.34.1
^ permalink raw reply	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH 1/2] builtin/refs: add list subcommand
  2025-06-27  7:49 [GSoC][RFC PATCH 0/2] Add refs list subcommand Meet Soni
@ 2025-06-27  7:49 ` Meet Soni
  2025-06-27 16:27   ` Jean-Noël Avila
  2025-06-29 11:05   ` [PATCH] doc:git-for-each-ref: fix styling and typos Jean-Noël Avila
  2025-06-27  7:49 ` [GSoC][RFC PATCH 2/2] t: add test for git refs list subcommand Meet Soni
                   ` (2 subsequent siblings)
  3 siblings, 2 replies; 65+ messages in thread
From: Meet Soni @ 2025-06-27  7:49 UTC (permalink / raw)
  To: git; +Cc: ps, shejialuo, karthik.188, Meet Soni, Junio C Hamano, John Cai
Git's reference management is distributed across multiple commands. As
part of an ongoing effort to consolidate and modernize reference
handling, introduce a `list` subcommand under the `git refs` umbrella as
a replacement for `git for-each-ref`.
For now, cmd_refs_list is effectively a copy of cmd_for_each_ref,
allowing us to reuse the existing filtering and formatting logic while
avoiding any backward compatibility issues. The core implementation
continues to reside in shared modules.
This duplication is temporary and intentional: future enhancements will
be added exclusively to `git refs list`, providing a gradual path
forward without disrupting existing workflows.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 Documentation/git-refs.adoc |  95 +++++++++++++++++++++++++++++++
 builtin/refs.c              | 110 ++++++++++++++++++++++++++++++++++++
 2 files changed, 205 insertions(+)
diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
index 4d6dc994f9..d8f81eaabd 100644
--- a/Documentation/git-refs.adoc
+++ b/Documentation/git-refs.adoc
@@ -11,6 +11,13 @@ SYNOPSIS
 [synopsis]
 git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
 git refs verify [--strict] [--verbose]
+git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
+	      [(--sort=<key>)...] [--format=<format>]
+	      [--include-root-refs] [ --stdin | <pattern>... ]
+	      [--points-at=<object>]
+	      [--merged[=<object>]] [--no-merged[=<object>]]
+	      [--contains[=<object>]] [--no-contains[=<object>]]
+	      [--exclude=<pattern> ...]
 
 DESCRIPTION
 -----------
@@ -26,6 +33,11 @@ migrate::
 verify::
 	Verify reference database consistency.
 
+list::
+	List references in the repository with support for filtering, formatting,
+	and sorting. This subcommand uses the same core logic as
+	linkgit:git-for-each-ref[1] and offers equivalent functionality.
+
 OPTIONS
 -------
 
@@ -57,6 +69,89 @@ The following options are specific to 'git refs verify':
 --verbose::
 	When verifying the reference database consistency, be chatty.
 
+The following options are specific to 'git refs list':
+
+<pattern>...::
+	If one or more patterns are given, only refs are shown that
+	match against at least one pattern, either using fnmatch(3) or
+	literally, in the latter case matching completely or from the
+	beginning up to a slash.
+
+--stdin::
+	If `--stdin` is supplied, then the list of patterns is read from
+	standard input instead of from the argument list.
+
+--count=<count>::
+	By default the command shows all refs that match
+	`<pattern>`.  This option makes it stop after showing
+	that many refs.
+
+--sort=<key>::
+	A field name to sort on.  Prefix `-` to sort in
+	descending order of the value.  When unspecified,
+	`refname` is used.  You may use the --sort=<key> option
+	multiple times, in which case the last key becomes the primary
+	key.
+
+--format=<format>::
+	A string that interpolates `%(fieldname)` from a ref being shown and
+	the object it points at. In addition, the string literal `%%`
+	renders as `%` and `%xx` - where `xx` are hex digits - renders as
+	the character with hex code `xx`. For example, `%00` interpolates to
+	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
++
+When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
+TAB %(refname)`.
+
+--color[=<when>]::
+	Respect any colors specified in the `--format` option. The
+	`<when>` field must be one of `always`, `never`, or `auto` (if
+	`<when>` is absent, behave as if `always` was given).
+
+--shell::
+--perl::
+--python::
+--tcl::
+	If given, strings that substitute `%(fieldname)`
+	placeholders are quoted as string literals suitable for
+	the specified host language.  This is meant to produce
+	a scriptlet that can directly be `eval`ed.
+
+--points-at=<object>::
+	Only list refs which points at the given object.
+
+--merged[=<object>]::
+	Only list refs whose tips are reachable from the
+	specified commit (HEAD if not specified).
+
+--no-merged[=<object>]::
+	Only list refs whose tips are not reachable from the
+	specified commit (HEAD if not specified).
+
+--contains[=<object>]::
+	Only list refs which contain the specified commit (HEAD if not
+	specified).
+
+--no-contains[=<object>]::
+	Only list refs which don't contain the specified commit (HEAD
+	if not specified).
+
+--ignore-case::
+	Sorting and filtering refs are case insensitive.
+
+--omit-empty::
+	Do not print a newline after formatted refs where the format expands
+	to the empty string.
+
+--exclude=<pattern>::
+	If one or more patterns are given, only refs which do not match
+	any excluded pattern(s) are shown. Matching is done using the
+	same rules as `<pattern>` above.
+
+--include-root-refs::
+	List root refs (HEAD and pseudorefs) apart from regular refs.
+
+
 KNOWN LIMITATIONS
 -----------------
 
diff --git a/builtin/refs.c b/builtin/refs.c
index 998d2a2c1c..70e1757791 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -3,6 +3,7 @@
 #include "config.h"
 #include "fsck.h"
 #include "parse-options.h"
+#include "ref-filter.h"
 #include "refs.h"
 #include "strbuf.h"
 #include "worktree.h"
@@ -13,6 +14,15 @@
 #define REFS_VERIFY_USAGE \
 	N_("git refs verify [--strict] [--verbose]")
 
+#define REFS_LIST_USAGE \
+	N_("git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n" \
+	   "              [(--sort=<key>)...] [--format=<format>]\n" \
+	   "              [--include-root-refs] [ --stdin | <pattern>... ]\n" \
+	   "              [--points-at=<object>]\n" \
+	   "              [--merged[=<object>]] [--no-merged[=<object>]]\n" \
+	   "              [--contains[=<object>]] [--no-contains[=<object>]]\n" \
+	   "              [--exclude=<pattern> ...]")
+
 static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
 			    struct repository *repo UNUSED)
 {
@@ -101,6 +111,104 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
 	return ret;
 }
 
+static int cmd_refs_list(int argc, const char **argv, const char *prefix,
+			 struct repository *repo)
+{
+	struct ref_sorting *sorting;
+	struct string_list sorting_options = STRING_LIST_INIT_DUP;
+	int icase = 0, include_root_refs = 0, from_stdin = 0;
+	struct ref_filter filter = REF_FILTER_INIT;
+	struct ref_format format = REF_FORMAT_INIT;
+	unsigned int flags = FILTER_REFS_REGULAR;
+	struct strvec vec = STRVEC_INIT;
+	const char * const list_usage[] = {
+		REFS_LIST_USAGE,
+		NULL
+	};
+
+	struct option opts[] = {
+		OPT_BIT('s', "shell", &format.quote_style,
+			N_("quote placeholders suitably for shells"), QUOTE_SHELL),
+		OPT_BIT('p', "perl",  &format.quote_style,
+			N_("quote placeholders suitably for perl"), QUOTE_PERL),
+		OPT_BIT(0 , "python", &format.quote_style,
+			N_("quote placeholders suitably for python"), QUOTE_PYTHON),
+		OPT_BIT(0 , "tcl",  &format.quote_style,
+			N_("quote placeholders suitably for Tcl"), QUOTE_TCL),
+		OPT_BOOL(0, "omit-empty",  &format.array_opts.omit_empty,
+			N_("do not output a newline after empty formatted refs")),
+
+		OPT_GROUP(""),
+		OPT_INTEGER(0, "count", &format.array_opts.max_count, N_("show only <n> matched refs")),
+		OPT_STRING(0, "format", &format.format, N_("format"), N_("format to use for the output")),
+		OPT__COLOR(&format.use_color, N_("respect format colors")),
+		OPT_REF_FILTER_EXCLUDE(&filter),
+		OPT_REF_SORT(&sorting_options),
+		OPT_CALLBACK(0, "points-at", &filter.points_at,
+			     N_("object"), N_("print only refs which points at the given object"),
+			     parse_opt_object_name),
+		OPT_MERGED(&filter, N_("print only refs that are merged")),
+		OPT_NO_MERGED(&filter, N_("print only refs that are not merged")),
+		OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")),
+		OPT_NO_CONTAINS(&filter.no_commit, N_("print only refs which don't contain the commit")),
+		OPT_BOOL(0, "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
+		OPT_BOOL(0, "stdin", &from_stdin, N_("read reference patterns from stdin")),
+		OPT_BOOL(0, "include-root-refs", &include_root_refs, N_("also include HEAD ref and pseudorefs")),
+		OPT_END(),
+	};
+
+	format.format = "%(objectname) %(objecttype)\t%(refname)";
+
+	repo_config(repo, git_default_config, NULL);
+
+	/* Default to sorting by refname unless overridden by --sort= */
+	string_list_append(&sorting_options, "refname");
+
+	parse_options(argc, argv, prefix, opts, list_usage, 0);
+	if (format.array_opts.max_count < 0) {
+		error("invalid --count value: `%d'", format.array_opts.max_count);
+		usage_with_options(list_usage, opts);
+	}
+	if (HAS_MULTI_BITS(format.quote_style)) {
+		error("more than one quoting style?");
+		usage_with_options(list_usage, opts);
+	}
+	if (verify_ref_format(&format))
+		usage_with_options(list_usage, opts);
+
+	sorting = ref_sorting_options(&sorting_options);
+	ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
+	filter.ignore_case = icase;
+
+	if (from_stdin) {
+		struct strbuf line = STRBUF_INIT;
+
+		if (argv[0])
+			die(_("unknown arguments supplied with --stdin"));
+
+		while (strbuf_getline(&line, stdin) != EOF)
+			strvec_push(&vec, line.buf);
+
+		strbuf_release(&line);
+
+		/* vec.v is NULL-terminated, just like 'argv'. */
+		filter.name_patterns = vec.v;
+	} else {
+		filter.name_patterns = argv;
+	}
+
+	if (include_root_refs)
+		flags |= FILTER_REFS_ROOT_REFS | FILTER_REFS_DETACHED_HEAD;
+
+	filter.match_as_path = 1;
+	filter_and_format_refs(&filter, flags, sorting, &format);
+
+	ref_filter_clear(&filter);
+	ref_sorting_release(sorting);
+	strvec_clear(&vec);
+	return 0;
+}
+
 int cmd_refs(int argc,
 	     const char **argv,
 	     const char *prefix,
@@ -109,12 +217,14 @@ int cmd_refs(int argc,
 	const char * const refs_usage[] = {
 		REFS_MIGRATE_USAGE,
 		REFS_VERIFY_USAGE,
+		REFS_LIST_USAGE,
 		NULL,
 	};
 	parse_opt_subcommand_fn *fn = NULL;
 	struct option opts[] = {
 		OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate),
 		OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify),
+		OPT_SUBCOMMAND("list", &fn, cmd_refs_list),
 		OPT_END(),
 	};
 
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH 2/2] t: add test for git refs list subcommand
  2025-06-27  7:49 [GSoC][RFC PATCH 0/2] Add refs list subcommand Meet Soni
  2025-06-27  7:49 ` [GSoC][RFC PATCH 1/2] builtin/refs: add " Meet Soni
@ 2025-06-27  7:49 ` Meet Soni
  2025-06-27 18:03 ` [GSoC][RFC PATCH 0/2] Add " Junio C Hamano
  2025-07-17  7:50 ` [GSoC][RFC PATCH v2 " Meet Soni
  3 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-06-27  7:49 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, Meet Soni, Jeff King, Taylor Blau,
	Junio C Hamano, Kousik Sanagavarapu
Adapt `t/t6300-for-each-ref.sh` to invoke ${GIT_REFS_LIST_CMD},
defaulting to `for-each-ref` if the variable is unset.
Add `t/t1461-refs-list.sh` to test git refs list by setting
`GIT_REFS_LIST_CMD` to `refs list` and sourcing
`t/t6300-for-each-ref.sh`
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 t/meson.build           |   1 +
 t/t1461-refs-list.sh    |   8 +
 t/t6300-for-each-ref.sh | 333 ++++++++++++++++++++--------------------
 3 files changed, 179 insertions(+), 163 deletions(-)
 create mode 100755 t/t1461-refs-list.sh
diff --git a/t/meson.build b/t/meson.build
index 50e89e764a..c959c039d0 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -224,6 +224,7 @@ integration_tests = [
   't1450-fsck.sh',
   't1451-fsck-buffer.sh',
   't1460-refs-migrate.sh',
+  't1461-refs-list.sh',
   't1500-rev-parse.sh',
   't1501-work-tree.sh',
   't1502-rev-parse-parseopt.sh',
diff --git a/t/t1461-refs-list.sh b/t/t1461-refs-list.sh
new file mode 100755
index 0000000000..9def7b2e5b
--- /dev/null
+++ b/t/t1461-refs-list.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+test_description='git refs list tests'
+
+. ./test-lib.sh
+
+GIT_REFS_LIST_CMD='refs list'
+. "$TEST_DIRECTORY"/t6300-for-each-ref.sh
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index ce9af79ab1..74a030371c 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -5,7 +5,9 @@
 
 test_description='for-each-ref test'
 
-. ./test-lib.sh
+. "${TEST_DIRECTORY:-.}/test-lib.sh"
+
+GIT_REFS_LIST_CMD=${GIT_REFS_LIST_CMD:-for-each-ref}
 GNUPGHOME_NOT_USED=$GNUPGHOME
 . "$TEST_DIRECTORY"/lib-gpg.sh
 . "$TEST_DIRECTORY"/lib-terminal.sh
@@ -61,7 +63,7 @@ test_atom () {
 
 	printf '%s\n' "$3" >expected
 	$test_do $PREREQ "basic atom: $ref $format" '
-		git for-each-ref --format="%($format)" "$ref" >actual &&
+		git ${GIT_REFS_LIST_CMD} --format="%($format)" "$ref" >actual &&
 		sanitize_pgp <actual >actual.clean &&
 		test_cmp expected actual.clean
 	'
@@ -88,7 +90,7 @@ test_atom () {
 			esac &&
 			# Leave $expect unquoted to lose possible leading whitespaces
 			echo $expect >expected &&
-			git for-each-ref --format="%(contents:size)" "$ref" >actual &&
+			git ${GIT_REFS_LIST_CMD} --format="%(contents:size)" "$ref" >actual &&
 			test_cmp expected actual
 		'
 	fi
@@ -281,7 +283,7 @@ test_atom tag HEAD ' '
 
 test_expect_success 'basic atom: refs/tags/testtag *raw' '
 	git cat-file commit refs/tags/testtag^{} >expected &&
-	git for-each-ref --format="%(*raw)" refs/tags/testtag >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(*raw)" refs/tags/testtag >actual &&
 	sanitize_pgp <expected >expected.clean &&
 	echo >>expected.clean &&
 	sanitize_pgp <actual >actual.clean &&
@@ -289,40 +291,45 @@ test_expect_success 'basic atom: refs/tags/testtag *raw' '
 '
 
 test_expect_success 'Check invalid atoms names are errors' '
-	test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
-'
-
-test_expect_success 'for-each-ref does not crash with -h' '
-	test_expect_code 129 git for-each-ref -h >usage &&
-	test_grep "[Uu]sage: git for-each-ref " usage &&
-	test_expect_code 129 nongit git for-each-ref -h >usage &&
-	test_grep "[Uu]sage: git for-each-ref " usage
-'
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(INVALID)" refs/heads
+'
+
+if test "$GIT_REFS_LIST_CMD" = "refs list"
+then
+	say "Skipping -h crash test for git refs list"
+else
+	test_expect_success "${GIT_REFS_LIST_CMD} does not crash with -h" '
+		test_expect_code 129 git ${GIT_REFS_LIST_CMD} -h >usage &&
+		test_grep "[Uu]sage: git for-each-ref " usage &&
+		test_expect_code 129 nongit git ${GIT_REFS_LIST_CMD} -h >usage &&
+		test_grep "[Uu]sage: git for-each-ref " usage
+	'
+fi
 
 test_expect_success 'Check format specifiers are ignored in naming date atoms' '
-	git for-each-ref --format="%(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate) %(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate:default)" refs/heads
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:default) %(authordate)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate) %(authordate:default)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:default) %(authordate:default)" refs/heads
 '
 
 test_expect_success 'Check valid format specifiers for date fields' '
-	git for-each-ref --format="%(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:relative)" refs/heads &&
-	git for-each-ref --format="%(authordate:short)" refs/heads &&
-	git for-each-ref --format="%(authordate:local)" refs/heads &&
-	git for-each-ref --format="%(authordate:iso8601)" refs/heads &&
-	git for-each-ref --format="%(authordate:rfc2822)" refs/heads
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:default)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:relative)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:short)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:local)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:iso8601)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:rfc2822)" refs/heads
 '
 
 test_expect_success 'Check invalid format specifiers are errors' '
-	test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(authordate:INVALID)" refs/heads
 '
 
 test_expect_success 'arguments to %(objectname:short=) must be positive integers' '
-	test_must_fail git for-each-ref --format="%(objectname:short=0)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=-1)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=foo)"
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(objectname:short=0)" &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(objectname:short=-1)" &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(objectname:short=foo)"
 '
 
 test_bad_atom () {
@@ -337,7 +344,7 @@ test_bad_atom () {
 
 	printf '%s\n' "$3" >expect
 	$test_do $PREREQ "err basic atom: $ref $format" '
-		test_must_fail git for-each-ref \
+		test_must_fail git ${GIT_REFS_LIST_CMD} \
 			--format="%($format)" "$ref" 2>error &&
 		test_cmp expect error
 	'
@@ -395,10 +402,10 @@ test_date () {
 	'refs/tags/testtag' '$tagger_date'
 	EOF
 	(
-		git for-each-ref --shell \
+		git ${GIT_REFS_LIST_CMD} --shell \
 			--format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
 			refs/heads &&
-		git for-each-ref --shell \
+		git ${GIT_REFS_LIST_CMD} --shell \
 			--format="%(refname) %(taggerdate${f:+:$f})" \
 			refs/tags
 	) >actual &&
@@ -429,16 +436,16 @@ test_expect_success 'Check format "default-local" date fields output' '
 # doesn't exit in error
 test_expect_success 'Check format "relative" date fields output' '
 	f=relative &&
-	(git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-	git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
+	(git ${GIT_REFS_LIST_CMD} --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
 '
 
 # We just check that this is the same as "relative" for now.
 test_expect_success 'Check format "relative-local" date fields output' '
 	test_date relative-local \
-		"$(git for-each-ref --format="%(committerdate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(authordate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(taggerdate:relative)" refs/tags)"
+		"$(git ${GIT_REFS_LIST_CMD} --format="%(committerdate:relative)" refs/heads)" \
+		"$(git ${GIT_REFS_LIST_CMD} --format="%(authordate:relative)" refs/heads)" \
+		"$(git ${GIT_REFS_LIST_CMD} --format="%(taggerdate:relative)" refs/tags)"
 '
 
 test_expect_success 'Check format "short" date fields output' '
@@ -488,7 +495,7 @@ test_expect_success 'Check format "raw-local" date fields output' '
 
 test_expect_success 'Check format of strftime date fields' '
 	echo "my date is 2006-07-04" >expected &&
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 	  --format="%(authordate:format:my date is %Y-%m-%d)" \
 	  refs/heads >actual &&
 	test_cmp expected actual
@@ -496,7 +503,7 @@ test_expect_success 'Check format of strftime date fields' '
 
 test_expect_success 'Check format of strftime-local date fields' '
 	echo "my date is 2006-07-03" >expected &&
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 	  --format="%(authordate:format-local:my date is %Y-%m-%d)" \
 	  refs/heads >actual &&
 	test_cmp expected actual
@@ -504,11 +511,11 @@ test_expect_success 'Check format of strftime-local date fields' '
 
 test_expect_success 'exercise strftime with odd fields' '
 	echo >expected &&
-	git for-each-ref --format="%(authordate:format:)" refs/heads >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:format:)" refs/heads >actual &&
 	test_cmp expected actual &&
 	long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" &&
 	echo $long >expected &&
-	git for-each-ref --format="%(authordate:format:$long)" refs/heads >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:format:$long)" refs/heads >actual &&
 	test_cmp expected actual
 '
 
@@ -519,7 +526,7 @@ refs/tags/testtag
 EOF
 
 test_expect_success 'Verify ascending sort' '
-	git for-each-ref --format="%(refname)" --sort=refname >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --sort=refname >actual &&
 	test_cmp expected actual
 '
 
@@ -531,13 +538,13 @@ refs/heads/main
 EOF
 
 test_expect_success 'Verify descending sort' '
-	git for-each-ref --format="%(refname)" --sort=-refname >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --sort=-refname >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'Give help even with invalid sort atoms' '
-	test_expect_code 129 git for-each-ref --sort=bogus -h >actual 2>&1 &&
-	grep "^usage: git for-each-ref" actual
+	test_expect_code 129 git ${GIT_REFS_LIST_CMD} --sort=bogus -h >actual 2>&1 &&
+	grep "^usage: git ${GIT_REFS_LIST_CMD}" actual
 '
 
 cat >expected <<\EOF
@@ -548,7 +555,7 @@ EOF
 test_expect_success 'exercise patterns with prefixes' '
 	git tag testtag-2 &&
 	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" \
 		refs/tags/testtag refs/tags/testtag-2 >actual &&
 	test_cmp expected actual
 '
@@ -561,7 +568,7 @@ EOF
 test_expect_success 'exercise glob patterns with prefixes' '
 	git tag testtag-2 &&
 	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" \
 		refs/tags/testtag "refs/tags/testtag-*" >actual &&
 	test_cmp expected actual
 '
@@ -578,7 +585,7 @@ test_expect_success 'exercise patterns with prefix exclusions' '
 		git tag "$tag" || return 1
 	done &&
 	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" \
 		refs/tags/ --exclude=refs/tags/foo >actual &&
 	test_cmp expected actual
 '
@@ -596,7 +603,7 @@ test_expect_success 'exercise patterns with pattern exclusions' '
 		git tag "$tag" || return 1
 	done &&
 	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" \
 		refs/tags/ --exclude="refs/tags/foo/t*" >actual &&
 	test_cmp expected actual
 '
@@ -608,17 +615,17 @@ cat >expected <<\EOF
 EOF
 
 test_expect_success 'Quoting style: shell' '
-	git for-each-ref --shell --format="%(refname)" >actual &&
+	git ${GIT_REFS_LIST_CMD} --shell --format="%(refname)" >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'Quoting style: perl' '
-	git for-each-ref --perl --format="%(refname)" >actual &&
+	git ${GIT_REFS_LIST_CMD} --perl --format="%(refname)" >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'Quoting style: python' '
-	git for-each-ref --python --format="%(refname)" >actual &&
+	git ${GIT_REFS_LIST_CMD} --python --format="%(refname)" >actual &&
 	test_cmp expected actual
 '
 
@@ -629,13 +636,13 @@ cat >expected <<\EOF
 EOF
 
 test_expect_success 'Quoting style: tcl' '
-	git for-each-ref --tcl --format="%(refname)" >actual &&
+	git ${GIT_REFS_LIST_CMD} --tcl --format="%(refname)" >actual &&
 	test_cmp expected actual
 '
 
 for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
 	test_expect_success "more than one quoting style: $i" "
-		test_must_fail git for-each-ref $i 2>err &&
+		test_must_fail git ${GIT_REFS_LIST_CMD} $i 2>err &&
 		grep '^error: more than one quoting style' err
 	"
 done
@@ -659,8 +666,8 @@ test_atom head push:track '[behind 1]'
 test_atom head push:trackshort '<'
 
 test_expect_success 'Check that :track[short] cannot be used with other atoms' '
-	test_must_fail git for-each-ref --format="%(refname:track)" 2>/dev/null &&
-	test_must_fail git for-each-ref --format="%(refname:trackshort)" 2>/dev/null
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(refname:track)" 2>/dev/null &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(refname:trackshort)" 2>/dev/null
 '
 
 test_expect_success 'Check that :track[short] works when upstream is invalid' '
@@ -670,14 +677,14 @@ test_expect_success 'Check that :track[short] works when upstream is invalid' '
 	EOF
 	test_when_finished "git config branch.main.merge refs/heads/main" &&
 	git config branch.main.merge refs/heads/does-not-exist &&
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(upstream:track)$LF%(upstream:trackshort)" \
 		refs/heads >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'Check for invalid refname format' '
-	test_must_fail git for-each-ref --format="%(refname:INVALID)"
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(refname:INVALID)"
 '
 
 test_expect_success 'set up color tests' '
@@ -694,24 +701,24 @@ test_expect_success 'set up color tests' '
 '
 
 test_expect_success TTY '%(color) shows color with a tty' '
-	test_terminal git for-each-ref --format="$color_format" >actual.raw &&
+	test_terminal git ${GIT_REFS_LIST_CMD} --format="$color_format" >actual.raw &&
 	test_decode_color <actual.raw >actual &&
 	test_cmp expected.color actual
 '
 
 test_expect_success '%(color) does not show color without tty' '
-	TERM=vt100 git for-each-ref --format="$color_format" >actual &&
+	TERM=vt100 git ${GIT_REFS_LIST_CMD} --format="$color_format" >actual &&
 	test_cmp expected.bare actual
 '
 
 test_expect_success '--color can override tty check' '
-	git for-each-ref --color --format="$color_format" >actual.raw &&
+	git ${GIT_REFS_LIST_CMD} --color --format="$color_format" >actual.raw &&
 	test_decode_color <actual.raw >actual &&
 	test_cmp expected.color actual
 '
 
 test_expect_success 'color.ui=always does not override tty check' '
-	git -c color.ui=always for-each-ref --format="$color_format" >actual &&
+	git -c color.ui=always ${GIT_REFS_LIST_CMD} --format="$color_format" >actual &&
 	test_cmp expected.bare actual
 '
 
@@ -732,7 +739,7 @@ test_expect_success 'describe atom vs git describe' '
 	(
 		cd describe-repo &&
 
-		git for-each-ref --format="%(objectname)" \
+		git ${GIT_REFS_LIST_CMD} --format="%(objectname)" \
 			refs/tags/ >obj &&
 		while read hash
 		do
@@ -747,7 +754,7 @@ test_expect_success 'describe atom vs git describe' '
 		test_path_exists expect-contains-good &&
 		test_path_exists expect-contains-bad &&
 
-		git for-each-ref --format="%(objectname) %(describe)" \
+		git ${GIT_REFS_LIST_CMD} --format="%(objectname) %(describe)" \
 			refs/tags/ >actual 2>err &&
 		test_cmp expect actual &&
 		test_must_be_empty err
@@ -758,7 +765,7 @@ test_expect_success 'describe:tags vs describe --tags' '
 	(
 		cd describe-repo &&
 		git describe --tags >expect &&
-		git for-each-ref --format="%(describe:tags)" \
+		git ${GIT_REFS_LIST_CMD} --format="%(describe:tags)" \
 				refs/heads/master >actual &&
 		test_cmp expect actual
 	)
@@ -772,7 +779,7 @@ test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
 		#	  recent tag reachable from it
 		test_commit --no-tag file &&
 		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
+		git ${GIT_REFS_LIST_CMD} --format="%(describe:abbrev=14)" \
 			refs/heads/master >actual &&
 		test_cmp expect actual &&
 
@@ -784,7 +791,7 @@ test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
 		#	  the name of the tag
 		git tag -a -m tagged tagname &&
 		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
+		git ${GIT_REFS_LIST_CMD} --format="%(describe:abbrev=14)" \
 			refs/heads/master >actual &&
 		test_cmp expect actual &&
 		test tagname = $(cat actual)
@@ -796,7 +803,7 @@ test_expect_success 'describe:match=... vs describe --match ...' '
 		cd describe-repo &&
 		git tag -a -m "tag foo" tag-foo &&
 		git describe --match "*-foo" >expect &&
-		git for-each-ref --format="%(describe:match="*-foo")" \
+		git ${GIT_REFS_LIST_CMD} --format="%(describe:match="*-foo")" \
 			refs/heads/master >actual &&
 		test_cmp expect actual
 	)
@@ -807,7 +814,7 @@ test_expect_success 'describe:exclude:... vs describe --exclude ...' '
 		cd describe-repo &&
 		git tag -a -m "tag bar" tag-bar &&
 		git describe --exclude "*-bar" >expect &&
-		git for-each-ref --format="%(describe:exclude="*-bar")" \
+		git ${GIT_REFS_LIST_CMD} --format="%(describe:exclude="*-bar")" \
 			refs/heads/master >actual &&
 		test_cmp expect actual
 	)
@@ -824,7 +831,7 @@ test_expect_success 'deref with describe atom' '
 
 		tagtwo
 		EOF
-		git for-each-ref --format="%(*describe)" >actual &&
+		git ${GIT_REFS_LIST_CMD} --format="%(*describe)" >actual &&
 		test_cmp expect actual
 	)
 '
@@ -837,7 +844,7 @@ test_expect_success 'err on bad describe atom arg' '
 		cat >expect <<-\EOF &&
 		fatal: unrecognized %(describe) argument: baz
 		EOF
-		test_must_fail git for-each-ref --format="%(describe:baz)" \
+		test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(describe:baz)" \
 			refs/heads/master 2>actual &&
 		test_cmp expect actual &&
 
@@ -846,7 +853,7 @@ test_expect_success 'err on bad describe atom arg' '
 		cat >expect <<-\EOF &&
 		fatal: unrecognized %(describe) argument: qux=1,abbrev=14
 		EOF
-		test_must_fail git for-each-ref \
+		test_must_fail git ${GIT_REFS_LIST_CMD} \
 			--format="%(describe:tags,qux=1,abbrev=14)" \
 			ref/heads/master 2>actual &&
 		test_cmp expect actual
@@ -866,7 +873,7 @@ test_expect_success 'Check ambiguous head and tag refs (strict)' '
 	git commit -m "Branch" &&
 	setdate_and_increment &&
 	git tag -m "Tagging at $datestamp" main &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	git ${GIT_REFS_LIST_CMD} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
 	test_cmp expected actual
 '
 
@@ -877,7 +884,7 @@ EOF
 
 test_expect_success 'Check ambiguous head and tag refs (loose)' '
 	git config --bool core.warnambiguousrefs false &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	git ${GIT_REFS_LIST_CMD} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
 	test_cmp expected actual
 '
 
@@ -890,7 +897,7 @@ test_expect_success 'Check ambiguous head and tag refs II (loose)' '
 	git checkout main &&
 	git tag ambiguous testtag^0 &&
 	git branch ambiguous testtag^0 &&
-	git for-each-ref --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
+	git ${GIT_REFS_LIST_CMD} --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
 	test_cmp expected actual
 '
 
@@ -923,7 +930,7 @@ test_expect_success 'an unusual tag with an incomplete line' '
 	bogo=$(git cat-file tag bogo) &&
 	bogo=$(printf "%s" "$bogo" | git mktag) &&
 	git tag -f bogo "$bogo" &&
-	git for-each-ref --format "%(body)" refs/tags/bogo
+	git ${GIT_REFS_LIST_CMD} --format "%(body)" refs/tags/bogo
 
 '
 
@@ -1000,7 +1007,7 @@ test_atom refs/tags/signed-empty contents "$sig"
 
 test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
 	git cat-file tag refs/tags/signed-empty >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-empty >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw)" refs/tags/signed-empty >actual &&
 	sanitize_pgp <expected >expected.clean &&
 	echo >>expected.clean &&
 	sanitize_pgp <actual >actual.clean &&
@@ -1018,7 +1025,7 @@ $sig"
 
 test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
 	git cat-file tag refs/tags/signed-short >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-short >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw)" refs/tags/signed-short >actual &&
 	sanitize_pgp <expected >expected.clean &&
 	echo >>expected.clean &&
 	sanitize_pgp <actual >actual.clean &&
@@ -1040,7 +1047,7 @@ $sig"
 
 test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
 	git cat-file tag refs/tags/signed-long >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-long >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw)" refs/tags/signed-long >actual &&
 	sanitize_pgp <expected >expected.clean &&
 	echo >>expected.clean &&
 	sanitize_pgp <actual >actual.clean &&
@@ -1062,10 +1069,10 @@ test_atom refs/mytrees/first contents ""
 test_expect_success 'basic atom: refs/mytrees/first raw' '
 	git cat-file tree refs/mytrees/first >expected &&
 	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/mytrees/first >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw)" refs/mytrees/first >actual &&
 	test_cmp expected actual &&
 	git cat-file -s refs/mytrees/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/mytrees/first >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw:size)" refs/mytrees/first >actual &&
 	test_cmp expected actual
 '
 
@@ -1079,10 +1086,10 @@ test_atom refs/myblobs/first contents ""
 test_expect_success 'basic atom: refs/myblobs/first raw' '
 	git cat-file blob refs/myblobs/first >expected &&
 	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/myblobs/first >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw)" refs/myblobs/first >actual &&
 	test_cmp expected actual &&
 	git cat-file -s refs/myblobs/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/myblobs/first >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw:size)" refs/myblobs/first >actual &&
 	test_cmp expected actual
 '
 
@@ -1127,7 +1134,7 @@ test_expect_success 'Verify sorts with raw' '
 	refs/myblobs/blob4
 	refs/heads/main
 	EOF
-	git for-each-ref --format="%(refname)" --sort=raw \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --sort=raw \
 		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
 	test_cmp expected actual
 '
@@ -1146,7 +1153,7 @@ test_expect_success 'Verify sorts with raw:size' '
 	refs/mytrees/first
 	refs/heads/main
 	EOF
-	git for-each-ref --format="%(refname)" --sort=raw:size \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --sort=raw:size \
 		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
 	test_cmp expected actual
 '
@@ -1166,7 +1173,7 @@ test_expect_success 'validate raw atom with %(if:equals)' '
 	not equals
 	not equals
 	EOF
-	git for-each-ref --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
 		refs/myblobs/ refs/heads/ >actual &&
 	test_cmp expected actual
 '
@@ -1186,7 +1193,7 @@ test_expect_success 'validate raw atom with %(if:notequals)' '
 	refs/myblobs/blob8
 	refs/myblobs/first
 	EOF
-	git for-each-ref --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
 		refs/myblobs/ refs/heads/ >actual &&
 	test_cmp expected actual
 '
@@ -1203,55 +1210,55 @@ test_expect_success 'empty raw refs with %(if)' '
 	refs/myblobs/blob8 empty
 	refs/myblobs/first not empty
 	EOF
-	git for-each-ref --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
 		refs/myblobs/ >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success '%(raw) with --python must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --python
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(raw)" --python
 '
 
 test_expect_success '%(raw) with --tcl must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --tcl
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(raw)" --tcl
 '
 
 test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' '
-	git for-each-ref --format="\$name= %(raw);
+	git ${GIT_REFS_LIST_CMD} --format="\$name= %(raw);
 print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
 	cmp blob1 actual &&
-	git for-each-ref --format="\$name= %(raw);
+	git ${GIT_REFS_LIST_CMD} --format="\$name= %(raw);
 print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
 	cmp blob3 actual &&
-	git for-each-ref --format="\$name= %(raw);
+	git ${GIT_REFS_LIST_CMD} --format="\$name= %(raw);
 print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
 	cmp blob8 actual &&
-	git for-each-ref --format="\$name= %(raw);
+	git ${GIT_REFS_LIST_CMD} --format="\$name= %(raw);
 print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
 	cmp one actual &&
 	git cat-file tree refs/mytrees/first > expected &&
-	git for-each-ref --format="\$name= %(raw);
+	git ${GIT_REFS_LIST_CMD} --format="\$name= %(raw);
 print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
 	cmp expected actual
 '
 
 test_expect_success '%(raw) with --shell must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --shell
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(raw)" --shell
 '
 
 test_expect_success '%(raw) with --shell and --sort=raw must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --sort=raw --shell
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(raw)" --sort=raw --shell
 '
 
 test_expect_success '%(raw:size) with --shell' '
-	git for-each-ref --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
-	git for-each-ref --format="%(raw:size)" --shell >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw:size)" --shell >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'for-each-ref --format compare with cat-file --batch' '
+test_expect_success "${GIT_REFS_LIST_CMD} --format compare with cat-file --batch" '
 	git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
-	git for-each-ref --format="%(objectname) %(objecttype) %(objectsize)
+	git ${GIT_REFS_LIST_CMD} --format="%(objectname) %(objecttype) %(objectsize)
 %(raw)" refs/mytrees/first >actual &&
 	test_cmp expected actual
 '
@@ -1262,7 +1269,7 @@ test_expect_success 'verify sorts with contents:size' '
 	refs/heads/newtag
 	refs/heads/ambiguous
 	EOF
-	git for-each-ref --format="%(refname)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" \
 		--sort=contents:size refs/heads/ >actual &&
 	test_cmp expect actual
 '
@@ -1294,7 +1301,7 @@ test_expect_success 'Verify sort with multiple keys' '
 	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
 	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
 	EOF
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
 		--sort=-refname \
 		--sort=taggeremail \
@@ -1314,7 +1321,7 @@ test_expect_success 'equivalent sorts fall back on refname' '
 	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
 	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
 	EOF
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
 		--sort=taggerdate \
 		"refs/tags/multi-*" >actual &&
@@ -1332,7 +1339,7 @@ test_expect_success '--no-sort cancels the previous sort keys' '
 	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
 	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
 	EOF
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
 		--sort=-refname \
 		--sort=taggeremail \
@@ -1356,7 +1363,7 @@ test_expect_success '--no-sort without subsequent --sort prints expected refs' '
 
 	# Sort the results with `sort` for a consistent comparison against
 	# expected
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(refname)" \
 		--no-sort \
 		"refs/tags/multi-*" | sort >actual &&
@@ -1387,7 +1394,7 @@ test_expect_success 'sort by date defaults to full timestamp' '
 	1707341660 refs/tags/custom-dates-1
 	EOF
 
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(creatordate:unix) %(refname)" \
 		--sort=creatordate \
 		"refs/tags/custom-dates-*" >actual &&
@@ -1402,7 +1409,7 @@ test_expect_success 'sort by custom date format' '
 	21:34:20 refs/tags/custom-dates-1
 	EOF
 
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(creatordate:format:%H:%M:%S) %(refname)" \
 		--sort="creatordate:format:%H:%M:%S" \
 		"refs/tags/custom-dates-*" >actual &&
@@ -1411,10 +1418,10 @@ test_expect_success 'sort by custom date format' '
 
 test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
 	test_when_finished "git checkout main" &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
 	sed -e "s/^\* /  /" actual >expect &&
 	git checkout --orphan orphaned-branch &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
 	test_cmp expect actual
 '
 
@@ -1451,9 +1458,9 @@ test_trailer_option () {
 	title=$1 option=$2
 	cat >expect
 	test_expect_success $prereq "$title" '
-		git for-each-ref --format="%($option)" refs/heads/main >actual &&
+		git ${GIT_REFS_LIST_CMD} --format="%($option)" refs/heads/main >actual &&
 		test_cmp expect actual &&
-		git for-each-ref --format="%(contents:$option)" refs/heads/main >actual &&
+		git ${GIT_REFS_LIST_CMD} --format="%(contents:$option)" refs/heads/main >actual &&
 		test_cmp expect actual
 	'
 }
@@ -1583,7 +1590,7 @@ test_expect_success 'multiple %(trailers) use their own options' '
 	EOF
 	t1="%(trailers:key=one,key_value_separator=W,separator=X)" &&
 	t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" &&
-	git for-each-ref --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
 	cat >expect <<-\EOF &&
 	oneWfooXoneWbar
 	twoYbazZtwoYqux
@@ -1596,9 +1603,9 @@ test_failing_trailer_option () {
 	cat >expect
 	test_expect_success "$title" '
 		# error message cannot be checked under i18n
-		test_must_fail git for-each-ref --format="%($option)" refs/heads/main 2>actual &&
+		test_must_fail git ${GIT_REFS_LIST_CMD} --format="%($option)" refs/heads/main 2>actual &&
 		test_cmp expect actual &&
-		test_must_fail git for-each-ref --format="%(contents:$option)" refs/heads/main 2>actual &&
+		test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(contents:$option)" refs/heads/main 2>actual &&
 		test_cmp expect actual
 	'
 }
@@ -1617,14 +1624,14 @@ test_expect_success 'if arguments, %(contents:trailers) shows error if colon is
 	cat >expect <<-EOF &&
 	fatal: unrecognized %(contents) argument: trailersonly
 	EOF
-	test_must_fail git for-each-ref --format="%(contents:trailersonly)" 2>actual &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(contents:trailersonly)" 2>actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'basic atom: head contents:trailers' '
-	git for-each-ref --format="%(contents:trailers)" refs/heads/main >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(contents:trailers)" refs/heads/main >actual &&
 	sanitize_pgp <actual >actual.clean &&
-	# git for-each-ref ends with a blank line
+	# git ${GIT_REFS_LIST_CMD} ends with a blank line
 	cat >expect <<-EOF &&
 	$(cat trailers)
 
@@ -1633,23 +1640,23 @@ test_expect_success 'basic atom: head contents:trailers' '
 '
 
 test_expect_success 'basic atom: rest must fail' '
-	test_must_fail git for-each-ref --format="%(rest)" refs/heads/main
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(rest)" refs/heads/main
 '
 
 test_expect_success 'HEAD atom does not take arguments' '
-	test_must_fail git for-each-ref --format="%(HEAD:foo)" 2>err &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(HEAD:foo)" 2>err &&
 	echo "fatal: %(HEAD) does not take arguments" >expect &&
 	test_cmp expect err
 '
 
 test_expect_success 'subject atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(subject:foo)" 2>err &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(subject:foo)" 2>err &&
 	echo "fatal: unrecognized %(subject) argument: foo" >expect &&
 	test_cmp expect err
 '
 
 test_expect_success 'refname atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(refname:foo)" 2>err &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(refname:foo)" 2>err &&
 	echo "fatal: unrecognized %(refname) argument: foo" >expect &&
 	test_cmp expect err
 '
@@ -1673,7 +1680,7 @@ test_expect_success 'trailer parsing not fooled by --- line' '
 		echo "trailer: right" &&
 		echo
 	} >expect &&
-	git for-each-ref --format="%(trailers)" refs/heads/main >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(trailers)" refs/heads/main >actual &&
 	test_cmp expect actual
 '
 
@@ -1686,7 +1693,7 @@ refs/heads/main
 EOF
 
 test_expect_success 'Verify usage of %(symref) atom' '
-	git for-each-ref --format="%(symref)" refs/heads/sym >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref)" refs/heads/sym >actual &&
 	test_cmp expected actual
 '
 
@@ -1695,7 +1702,7 @@ heads/main
 EOF
 
 test_expect_success 'Verify usage of %(symref:short) atom' '
-	git for-each-ref --format="%(symref:short)" refs/heads/sym >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:short)" refs/heads/sym >actual &&
 	test_cmp expected actual
 '
 
@@ -1705,12 +1712,12 @@ heads/main
 EOF
 
 test_expect_success 'Verify usage of %(symref:lstrip) atom' '
-	git for-each-ref --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
 	test_cmp expected actual &&
 
-	git for-each-ref --format="%(symref:strip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:strip=2)" refs/heads/sym > actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
 	test_cmp expected actual
 '
 
@@ -1720,8 +1727,8 @@ refs/heads
 EOF
 
 test_expect_success 'Verify usage of %(symref:rstrip) atom' '
-	git for-each-ref --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
 	test_cmp expected actual
 '
 
@@ -1745,38 +1752,38 @@ test_expect_success ':remotename and :remoteref' '
 			"%(push:remoteref)=refs/heads/pushed/main"
 		do
 			echo "${pair#*=}" >expect &&
-			git for-each-ref --format="${pair%=*}" \
+			git ${GIT_REFS_LIST_CMD} --format="${pair%=*}" \
 				refs/heads/main >actual &&
 			test_cmp expect actual || exit 1
 		done &&
 		git branch push-simple &&
 		git config branch.push-simple.pushRemote from &&
-		actual="$(git for-each-ref \
+		actual="$(git ${GIT_REFS_LIST_CMD} \
 			--format="%(push:remotename),%(push:remoteref)" \
 			refs/heads/push-simple)" &&
 		test from, = "$actual"
 	)
 '
 
-test_expect_success 'for-each-ref --ignore-case ignores case' '
-	git for-each-ref --format="%(refname)" refs/heads/MAIN >actual &&
+test_expect_success "${GIT_REFS_LIST_CMD} --ignore-case ignores case" '
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" refs/heads/MAIN >actual &&
 	test_must_be_empty actual &&
 
 	echo refs/heads/main >expect &&
-	git for-each-ref --format="%(refname)" --ignore-case \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --ignore-case \
 		refs/heads/MAIN >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'for-each-ref --omit-empty works' '
-	git for-each-ref --format="%(refname)" >actual &&
+test_expect_success "${GIT_REFS_LIST_CMD} --omit-empty works" '
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" >actual &&
 	test_line_count -gt 1 actual &&
-	git for-each-ref --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
 	echo refs/heads/main >expect &&
 	test_cmp expect actual
 '
 
-test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
+test_expect_success "${GIT_REFS_LIST_CMD} --ignore-case works on multiple sort keys" '
 	# name refs numerically to avoid case-insensitive filesystem conflicts
 	nr=0 &&
 	for email in a A b B
@@ -1789,7 +1796,7 @@ test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
 			return 1
 		done
 	done &&
-	git for-each-ref --ignore-case \
+	git ${GIT_REFS_LIST_CMD} --ignore-case \
 		--format="%(taggeremail) %(subject) %(refname)" \
 		--sort=refname \
 		--sort=subject \
@@ -1816,13 +1823,13 @@ test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
 	test_cmp expect actual
 '
 
-test_expect_success 'for-each-ref reports broken tags' '
+test_expect_success "${GIT_REFS_LIST_CMD} reports broken tags" '
 	git tag -m "good tag" broken-tag-good HEAD &&
 	git cat-file tag broken-tag-good >good &&
 	sed s/commit/blob/ <good >bad &&
 	bad=$(git hash-object -w -t tag bad) &&
 	git update-ref refs/tags/broken-tag-bad $bad &&
-	test_must_fail git for-each-ref --format="%(*objectname)" \
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(*objectname)" \
 		refs/tags/broken-tag-*
 '
 
@@ -1884,21 +1891,21 @@ test_expect_success 'set up tag with signature and trailers' '
 # use "separator=" here to suppress the terminating newline
 test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo'
 
-test_expect_success 'git for-each-ref --stdin: empty' '
+test_expect_success "git ${GIT_REFS_LIST_CMD} --stdin: empty" '
 	>in &&
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	git for-each-ref --format="%(refname)" >expect &&
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --stdin <in >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" >expect &&
 	test_cmp expect actual
 '
 
-test_expect_success 'git for-each-ref --stdin: fails if extra args' '
+test_expect_success "git ${GIT_REFS_LIST_CMD} --stdin: fails if extra args" '
 	>in &&
-	test_must_fail git for-each-ref --format="%(refname)" \
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(refname)" \
 		--stdin refs/heads/extra <in 2>err &&
 	grep "unknown arguments supplied with --stdin" err
 '
 
-test_expect_success 'git for-each-ref --stdin: matches' '
+test_expect_success "git ${GIT_REFS_LIST_CMD} --stdin: matches" '
 	cat >in <<-EOF &&
 	refs/tags/multi*
 	refs/heads/amb*
@@ -1917,24 +1924,24 @@ test_expect_success 'git for-each-ref --stdin: matches' '
 	refs/tags/multiline
 	EOF
 
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --stdin <in >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'git for-each-ref with non-existing refs' '
+test_expect_success "git ${GIT_REFS_LIST_CMD} with non-existing refs" '
 	cat >in <<-EOF &&
 	refs/heads/this-ref-does-not-exist
 	refs/tags/bogus
 	EOF
 
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --stdin <in >actual &&
 	test_must_be_empty actual &&
 
-	xargs git for-each-ref --format="%(refname)" <in >actual &&
+	xargs git ${GIT_REFS_LIST_CMD} --format="%(refname)" <in >actual &&
 	test_must_be_empty actual
 '
 
-test_expect_success 'git for-each-ref with nested tags' '
+test_expect_success "git ${GIT_REFS_LIST_CMD} with nested tags" '
 	git tag -am "Normal tag" nested/base HEAD &&
 	git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
 	git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
@@ -1950,14 +1957,14 @@ test_expect_success 'git for-each-ref with nested tags' '
 	refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
 	EOF
 
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
 		refs/tags/nested/ >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'is-base atom with non-commits' '
-	git for-each-ref --format="%(is-base:HEAD) %(refname)" >out 2>err &&
+	git ${GIT_REFS_LIST_CMD} --format="%(is-base:HEAD) %(refname)" >out 2>err &&
 	grep "(HEAD) refs/heads/main" out &&
 
 	test_line_count = 2 err &&
@@ -2034,7 +2041,7 @@ test_expect_success GPGSSH 'setup for signature atom using ssh' '
 test_expect_success GPG2 'bare signature atom' '
 	git verify-commit first-signed 2>expect &&
 	echo  >>expect &&
-	git for-each-ref refs/tags/first-signed \
+	git ${GIT_REFS_LIST_CMD} refs/tags/first-signed \
 		--format="%(signature)" >actual &&
 	test_cmp expect actual
 '
@@ -2048,7 +2055,7 @@ test_expect_success GPG 'show good signature with custom format' '
 	73D758744BE721698EC54E8713B6F51ECDDE430D
 	73D758744BE721698EC54E8713B6F51ECDDE430D
 	EOF
-	git for-each-ref refs/tags/first-signed \
+	git ${GIT_REFS_LIST_CMD} refs/tags/first-signed \
 		--format="$GRADE_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2063,7 +2070,7 @@ test_expect_success GPGSSH 'show good signature with custom format with ssh' '
 
 	EOF
 	sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
-	git for-each-ref refs/tags/eighth-signed-ssh \
+	git ${GIT_REFS_LIST_CMD} refs/tags/eighth-signed-ssh \
 		--format="$GRADE_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2082,7 +2089,7 @@ test_expect_success GPG 'signature atom with grade option and bad signature' '
 
 
 	EOF
-	git for-each-ref refs/tags/third-signed \
+	git ${GIT_REFS_LIST_CMD} refs/tags/third-signed \
 		--format="$GRADE_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2095,7 +2102,7 @@ test_expect_success GPG 'show untrusted signature with custom format' '
 	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
 	D4BE22311AD3131E5EDA29A461092E85B7227189
 	EOF
-	git for-each-ref refs/tags/fourth-signed \
+	git ${GIT_REFS_LIST_CMD} refs/tags/fourth-signed \
 		--format="$GRADE_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2108,7 +2115,7 @@ test_expect_success GPG 'show untrusted signature with undefined trust level' '
 	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
 	D4BE22311AD3131E5EDA29A461092E85B7227189
 	EOF
-	git for-each-ref refs/tags/fourth-signed \
+	git ${GIT_REFS_LIST_CMD} refs/tags/fourth-signed \
 		--format="$TRUSTLEVEL_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2121,7 +2128,7 @@ test_expect_success GPG 'show untrusted signature with ultimate trust level' '
 	73D758744BE721698EC54E8713B6F51ECDDE430D
 	73D758744BE721698EC54E8713B6F51ECDDE430D
 	EOF
-	git for-each-ref refs/tags/sixth-signed \
+	git ${GIT_REFS_LIST_CMD} refs/tags/sixth-signed \
 		--format="$TRUSTLEVEL_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2134,7 +2141,7 @@ test_expect_success GPG 'show unknown signature with custom format' '
 
 
 	EOF
-	GNUPGHOME="$GNUPGHOME_NOT_USED" git for-each-ref \
+	GNUPGHOME="$GNUPGHOME_NOT_USED" git ${GIT_REFS_LIST_CMD} \
 		refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2147,7 +2154,7 @@ test_expect_success GPG 'show lack of signature with custom format' '
 
 
 	EOF
-	git for-each-ref refs/tags/seventh-unsigned \
+	git ${GIT_REFS_LIST_CMD} refs/tags/seventh-unsigned \
 		--format="$GRADE_FORMAT" >actual &&
 	test_cmp expect actual
 '
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH 1/2] builtin/refs: add list subcommand
  2025-06-27  7:49 ` [GSoC][RFC PATCH 1/2] builtin/refs: add " Meet Soni
@ 2025-06-27 16:27   ` Jean-Noël Avila
  2025-06-27 18:13     ` Junio C Hamano
  2025-06-30  4:28     ` Meet Soni
  2025-06-29 11:05   ` [PATCH] doc:git-for-each-ref: fix styling and typos Jean-Noël Avila
  1 sibling, 2 replies; 65+ messages in thread
From: Jean-Noël Avila @ 2025-06-27 16:27 UTC (permalink / raw)
  To: Meet Soni, git; +Cc: ps, shejialuo, karthik.188, Junio C Hamano, John Cai
Hello,
I'm only focusing on the documentation part.
Le 27/06/2025 à 09:49, Meet Soni a écrit :
>
> ---
>  Documentation/git-refs.adoc |  95 +++++++++++++++++++++++++++++++
>  builtin/refs.c              | 110 ++++++++++++++++++++++++++++++++++++
>  2 files changed, 205 insertions(+)
> 
> diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
> index 4d6dc994f9..d8f81eaabd 100644
> --- a/Documentation/git-refs.adoc
> +++ b/Documentation/git-refs.adoc
> @@ -11,6 +11,13 @@ SYNOPSIS
>  [synopsis]
>  git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
>  git refs verify [--strict] [--verbose]
> +git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
please use spaces around '|' in alternative options:
[--shell | --perl | --python | --tcl]
> +	      [(--sort=<key>)...] [--format=<format>]
This "[(--sort=<key>)...]" form is new to me. It abides by the synopsis
syntax. As I understand it, the user can specify a number of
--sort=<key> on the command line.
From the description below, it seems that for --format, the actual
syntax should be:
[--format[=<format>]]
> +	      [--include-root-refs] [ --stdin | <pattern>... ]
Here no spaces, after '[' or before ']':
[--stdin | <pattern>...]
For syntax precedence, we have not documented anything, but the obvious
meaning is "--stdin or a number of <pattern>. OK
> +	      [--points-at=<object>]
> +	      [--merged[=<object>]] [--no-merged[=<object>]]
> +	      [--contains[=<object>]] [--no-contains[=<object>]]
> +	      [--exclude=<pattern> ...]
What are the ... meaning here ? Is it --exclude= followed by a number of
<pattern>? What is the separator then?
Or is it like for --sort?
Maybe use another placeholder name to differentiate from the <pattern>
alternative to --stdin
>  
>  DESCRIPTION
>  -----------
> @@ -26,6 +33,11 @@ migrate::
>  verify::
>  	Verify reference database consistency.
>  
> +list::
OK, synopsis is used in the first part, but this man page has not been
fully converted. Let's stick to the previous style.
> +	List references in the repository with support for filtering, formatting,
> +	and sorting. This subcommand uses the same core logic as
> +	linkgit:git-for-each-ref[1] and offers equivalent functionality.
> +
>  OPTIONS
>  -------
>  
> @@ -57,6 +69,89 @@ The following options are specific to 'git refs verify':
>  --verbose::
>  	When verifying the reference database consistency, be chatty.
>  
> +The following options are specific to 'git refs list':
> +
> +<pattern>...::
> +	If one or more patterns are given, only refs are shown that
> +	match against at least one pattern, either using fnmatch(3) or
> +	literally, in the latter case matching completely or from the
> +	beginning up to a slash.
> +> +--stdin::
> +	If `--stdin` is supplied, then the list of patterns is read from
> +	standard input instead of from the argument list.> +
> +--count=<count>::
> +	By default the command shows all refs that match
> +	`<pattern>`.  This option makes it stop after showing
> +	that many refs.
Please revert the sentences and remove the "this option makes it". The
option description should state first the action of the option, then
talk about default values, behavior, ...
> +
> +--sort=<key>::
> +	A field name to sort on.  Prefix `-` to sort in
Use a verb in imperative mood as much as possible.
> +	descending order of the value.  When unspecified,
> +	`refname` is used.  You may use the --sort=<key> option
> +	multiple times, in which case the last key becomes the primary
> +	key.
> +
> +--format=<format>::
Cite the form with optional param
> +	A string that interpolates `%(fieldname)` from a ref being shown and
> +	the object it points at. In addition, the string literal `%%`
> +	renders as `%` and `%xx` - where `xx` are hex digits - renders as
> +	the character with hex code `xx`. For example, `%00` interpolates to
the ASCII character only I guess: encoding matters here as we are
usually in UTF-8.
> +	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
> ++
> +When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
> +TAB %(refname)`.
> +
> +--color[=<when>]::
> +	Respect any colors specified in the `--format` option. The
> +	`<when>` field must be one of `always`, `never`, or `auto` (if
> +	`<when>` is absent, behave as if `always` was given).
> +
> +--shell::
> +--perl::
> +--python::
> +--tcl::
> +	If given, strings that substitute `%(fieldname)`
Personal taste: "If given" is useless here.
> +	placeholders are quoted as string literals suitable for
> +	the specified host language.  This is meant to produce
> +	a scriptlet that can directly be `eval`ed.
> +
> +--points-at=<object>::
> +	Only list refs which points at the given object.
> +
> +--merged[=<object>]::
> +	Only list refs whose tips are reachable from the
> +	specified commit (HEAD if not specified).
> +
> +--no-merged[=<object>]::
> +	Only list refs whose tips are not reachable from the
> +	specified commit (HEAD if not specified).
> +
> +--contains[=<object>]::
> +	Only list refs which contain the specified commit (HEAD if not
> +	specified).
> +
> +--no-contains[=<object>]::
> +	Only list refs which don't contain the specified commit (HEAD
> +	if not specified).
> +
> +--ignore-case::
> +	Sorting and filtering refs are case insensitive.
> +
> +--omit-empty::
> +	Do not print a newline after formatted refs where the format expands
> +	to the empty string.
> +
> +--exclude=<pattern>::
> +	If one or more patterns are given, only refs which do not match
> +	any excluded pattern(s) are shown. Matching is done using the
> +	same rules as `<pattern>` above.
> +
> +--include-root-refs::
> +	List root refs (HEAD and pseudorefs) apart from regular refs.
The description seems a bit off. Did you really mean "apart from", not
"along with"?
Thanks
Jean-Noël
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH 0/2] Add refs list subcommand
  2025-06-27  7:49 [GSoC][RFC PATCH 0/2] Add refs list subcommand Meet Soni
  2025-06-27  7:49 ` [GSoC][RFC PATCH 1/2] builtin/refs: add " Meet Soni
  2025-06-27  7:49 ` [GSoC][RFC PATCH 2/2] t: add test for git refs list subcommand Meet Soni
@ 2025-06-27 18:03 ` Junio C Hamano
  2025-06-28  8:05   ` shejialuo
  2025-06-30  3:53   ` Meet Soni
  2025-07-17  7:50 ` [GSoC][RFC PATCH v2 " Meet Soni
  3 siblings, 2 replies; 65+ messages in thread
From: Junio C Hamano @ 2025-06-27 18:03 UTC (permalink / raw)
  To: Meet Soni; +Cc: git, ps, shejialuo, karthik.188
Meet Soni <meetsoni3017@gmail.com> writes:
>   - Remove legacy or obscure options that are no longer needed.
Such as?
>   - Add improvements that wouldn't make sense to bolt onto legacy
>     commands.
Such as?
While I agree that there may be cases that the above goals in
general would bring us improvements, I think neither of these two
applies to for-each-ref.  People are using for-each-ref to iterate
over and enumerate refs already, and if you are to add some new
features to "git refs list", they certainly will demand these new
goodies to be added to for-each-ref as well.
So, I dunno.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH 1/2] builtin/refs: add list subcommand
  2025-06-27 16:27   ` Jean-Noël Avila
@ 2025-06-27 18:13     ` Junio C Hamano
  2025-06-30  4:28     ` Meet Soni
  1 sibling, 0 replies; 65+ messages in thread
From: Junio C Hamano @ 2025-06-27 18:13 UTC (permalink / raw)
  To: Jean-Noël Avila; +Cc: Meet Soni, git, ps, shejialuo, karthik.188, John Cai
Jean-Noël Avila <jn.avila@free.fr> writes:
> Hello,
>
> I'm only focusing on the documentation part.
>
> Le 27/06/2025 à 09:49, Meet Soni a écrit :
>>
>> ---
>>  Documentation/git-refs.adoc |  95 +++++++++++++++++++++++++++++++
>>  builtin/refs.c              | 110 ++++++++++++++++++++++++++++++++++++
>>  2 files changed, 205 insertions(+)
>> 
>> diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
>> index 4d6dc994f9..d8f81eaabd 100644
>> --- a/Documentation/git-refs.adoc
>> +++ b/Documentation/git-refs.adoc
>> @@ -11,6 +11,13 @@ SYNOPSIS
>>  [synopsis]
>>  git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
>>  git refs verify [--strict] [--verbose]
>> +git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
>
> please use spaces around '|' in alternative options:
> [--shell | --perl | --python | --tcl]
As this seems to be copied and pasted with minimum modification,
perhaps we are better off if you sent a "here is how you should do
it" patch against Documentation/git-for-each-ref.adoc file where
this was copied from.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH 0/2] Add refs list subcommand
  2025-06-27 18:03 ` [GSoC][RFC PATCH 0/2] Add " Junio C Hamano
@ 2025-06-28  8:05   ` shejialuo
  2025-06-30 14:05     ` Junio C Hamano
  2025-06-30  3:53   ` Meet Soni
  1 sibling, 1 reply; 65+ messages in thread
From: shejialuo @ 2025-06-28  8:05 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Meet Soni, git, ps, karthik.188
On Fri, Jun 27, 2025 at 11:03:13AM -0700, Junio C Hamano wrote:
> Meet Soni <meetsoni3017@gmail.com> writes:
> 
> >   - Remove legacy or obscure options that are no longer needed.
> 
> Such as?
> 
> >   - Add improvements that wouldn't make sense to bolt onto legacy
> >     commands.
> 
> Such as?
> 
> While I agree that there may be cases that the above goals in
> general would bring us improvements, I think neither of these two
> applies to for-each-ref.  People are using for-each-ref to iterate
> over and enumerate refs already, and if you are to add some new
> features to "git refs list", they certainly will demand these new
> goodies to be added to for-each-ref as well.
> 
If so, we would make "git refs list" to place "git for-each-ref" at
all. However, in the current implementation, we indeed introduce
duplicate code path if we decide to do above.
Thanks,
Jialuo
^ permalink raw reply	[flat|nested] 65+ messages in thread
* [PATCH] doc:git-for-each-ref: fix styling and typos
  2025-06-27  7:49 ` [GSoC][RFC PATCH 1/2] builtin/refs: add " Meet Soni
  2025-06-27 16:27   ` Jean-Noël Avila
@ 2025-06-29 11:05   ` Jean-Noël Avila
  2025-06-30 15:48     ` Junio C Hamano
  1 sibling, 1 reply; 65+ messages in thread
From: Jean-Noël Avila @ 2025-06-29 11:05 UTC (permalink / raw)
  To: Meet Soni, git, ps, shejialuo; +Cc: Jean-Noël Avila
This commit fixes the synopsis syntax writing and changes the wording of a few
descriptions to be more consistent with the rest of the documentation.
Signed-off-by: Jean-Noël Avila <jn.avila@free.fr>
---
 Documentation/git-for-each-ref.adoc | 30 ++++++++++++++---------------
 1 file changed, 14 insertions(+), 16 deletions(-)
diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc
index 5ef89fc0fe..c2b2660771 100644
--- a/Documentation/git-for-each-ref.adoc
+++ b/Documentation/git-for-each-ref.adoc
@@ -8,13 +8,13 @@ git-for-each-ref - Output information on each ref
 SYNOPSIS
 --------
 [verse]
-'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
-		   [(--sort=<key>)...] [--format=<format>]
-		   [--include-root-refs] [ --stdin | <pattern>... ]
+'git for-each-ref' [--count=<count>] [--shell | --perl | --python | --tcl]
+		   [(--sort=<key>)...] [--format[=<format>]]
+		   [--include-root-refs] [--stdin | <pattern>...]
 		   [--points-at=<object>]
 		   [--merged[=<object>]] [--no-merged[=<object>]]
 		   [--contains[=<object>]] [--no-contains[=<object>]]
-		   [--exclude=<pattern> ...]
+		   [(--exclude=<excluded-pattern>)...]
 
 DESCRIPTION
 -----------
@@ -35,13 +35,11 @@ OPTIONS
 	beginning up to a slash.
 
 --stdin::
-	If `--stdin` is supplied, then the list of patterns is read from
-	standard input instead of from the argument list.
+	The list of patterns is read from standard input instead of from
+	the argument list.
 
 --count=<count>::
-	By default the command shows all refs that match
-	`<pattern>`.  This option makes it stop after showing
-	that many refs.
+	Stop after showing <count> refs.
 
 --sort=<key>::
 	A field name to sort on.  Prefix `-` to sort in
@@ -50,7 +48,7 @@ OPTIONS
 	multiple times, in which case the last key becomes the primary
 	key.
 
---format=<format>::
+ --format[=<format>]::
 	A string that interpolates `%(fieldname)` from a ref being shown and
 	the object it points at. In addition, the string literal `%%`
 	renders as `%` and `%xx` - where `xx` are hex digits - renders as
@@ -100,10 +98,10 @@ TAB %(refname)`.
 	Do not print a newline after formatted refs where the format expands
 	to the empty string.
 
---exclude=<pattern>::
-	If one or more patterns are given, only refs which do not match
-	any excluded pattern(s) are shown. Matching is done using the
-	same rules as `<pattern>` above.
+--exclude=<excluded-pattern>::
+	If one or more --exclude options are given, only refs which do not
+	match any _<excluded-pattern>_ parameters are shown. Matching is done
+	using the same rules as _<pattern>_ above.
 
 --include-root-refs::
 	List root refs (HEAD and pseudorefs) apart from regular refs.
@@ -131,8 +129,8 @@ refname::
 	`refs/tags/foo` into `tags/foo` and `%(refname:rstrip=-1)`
 	turns `refs/tags/foo` into `refs`). When the ref does not have
 	enough components, the result becomes an empty string if
-	stripping with positive <N>, or it becomes the full refname if
-	stripping with negative <N>.  Neither is an error.
+	stripping with positive _<N>_, or it becomes the full refname if
+	stripping with negative _<N>_.  Neither is an error.
 +
 `strip` can be used as a synonym to `lstrip`.
 
-- 
2.49.0
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH 0/2] Add refs list subcommand
  2025-06-27 18:03 ` [GSoC][RFC PATCH 0/2] Add " Junio C Hamano
  2025-06-28  8:05   ` shejialuo
@ 2025-06-30  3:53   ` Meet Soni
  2025-06-30 20:10     ` Junio C Hamano
  1 sibling, 1 reply; 65+ messages in thread
From: Meet Soni @ 2025-06-30  3:53 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, ps, shejialuo, karthik.188
On Fri, 27 Jun 2025 at 23:33, Junio C Hamano <gitster@pobox.com> wrote:
>
> Meet Soni <meetsoni3017@gmail.com> writes:
>
> >   - Remove legacy or obscure options that are no longer needed.
>
> Such as?
>
> >   - Add improvements that wouldn't make sense to bolt onto legacy
> >     commands.
>
> Such as?
>
> While I agree that there may be cases that the above goals in
> general would bring us improvements, I think neither of these two
> applies to for-each-ref.  People are using for-each-ref to iterate
> over and enumerate refs already, and if you are to add some new
> features to "git refs list", they certainly will demand these new
> goodies to be added to for-each-ref as well.
>
> So, I dunno.
To clarify, I don't have specific options or improvements in mind right now.
The idea behind mentioning them was to acknowledge that having a consolidated
interface like git refs might open the door to such discussions.
The primary motivation here is to make ref-related commands discoverable at a
single entry point - git refs, rather than scattered across several top-level
commands. The aim is to improve disoverability and set the stage for
potential future
cleanup or enhancements, should the community find value in doing so.
Per mentor suggestion, this RFC was meant to invite broader input on whether
the community sees value in such a consolidation, and if so, what shape future
refinements (if any) might take.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH 1/2] builtin/refs: add list subcommand
  2025-06-27 16:27   ` Jean-Noël Avila
  2025-06-27 18:13     ` Junio C Hamano
@ 2025-06-30  4:28     ` Meet Soni
  1 sibling, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-06-30  4:28 UTC (permalink / raw)
  To: Jean-Noël Avila
  Cc: git, ps, shejialuo, karthik.188, Junio C Hamano, John Cai
Thanks for the review, and apologies. I should've clarified earlier that the
current documentation was meant as a placeholder until the feature direction is
finalized. The intention was to help reviewers get a sense of the intended
usage, and I plan to revise it more thoroughly once there's consensus on the
interface.
Also, noted the follow-up patch to improve the git-for-each-ref documentation.
Thanks for taking that initiative!
Thanks,
Meet
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH 0/2] Add refs list subcommand
  2025-06-28  8:05   ` shejialuo
@ 2025-06-30 14:05     ` Junio C Hamano
  2025-07-06 12:58       ` shejialuo
  0 siblings, 1 reply; 65+ messages in thread
From: Junio C Hamano @ 2025-06-30 14:05 UTC (permalink / raw)
  To: shejialuo; +Cc: Meet Soni, git, ps, karthik.188
shejialuo <shejialuo@gmail.com> writes:
> If so, we would make "git refs list" to place "git for-each-ref" at
> all. However, in the current implementation, we indeed introduce
> duplicate code path if we decide to do above.
I do not know what you meant by your first sentence.
If you make "git refs list <anything>" a thin wrapper for "git
for-each-ref <anthing>", you can satisfy "I want to teach any and
all features related to references to the 'git refs' command" while
not penalizing existing users.  After all, that is essentially what
"git branch" and "git tag" do as their listing mode and supports the
featurs from for-each-ref, isn't it?
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [PATCH] doc:git-for-each-ref: fix styling and typos
  2025-06-29 11:05   ` [PATCH] doc:git-for-each-ref: fix styling and typos Jean-Noël Avila
@ 2025-06-30 15:48     ` Junio C Hamano
  2025-06-30 18:55       ` Jean-Noël AVILA
  0 siblings, 1 reply; 65+ messages in thread
From: Junio C Hamano @ 2025-06-30 15:48 UTC (permalink / raw)
  To: Jean-Noël Avila; +Cc: Meet Soni, git, ps, shejialuo
Jean-Noël Avila <jn.avila@free.fr> writes:
> This commit fixes the synopsis syntax writing and changes the wording of a few
> descriptions to be more consistent with the rest of the documentation.
>
> Signed-off-by: Jean-Noël Avila <jn.avila@free.fr>
> ---
>  Documentation/git-for-each-ref.adoc | 30 ++++++++++++++---------------
>  1 file changed, 14 insertions(+), 16 deletions(-)
It is not making anything worse and all the changes I see here
(except for a stray SP slipped in) are for the better, but it is
curious that this stops halfway.  Things I noticed:
> diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc
> index 5ef89fc0fe..c2b2660771 100644
> --- a/Documentation/git-for-each-ref.adoc
> +++ b/Documentation/git-for-each-ref.adoc
> @@ -8,13 +8,13 @@ git-for-each-ref - Output information on each ref
>  SYNOPSIS
>  --------
>  [verse]
Eventually we would switch to [synopsis] I presume?
> -'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
> -		   [(--sort=<key>)...] [--format=<format>]
> -		   [--include-root-refs] [ --stdin | <pattern>... ]
> +'git for-each-ref' [--count=<count>] [--shell | --perl | --python | --tcl]
> +		   [(--sort=<key>)...] [--format[=<format>]]
> +		   [--include-root-refs] [--stdin | <pattern>...]
>  		   [--points-at=<object>]
>  		   [--merged[=<object>]] [--no-merged[=<object>]]
>  		   [--contains[=<object>]] [--no-contains[=<object>]]
> -		   [--exclude=<pattern> ...]
> +		   [(--exclude=<excluded-pattern>)...]
>  
>  DESCRIPTION
>  -----------
> @@ -35,13 +35,11 @@ OPTIONS
>  	beginning up to a slash.
>  
>  --stdin::
> -	If `--stdin` is supplied, then the list of patterns is read from
> -	standard input instead of from the argument list.
> +	The list of patterns is read from standard input instead of from
> +	the argument list.
>  
>  --count=<count>::
> -	By default the command shows all refs that match
> -	`<pattern>`.  This option makes it stop after showing
> -	that many refs.
> +	Stop after showing <count> refs.
This patch would have changed this to _<count>_, judging from what
it did elsewhere.
> @@ -50,7 +48,7 @@ OPTIONS
>  	multiple times, in which case the last key becomes the primary
>  	key.
>  
> ---format=<format>::
> + --format[=<format>]::
Stray SP in the front?
> @@ -100,10 +98,10 @@ TAB %(refname)`.
>  	Do not print a newline after formatted refs where the format expands
>  	to the empty string.
>  
> ---exclude=<pattern>::
> -	If one or more patterns are given, only refs which do not match
> -	any excluded pattern(s) are shown. Matching is done using the
> -	same rules as `<pattern>` above.
> +--exclude=<excluded-pattern>::
> +	If one or more --exclude options are given, only refs which do not
> +	match any _<excluded-pattern>_ parameters are shown. Matching is done
> +	using the same rules as _<pattern>_ above.
OK.  Doing the literal `--exclude` for options in the description is
left for future patches would not make it any worse, and adopting
_<placeholder>_ convention makes it better.
>  --include-root-refs::
>  	List root refs (HEAD and pseudorefs) apart from regular refs.
> @@ -131,8 +129,8 @@ refname::
>  	`refs/tags/foo` into `tags/foo` and `%(refname:rstrip=-1)`
>  	turns `refs/tags/foo` into `refs`). When the ref does not have
>  	enough components, the result becomes an empty string if
> -	stripping with positive <N>, or it becomes the full refname if
> -	stripping with negative <N>.  Neither is an error.
> +	stripping with positive _<N>_, or it becomes the full refname if
> +	stripping with negative _<N>_.  Neither is an error.
>  +
>  `strip` can be used as a synonym to `lstrip`.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [PATCH] doc:git-for-each-ref: fix styling and typos
  2025-06-30 15:48     ` Junio C Hamano
@ 2025-06-30 18:55       ` Jean-Noël AVILA
  0 siblings, 0 replies; 65+ messages in thread
From: Jean-Noël AVILA @ 2025-06-30 18:55 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Meet Soni, git, ps, shejialuo
On Monday, 30 June 2025 17:48:05 CEST Junio C Hamano wrote:
> Jean-Noël Avila <jn.avila@free.fr> writes:
> > This commit fixes the synopsis syntax writing and changes the wording of a
> > few descriptions to be more consistent with the rest of the documentation.
> > 
> > Signed-off-by: Jean-Noël Avila <jn.avila@free.fr>
> > ---
> > 
> >  Documentation/git-for-each-ref.adoc | 30 ++++++++++++++---------------
> >  1 file changed, 14 insertions(+), 16 deletions(-)
> 
> It is not making anything worse and all the changes I see here
> (except for a stray SP slipped in) are for the better, but it is
> 
> curious that this stops halfway.  Things I noticed:
I was just focusing on general style and synopsis syntax as an example for the 
original patch in this thread, not really trying to convert this page. I agree 
that I should do the full monty while at it.
Will roll a V2 then.
Thanks
Jean-Noël
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH 0/2] Add refs list subcommand
  2025-06-30  3:53   ` Meet Soni
@ 2025-06-30 20:10     ` Junio C Hamano
  2025-07-09 13:36       ` Patrick Steinhardt
  0 siblings, 1 reply; 65+ messages in thread
From: Junio C Hamano @ 2025-06-30 20:10 UTC (permalink / raw)
  To: Meet Soni; +Cc: git, ps, shejialuo, karthik.188
Meet Soni <meetsoni3017@gmail.com> writes:
> To clarify, I don't have specific options or improvements in mind right now.
> The idea behind mentioning them was to acknowledge that having a consolidated
> interface like git refs might open the door to such discussions.
One complaint we heard a lot about Git in the past (I do not know if
people got used to and learned to live with, or they are still
complaining about the same these days) was that there were always
multiple ways to do related things slightly differently.
The machinery of for-each-ref is shared by branch and tag to give
them feature-parity.  Adding yet another command that behaves
slightly differently, with the intention to make it diverge even
more in the future, feels going backwards.
> The primary motivation here is to make ref-related commands discoverable at a
> single entry point - git refs, rather than scattered across several top-level
> commands. The aim is to improve disoverability and set the stage for
> potential future
> cleanup or enhancements, should the community find value in doing so.
We need to be a bit careful.  There was a case that went in a
totally opposite direction.  Even though a single command was there
in the beginning that can be used to pick a specific thing out of
the histories stored in a repository to externalize it in the file
system to be worked on, it was later split into two commands as
folks thought it would make it more discoverable.
Even though branches, tags, and symbolic refs like HEAD are all
refs, from the point of view of end-users, they are different things
built on top of the same underlying mechanism.  The fact that they
share the underlying mechanism does not necessarily mean that a
single same command is easier to discover and work on them to the
users.
It may not hurt too much to have "enumerate all refs, and for each
ref, allowing to limit them or to sort them with some criteria, and
show them in a customizable format" as "git refs list", but we do
need to keep the feature parity with "git for-each-ref".  Adding
unnecessary subtle differences would only confuse users.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH 0/2] Add refs list subcommand
  2025-06-30 14:05     ` Junio C Hamano
@ 2025-07-06 12:58       ` shejialuo
  0 siblings, 0 replies; 65+ messages in thread
From: shejialuo @ 2025-07-06 12:58 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Meet Soni, git, ps, karthik.188
On Mon, Jun 30, 2025 at 07:05:43AM -0700, Junio C Hamano wrote:
> shejialuo <shejialuo@gmail.com> writes:
> 
> > If so, we would make "git refs list" to place "git for-each-ref" at
> > all. However, in the current implementation, we indeed introduce
> > duplicate code path if we decide to do above.
> 
> I do not know what you meant by your first sentence.
> 
Sorry, I think I made you confused here. My meaning is exactly below
what you have said.
> If you make "git refs list <anything>" a thin wrapper for "git
> for-each-ref <anthing>", you can satisfy "I want to teach any and
> all features related to references to the 'git refs' command" while
> not penalizing existing users.  After all, that is essentially what
> "git branch" and "git tag" do as their listing mode and supports the
> featurs from for-each-ref, isn't it?
That's right. I just want to let Meet know, if we decide to make "git
refs list" a wrapper for "git for-each-ref", we need to change the
current code design as we introduced repeated code path.
---
Sorry for the late reply, as I am extremely busy with my own business.
Thanks,
Jialuo
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH 0/2] Add refs list subcommand
  2025-06-30 20:10     ` Junio C Hamano
@ 2025-07-09 13:36       ` Patrick Steinhardt
  0 siblings, 0 replies; 65+ messages in thread
From: Patrick Steinhardt @ 2025-07-09 13:36 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Meet Soni, git, shejialuo, karthik.188
On Mon, Jun 30, 2025 at 01:10:36PM -0700, Junio C Hamano wrote:
> Meet Soni <meetsoni3017@gmail.com> writes:
> 
> > To clarify, I don't have specific options or improvements in mind right now.
> > The idea behind mentioning them was to acknowledge that having a consolidated
> > interface like git refs might open the door to such discussions.
> 
> One complaint we heard a lot about Git in the past (I do not know if
> people got used to and learned to live with, or they are still
> complaining about the same these days) was that there were always
> multiple ways to do related things slightly differently.
> 
> The machinery of for-each-ref is shared by branch and tag to give
> them feature-parity.  Adding yet another command that behaves
> slightly differently, with the intention to make it diverge even
> more in the future, feels going backwards.
That's fair. I do think that the common infrastructure should be shared
indeed, and that includes the options. So the most important benefit of
the new subcommand would be an improvement to discoverability.
But there is one default in git-for-each-ref(1) that has been biting us
multiple times at GitLab already, namely the default format. In
git-for-each-ref(1) it is:
    %(objectname) %(objecttype)\t%(refname)
The problem with this format is `%(objecttype)` -- it requires us to not
only read refs from the reference database, but also resolve the object
via the ODB so that we can figure out its type. And that can be a huge
slowdown in large repositories. Take e.g. the Chromium repository:
    Benchmark 1: git for-each-ref
      Time (mean ± σ):     148.9 ms ±   1.0 ms    [User: 80.7 ms, System: 67.2 ms]
      Range (min … max):   147.2 ms … 154.2 ms    100 runs
    Benchmark 2: git for-each-ref --format="%(objectname) %(refname)"
      Time (mean ± σ):      22.4 ms ±   0.3 ms    [User: 21.4 ms, System: 1.0 ms]
      Range (min … max):    22.0 ms …  24.0 ms    100 runs
    Summary
      git for-each-ref --format="%(objectname) %(refname)" ran
        6.65 ± 0.09 times faster than git for-each-ref
So from my point of view, this is a thing we should consider changing in
the new subcommand. For all the other options I agree, we should have
common infra and thus common options.
Patrick
^ permalink raw reply	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v2 0/2] Add refs list subcommand
  2025-06-27  7:49 [GSoC][RFC PATCH 0/2] Add refs list subcommand Meet Soni
                   ` (2 preceding siblings ...)
  2025-06-27 18:03 ` [GSoC][RFC PATCH 0/2] Add " Junio C Hamano
@ 2025-07-17  7:50 ` Meet Soni
  2025-07-17  7:50   ` [GSoC][RFC PATCH v2 1/2] builtin/refs: add " Meet Soni
                     ` (2 more replies)
  3 siblings, 3 replies; 65+ messages in thread
From: Meet Soni @ 2025-07-17  7:50 UTC (permalink / raw)
  To: git; +Cc: ps, shejialuo, karthik.188, gitster, Meet Soni
Hello everyone,
This is the second version of the patch series that introduces the `git
refs list` subcommand as a modern alternative to `git for-each-ref`.
Thank you to everyone who provided valuable feedback on the first
version. This new version incorporates the suggestions, improving both
the code's implementation and the documentation's structure.
Changes in v2:
  - Refactored the implementation of `refs list`. Instead of duplicating
    the logic from `for-each-ref`, it is now a thin wrapper that
    calls the original command. This avoids code duplication while
    maintaining identical behavior.
  - Refactored the documentation by moving the common command options
    shared by `refs list` and `for-each-ref` into a new standalone
    file (`ref-list-options.adoc`).
  - Both man pages now use the AsciiDoc `include::` macro to embed these
    shared options, ensuring documentation stays consistent.
---
(v1 cover-letter text)
This patch series introduces `git refs list` as a modern replacement for
`git for-each-ref`, as part of an effort to consolidate ref-related
functionality under a unified `git refs` command.
Git's ref-related operations are currently handled by several distinct
commands, such as `git show-ref`, `git for-each-ref`, `git update-ref`,
`git pack-refs`, etc. This distribution has a few practical drawbacks:
- Users need to rely on multiple commands for related tasks involving
  refs.
- The commands may differ slightly in behavior and option syntax,
  leading to inconsistency.
We propose a long-term consolidation effort to bring ref-related
subcommands under the umbrella of a single command: `git refs`.
The implementation of `git refs list` is functionally identical to `git
for-each-ref`. It reuses the same internal logic (cmd_for_each_ref) to
ensure complete backward compatibility. The purpose of this patch is not
to introduce new behavior but to provide an alternate entry point under
the consolidated `git refs` namespace.
The motivation behind this change is twofold:
- Consolidation: Centralizing ref-related operations makes them easier
  to discover, use, and maintain.
- Evolution: While the initial goal is parity with existing commands,
  this consolidation allows for careful reconsideration of which
  features are essential. Over time, we can:
  - Remove legacy or obscure options that are no longer needed.
  - Add improvements that wouldn't make sense to bolt onto legacy
    commands.
  - Offering a more consistent and user-friendly surface.
To verify backward compatibility, this patch also includes a test
`t/t1461-refs-list.sh`, which runs the full `t6300-for-each-ref.sh` test
using `git refs list`. The test uses ${GIT_REFS_LIST_CMD:-for-each-ref}
to allow substitution without duplicating tests.
This patch is deliberately conservative: it introduces no behavioral
changes and leaves `for-each-ref` untouched. The goal is to lay
groundwork and demonstrate viability of ref consolidation within `git
refs`.
Going forward, I'd like to initiate a discussion on what the ideal
surface of `git refs list` should look like. Which options and features
from `for-each-ref` should be carried over? Are there any that are
obsolete or overly niche? What improvements might be worth considering
now that we have a new, consolidated interface?
Feedback on this, especially from those who rely on `for-each-ref` in
scripts or tooling would be very helpful.
Meet Soni (2):
  builtin/refs: add list subcommand
  t: add test for git refs list subcommand
 Documentation/git-for-each-ref.adoc  |  80 +------
 Documentation/git-refs.adoc          |  16 ++
 Documentation/refs-list-options.adoc |  80 +++++++
 builtin/for-each-ref.c               |  24 +-
 builtin/refs.c                       |  35 +++
 t/meson.build                        |   1 +
 t/t1461-refs-list.sh                 |   8 +
 t/t6300-for-each-ref.sh              | 333 ++++++++++++++-------------
 8 files changed, 331 insertions(+), 246 deletions(-)
 create mode 100644 Documentation/refs-list-options.adoc
 create mode 100755 t/t1461-refs-list.sh
Range-diff against v1:
1:  e7b19c71eb ! 1:  b2d3026520 builtin/refs: add list subcommand
    @@ Commit message
         instead of duplicating its logic. Forward all arguments to the existing
         function to ensure behavior is identical.
     
    -    This prevents code duplication and allows `refs list` to benefit from
    -    any future fixes to the underlying `for-each-ref` machinery.
    +    Add documentation for the new command. To keep the documentation DRY and
    +    consistent with `git-for-each-ref(1)`, refactor the shared command
    +    options into a standalone file. Use the AsciiDoc `include::` macro to
    +    embed these options in both man pages.
    +
    +    This prevents duplication in both code and documentation, ensuring that
    +    `refs list` benefits from any future fixes to the underlying
    +    `for-each-ref` machinery and its shared documentation.
     
         Mentored-by: Patrick Steinhardt <ps@pks.im>
         Mentored-by: shejialuo <shejialuo@gmail.com>
         Mentored-by: Karthik Nayak <karthik.188@gmail.com>
         Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
     
    + ## Documentation/git-for-each-ref.adoc ##
    +@@ Documentation/git-for-each-ref.adoc: host language allowing their direct evaluation in that language.
    + 
    + OPTIONS
    + -------
    +-<pattern>...::
    +-	If one or more patterns are given, only refs are shown that
    +-	match against at least one pattern, either using fnmatch(3) or
    +-	literally, in the latter case matching completely or from the
    +-	beginning up to a slash.
    +-
    +---stdin::
    +-	If `--stdin` is supplied, then the list of patterns is read from
    +-	standard input instead of from the argument list.
    +-
    +---count=<count>::
    +-	By default the command shows all refs that match
    +-	`<pattern>`.  This option makes it stop after showing
    +-	that many refs.
    +-
    +---sort=<key>::
    +-	A field name to sort on.  Prefix `-` to sort in
    +-	descending order of the value.  When unspecified,
    +-	`refname` is used.  You may use the --sort=<key> option
    +-	multiple times, in which case the last key becomes the primary
    +-	key.
    +-
    +---format=<format>::
    +-	A string that interpolates `%(fieldname)` from a ref being shown and
    +-	the object it points at. In addition, the string literal `%%`
    +-	renders as `%` and `%xx` - where `xx` are hex digits - renders as
    +-	the character with hex code `xx`. For example, `%00` interpolates to
    +-	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
    +-+
    +-When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
    +-TAB %(refname)`.
    +-
    +---color[=<when>]::
    +-	Respect any colors specified in the `--format` option. The
    +-	`<when>` field must be one of `always`, `never`, or `auto` (if
    +-	`<when>` is absent, behave as if `always` was given).
    +-
    +---shell::
    +---perl::
    +---python::
    +---tcl::
    +-	If given, strings that substitute `%(fieldname)`
    +-	placeholders are quoted as string literals suitable for
    +-	the specified host language.  This is meant to produce
    +-	a scriptlet that can directly be `eval`ed.
    +-
    +---points-at=<object>::
    +-	Only list refs which points at the given object.
    +-
    +---merged[=<object>]::
    +-	Only list refs whose tips are reachable from the
    +-	specified commit (HEAD if not specified).
    +-
    +---no-merged[=<object>]::
    +-	Only list refs whose tips are not reachable from the
    +-	specified commit (HEAD if not specified).
    +-
    +---contains[=<object>]::
    +-	Only list refs which contain the specified commit (HEAD if not
    +-	specified).
    +-
    +---no-contains[=<object>]::
    +-	Only list refs which don't contain the specified commit (HEAD
    +-	if not specified).
    +-
    +---ignore-case::
    +-	Sorting and filtering refs are case insensitive.
    +-
    +---omit-empty::
    +-	Do not print a newline after formatted refs where the format expands
    +-	to the empty string.
    +-
    +---exclude=<pattern>::
    +-	If one or more patterns are given, only refs which do not match
    +-	any excluded pattern(s) are shown. Matching is done using the
    +-	same rules as `<pattern>` above.
    +-
    +---include-root-refs::
    +-	List root refs (HEAD and pseudorefs) apart from regular refs.
    ++include::refs-list-options.adoc[]
    + 
    + FIELD NAMES
    + -----------
    +
      ## Documentation/git-refs.adoc ##
     @@ Documentation/git-refs.adoc: SYNOPSIS
      [synopsis]
    @@ Documentation/git-refs.adoc: migrate::
      	Verify reference database consistency.
      
     +list::
    -+	List references in the repository with support for filtering, formatting,
    -+	and sorting. This subcommand uses the same core logic as
    -+	linkgit:git-for-each-ref[1] and offers equivalent functionality.
    ++	List references in the repository with support for filtering,
    ++	formatting, and sorting. This subcommand is an alias for
    ++	linkgit:git-for-each-ref[1] and offers identical functionality.
     +
      OPTIONS
      -------
    @@ Documentation/git-refs.adoc: The following options are specific to 'git refs ver
      
     +The following options are specific to 'git refs list':
     +
    ++include::refs-list-options.adoc[]
    ++
    + KNOWN LIMITATIONS
    + -----------------
    + 
    +
    + ## Documentation/refs-list-options.adoc (new) ##
    +@@
    ++// Shared options for for-each-ref and refs list
     +<pattern>...::
     +	If one or more patterns are given, only refs are shown that
     +	match against at least one pattern, either using fnmatch(3) or
    @@ Documentation/git-refs.adoc: The following options are specific to 'git refs ver
     +
     +--include-root-refs::
     +	List root refs (HEAD and pseudorefs) apart from regular refs.
    -+
    -+
    - KNOWN LIMITATIONS
    - -----------------
    - 
     
      ## builtin/for-each-ref.c ##
     @@ builtin/for-each-ref.c: static char const * const for_each_ref_usage[] = {
2:  8ce521880c = 2:  2d6534841f t: add test for git refs list subcommand
-- 
2.34.1
^ permalink raw reply	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v2 1/2] builtin/refs: add list subcommand
  2025-07-17  7:50 ` [GSoC][RFC PATCH v2 " Meet Soni
@ 2025-07-17  7:50   ` Meet Soni
  2025-07-17 16:48     ` Eric Sunshine
  2025-07-17  7:50   ` [GSoC][RFC PATCH v2 2/2] t: add test for git refs " Meet Soni
  2025-07-23  6:43   ` [GSoC][RFC PATCH v3 0/3] Add " Meet Soni
  2 siblings, 1 reply; 65+ messages in thread
From: Meet Soni @ 2025-07-17  7:50 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, Meet Soni, Christian Couder,
	Victoria Dye
Git's reference management is distributed across multiple commands. As
part of an ongoing effort to consolidate and modernize reference
handling, introduce a `list` subcommand under the `git refs` umbrella as
a replacement for `git for-each-ref`.
Implement `cmd_refs_list` as a thin wrapper around `cmd_for_each_ref`
instead of duplicating its logic. Forward all arguments to the existing
function to ensure behavior is identical.
Add documentation for the new command. To keep the documentation DRY and
consistent with `git-for-each-ref(1)`, refactor the shared command
options into a standalone file. Use the AsciiDoc `include::` macro to
embed these options in both man pages.
This prevents duplication in both code and documentation, ensuring that
`refs list` benefits from any future fixes to the underlying
`for-each-ref` machinery and its shared documentation.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 Documentation/git-for-each-ref.adoc  | 80 +---------------------------
 Documentation/git-refs.adoc          | 16 ++++++
 Documentation/refs-list-options.adoc | 80 ++++++++++++++++++++++++++++
 builtin/for-each-ref.c               | 24 +++++++--
 builtin/refs.c                       | 35 ++++++++++++
 5 files changed, 152 insertions(+), 83 deletions(-)
 create mode 100644 Documentation/refs-list-options.adoc
diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc
index 5ef89fc0fe..f7bbc1902a 100644
--- a/Documentation/git-for-each-ref.adoc
+++ b/Documentation/git-for-each-ref.adoc
@@ -28,85 +28,7 @@ host language allowing their direct evaluation in that language.
 
 OPTIONS
 -------
-<pattern>...::
-	If one or more patterns are given, only refs are shown that
-	match against at least one pattern, either using fnmatch(3) or
-	literally, in the latter case matching completely or from the
-	beginning up to a slash.
-
---stdin::
-	If `--stdin` is supplied, then the list of patterns is read from
-	standard input instead of from the argument list.
-
---count=<count>::
-	By default the command shows all refs that match
-	`<pattern>`.  This option makes it stop after showing
-	that many refs.
-
---sort=<key>::
-	A field name to sort on.  Prefix `-` to sort in
-	descending order of the value.  When unspecified,
-	`refname` is used.  You may use the --sort=<key> option
-	multiple times, in which case the last key becomes the primary
-	key.
-
---format=<format>::
-	A string that interpolates `%(fieldname)` from a ref being shown and
-	the object it points at. In addition, the string literal `%%`
-	renders as `%` and `%xx` - where `xx` are hex digits - renders as
-	the character with hex code `xx`. For example, `%00` interpolates to
-	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
-+
-When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
-TAB %(refname)`.
-
---color[=<when>]::
-	Respect any colors specified in the `--format` option. The
-	`<when>` field must be one of `always`, `never`, or `auto` (if
-	`<when>` is absent, behave as if `always` was given).
-
---shell::
---perl::
---python::
---tcl::
-	If given, strings that substitute `%(fieldname)`
-	placeholders are quoted as string literals suitable for
-	the specified host language.  This is meant to produce
-	a scriptlet that can directly be `eval`ed.
-
---points-at=<object>::
-	Only list refs which points at the given object.
-
---merged[=<object>]::
-	Only list refs whose tips are reachable from the
-	specified commit (HEAD if not specified).
-
---no-merged[=<object>]::
-	Only list refs whose tips are not reachable from the
-	specified commit (HEAD if not specified).
-
---contains[=<object>]::
-	Only list refs which contain the specified commit (HEAD if not
-	specified).
-
---no-contains[=<object>]::
-	Only list refs which don't contain the specified commit (HEAD
-	if not specified).
-
---ignore-case::
-	Sorting and filtering refs are case insensitive.
-
---omit-empty::
-	Do not print a newline after formatted refs where the format expands
-	to the empty string.
-
---exclude=<pattern>::
-	If one or more patterns are given, only refs which do not match
-	any excluded pattern(s) are shown. Matching is done using the
-	same rules as `<pattern>` above.
-
---include-root-refs::
-	List root refs (HEAD and pseudorefs) apart from regular refs.
+include::refs-list-options.adoc[]
 
 FIELD NAMES
 -----------
diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
index 4d6dc994f9..ded90f435b 100644
--- a/Documentation/git-refs.adoc
+++ b/Documentation/git-refs.adoc
@@ -11,6 +11,13 @@ SYNOPSIS
 [synopsis]
 git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
 git refs verify [--strict] [--verbose]
+git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
+	      [(--sort=<key>)...] [--format=<format>]
+	      [--include-root-refs] [ --stdin | <pattern>... ]
+	      [--points-at=<object>]
+	      [--merged[=<object>]] [--no-merged[=<object>]]
+	      [--contains[=<object>]] [--no-contains[=<object>]]
+	      [--exclude=<pattern> ...]
 
 DESCRIPTION
 -----------
@@ -26,6 +33,11 @@ migrate::
 verify::
 	Verify reference database consistency.
 
+list::
+	List references in the repository with support for filtering,
+	formatting, and sorting. This subcommand is an alias for
+	linkgit:git-for-each-ref[1] and offers identical functionality.
+
 OPTIONS
 -------
 
@@ -57,6 +69,10 @@ The following options are specific to 'git refs verify':
 --verbose::
 	When verifying the reference database consistency, be chatty.
 
+The following options are specific to 'git refs list':
+
+include::refs-list-options.adoc[]
+
 KNOWN LIMITATIONS
 -----------------
 
diff --git a/Documentation/refs-list-options.adoc b/Documentation/refs-list-options.adoc
new file mode 100644
index 0000000000..9d6557cdb9
--- /dev/null
+++ b/Documentation/refs-list-options.adoc
@@ -0,0 +1,80 @@
+// Shared options for for-each-ref and refs list
+<pattern>...::
+	If one or more patterns are given, only refs are shown that
+	match against at least one pattern, either using fnmatch(3) or
+	literally, in the latter case matching completely or from the
+	beginning up to a slash.
+
+--stdin::
+	If `--stdin` is supplied, then the list of patterns is read from
+	standard input instead of from the argument list.
+
+--count=<count>::
+	By default the command shows all refs that match
+	`<pattern>`.  This option makes it stop after showing
+	that many refs.
+
+--sort=<key>::
+	A field name to sort on.  Prefix `-` to sort in
+	descending order of the value.  When unspecified,
+	`refname` is used.  You may use the --sort=<key> option
+	multiple times, in which case the last key becomes the primary
+	key.
+
+--format=<format>::
+	A string that interpolates `%(fieldname)` from a ref being shown and
+	the object it points at. In addition, the string literal `%%`
+	renders as `%` and `%xx` - where `xx` are hex digits - renders as
+	the character with hex code `xx`. For example, `%00` interpolates to
+	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
++
+When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
+TAB %(refname)`.
+
+--color[=<when>]::
+	Respect any colors specified in the `--format` option. The
+	`<when>` field must be one of `always`, `never`, or `auto` (if
+	`<when>` is absent, behave as if `always` was given).
+
+--shell::
+--perl::
+--python::
+--tcl::
+	If given, strings that substitute `%(fieldname)`
+	placeholders are quoted as string literals suitable for
+	the specified host language.  This is meant to produce
+	a scriptlet that can directly be `eval`ed.
+
+--points-at=<object>::
+	Only list refs which points at the given object.
+
+--merged[=<object>]::
+	Only list refs whose tips are reachable from the
+	specified commit (HEAD if not specified).
+
+--no-merged[=<object>]::
+	Only list refs whose tips are not reachable from the
+	specified commit (HEAD if not specified).
+
+--contains[=<object>]::
+	Only list refs which contain the specified commit (HEAD if not
+	specified).
+
+--no-contains[=<object>]::
+	Only list refs which don't contain the specified commit (HEAD
+	if not specified).
+
+--ignore-case::
+	Sorting and filtering refs are case insensitive.
+
+--omit-empty::
+	Do not print a newline after formatted refs where the format expands
+	to the empty string.
+
+--exclude=<pattern>::
+	If one or more patterns are given, only refs which do not match
+	any excluded pattern(s) are shown. Matching is done using the
+	same rules as `<pattern>` above.
+
+--include-root-refs::
+	List root refs (HEAD and pseudorefs) apart from regular refs.
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 3d2207ec77..d7d8279049 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -16,11 +16,27 @@ static char const * const for_each_ref_usage[] = {
 	NULL
 };
 
+#define REFS_LIST_USAGE \
+	N_("git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n" \
+	   "              [(--sort=<key>)...] [--format=<format>]\n" \
+	   "              [--include-root-refs] [ --stdin | <pattern>... ]\n" \
+	   "              [--points-at=<object>]\n" \
+	   "              [--merged[=<object>]] [--no-merged[=<object>]]\n" \
+	   "              [--contains[=<object>]] [--no-contains[=<object>]]\n" \
+	   "              [--exclude=<pattern> ...]")
+
+static char const * const refs_list_usage[] = {
+	REFS_LIST_USAGE,
+	NULL
+};
+
 int cmd_for_each_ref(int argc,
 		     const char **argv,
 		     const char *prefix,
 		     struct repository *repo)
 {
+	int cmd_is_refs_list = !strcmp(argv[0], "refs list");
+	const char *const *opt_usage = cmd_is_refs_list ? refs_list_usage : for_each_ref_usage;
 	struct ref_sorting *sorting;
 	struct string_list sorting_options = STRING_LIST_INIT_DUP;
 	int icase = 0, include_root_refs = 0, from_stdin = 0;
@@ -67,17 +83,17 @@ int cmd_for_each_ref(int argc,
 	/* Set default (refname) sorting */
 	string_list_append(&sorting_options, "refname");
 
-	parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
+	parse_options(argc, argv, prefix, opts, opt_usage, 0);
 	if (format.array_opts.max_count < 0) {
 		error("invalid --count argument: `%d'", format.array_opts.max_count);
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(opt_usage, opts);
 	}
 	if (HAS_MULTI_BITS(format.quote_style)) {
 		error("more than one quoting style?");
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(opt_usage, opts);
 	}
 	if (verify_ref_format(&format))
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(opt_usage, opts);
 
 	sorting = ref_sorting_options(&sorting_options);
 	ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
diff --git a/builtin/refs.c b/builtin/refs.c
index 998d2a2c1c..41e29d1b5f 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -3,6 +3,7 @@
 #include "config.h"
 #include "fsck.h"
 #include "parse-options.h"
+#include "ref-filter.h"
 #include "refs.h"
 #include "strbuf.h"
 #include "worktree.h"
@@ -13,6 +14,15 @@
 #define REFS_VERIFY_USAGE \
 	N_("git refs verify [--strict] [--verbose]")
 
+#define REFS_LIST_USAGE \
+	N_("git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n" \
+	   "              [(--sort=<key>)...] [--format=<format>]\n" \
+	   "              [--include-root-refs] [ --stdin | <pattern>... ]\n" \
+	   "              [--points-at=<object>]\n" \
+	   "              [--merged[=<object>]] [--no-merged[=<object>]]\n" \
+	   "              [--contains[=<object>]] [--no-contains[=<object>]]\n" \
+	   "              [--exclude=<pattern> ...]")
+
 static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
 			    struct repository *repo UNUSED)
 {
@@ -101,6 +111,29 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
 	return ret;
 }
 
+static int cmd_refs_list(int argc, const char **argv, const char *prefix,
+		  struct repository *repo)
+{
+	struct strvec args = STRVEC_INIT;
+	const char **args_copy;
+	int ret;
+
+	strvec_push(&args, "refs list");
+
+	for (int i = 1; i < argc; i++)
+		strvec_push(&args, argv[i]);
+
+	CALLOC_ARRAY(args_copy, args.nr + 1);
+	COPY_ARRAY(args_copy, args.v, args.nr);
+
+	ret = cmd_for_each_ref(args.nr, args_copy, prefix, repo);
+
+	strvec_clear(&args);
+	free(args_copy);
+
+	return ret;
+}
+
 int cmd_refs(int argc,
 	     const char **argv,
 	     const char *prefix,
@@ -109,12 +142,14 @@ int cmd_refs(int argc,
 	const char * const refs_usage[] = {
 		REFS_MIGRATE_USAGE,
 		REFS_VERIFY_USAGE,
+		REFS_LIST_USAGE,
 		NULL,
 	};
 	parse_opt_subcommand_fn *fn = NULL;
 	struct option opts[] = {
 		OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate),
 		OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify),
+		OPT_SUBCOMMAND("list", &fn, cmd_refs_list),
 		OPT_END(),
 	};
 
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v2 2/2] t: add test for git refs list subcommand
  2025-07-17  7:50 ` [GSoC][RFC PATCH v2 " Meet Soni
  2025-07-17  7:50   ` [GSoC][RFC PATCH v2 1/2] builtin/refs: add " Meet Soni
@ 2025-07-17  7:50   ` Meet Soni
  2025-07-17 21:01     ` Junio C Hamano
  2025-07-23  6:43   ` [GSoC][RFC PATCH v3 0/3] Add " Meet Soni
  2 siblings, 1 reply; 65+ messages in thread
From: Meet Soni @ 2025-07-17  7:50 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, Meet Soni, Taylor Blau,
	Jeff King, Kousik Sanagavarapu
Adapt `t/t6300-for-each-ref.sh` to invoke ${GIT_REFS_LIST_CMD},
defaulting to `for-each-ref` if the variable is unset.
Add `t/t1461-refs-list.sh` to test git refs list by setting
`GIT_REFS_LIST_CMD` to `refs list` and sourcing
`t/t6300-for-each-ref.sh`
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 t/meson.build           |   1 +
 t/t1461-refs-list.sh    |   8 +
 t/t6300-for-each-ref.sh | 333 ++++++++++++++++++++--------------------
 3 files changed, 179 insertions(+), 163 deletions(-)
 create mode 100755 t/t1461-refs-list.sh
diff --git a/t/meson.build b/t/meson.build
index 50e89e764a..c959c039d0 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -224,6 +224,7 @@ integration_tests = [
   't1450-fsck.sh',
   't1451-fsck-buffer.sh',
   't1460-refs-migrate.sh',
+  't1461-refs-list.sh',
   't1500-rev-parse.sh',
   't1501-work-tree.sh',
   't1502-rev-parse-parseopt.sh',
diff --git a/t/t1461-refs-list.sh b/t/t1461-refs-list.sh
new file mode 100755
index 0000000000..9def7b2e5b
--- /dev/null
+++ b/t/t1461-refs-list.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+test_description='git refs list tests'
+
+. ./test-lib.sh
+
+GIT_REFS_LIST_CMD='refs list'
+. "$TEST_DIRECTORY"/t6300-for-each-ref.sh
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index ce9af79ab1..74a030371c 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -5,7 +5,9 @@
 
 test_description='for-each-ref test'
 
-. ./test-lib.sh
+. "${TEST_DIRECTORY:-.}/test-lib.sh"
+
+GIT_REFS_LIST_CMD=${GIT_REFS_LIST_CMD:-for-each-ref}
 GNUPGHOME_NOT_USED=$GNUPGHOME
 . "$TEST_DIRECTORY"/lib-gpg.sh
 . "$TEST_DIRECTORY"/lib-terminal.sh
@@ -61,7 +63,7 @@ test_atom () {
 
 	printf '%s\n' "$3" >expected
 	$test_do $PREREQ "basic atom: $ref $format" '
-		git for-each-ref --format="%($format)" "$ref" >actual &&
+		git ${GIT_REFS_LIST_CMD} --format="%($format)" "$ref" >actual &&
 		sanitize_pgp <actual >actual.clean &&
 		test_cmp expected actual.clean
 	'
@@ -88,7 +90,7 @@ test_atom () {
 			esac &&
 			# Leave $expect unquoted to lose possible leading whitespaces
 			echo $expect >expected &&
-			git for-each-ref --format="%(contents:size)" "$ref" >actual &&
+			git ${GIT_REFS_LIST_CMD} --format="%(contents:size)" "$ref" >actual &&
 			test_cmp expected actual
 		'
 	fi
@@ -281,7 +283,7 @@ test_atom tag HEAD ' '
 
 test_expect_success 'basic atom: refs/tags/testtag *raw' '
 	git cat-file commit refs/tags/testtag^{} >expected &&
-	git for-each-ref --format="%(*raw)" refs/tags/testtag >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(*raw)" refs/tags/testtag >actual &&
 	sanitize_pgp <expected >expected.clean &&
 	echo >>expected.clean &&
 	sanitize_pgp <actual >actual.clean &&
@@ -289,40 +291,45 @@ test_expect_success 'basic atom: refs/tags/testtag *raw' '
 '
 
 test_expect_success 'Check invalid atoms names are errors' '
-	test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
-'
-
-test_expect_success 'for-each-ref does not crash with -h' '
-	test_expect_code 129 git for-each-ref -h >usage &&
-	test_grep "[Uu]sage: git for-each-ref " usage &&
-	test_expect_code 129 nongit git for-each-ref -h >usage &&
-	test_grep "[Uu]sage: git for-each-ref " usage
-'
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(INVALID)" refs/heads
+'
+
+if test "$GIT_REFS_LIST_CMD" = "refs list"
+then
+	say "Skipping -h crash test for git refs list"
+else
+	test_expect_success "${GIT_REFS_LIST_CMD} does not crash with -h" '
+		test_expect_code 129 git ${GIT_REFS_LIST_CMD} -h >usage &&
+		test_grep "[Uu]sage: git for-each-ref " usage &&
+		test_expect_code 129 nongit git ${GIT_REFS_LIST_CMD} -h >usage &&
+		test_grep "[Uu]sage: git for-each-ref " usage
+	'
+fi
 
 test_expect_success 'Check format specifiers are ignored in naming date atoms' '
-	git for-each-ref --format="%(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate) %(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate:default)" refs/heads
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:default) %(authordate)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate) %(authordate:default)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:default) %(authordate:default)" refs/heads
 '
 
 test_expect_success 'Check valid format specifiers for date fields' '
-	git for-each-ref --format="%(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:relative)" refs/heads &&
-	git for-each-ref --format="%(authordate:short)" refs/heads &&
-	git for-each-ref --format="%(authordate:local)" refs/heads &&
-	git for-each-ref --format="%(authordate:iso8601)" refs/heads &&
-	git for-each-ref --format="%(authordate:rfc2822)" refs/heads
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:default)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:relative)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:short)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:local)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:iso8601)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:rfc2822)" refs/heads
 '
 
 test_expect_success 'Check invalid format specifiers are errors' '
-	test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(authordate:INVALID)" refs/heads
 '
 
 test_expect_success 'arguments to %(objectname:short=) must be positive integers' '
-	test_must_fail git for-each-ref --format="%(objectname:short=0)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=-1)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=foo)"
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(objectname:short=0)" &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(objectname:short=-1)" &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(objectname:short=foo)"
 '
 
 test_bad_atom () {
@@ -337,7 +344,7 @@ test_bad_atom () {
 
 	printf '%s\n' "$3" >expect
 	$test_do $PREREQ "err basic atom: $ref $format" '
-		test_must_fail git for-each-ref \
+		test_must_fail git ${GIT_REFS_LIST_CMD} \
 			--format="%($format)" "$ref" 2>error &&
 		test_cmp expect error
 	'
@@ -395,10 +402,10 @@ test_date () {
 	'refs/tags/testtag' '$tagger_date'
 	EOF
 	(
-		git for-each-ref --shell \
+		git ${GIT_REFS_LIST_CMD} --shell \
 			--format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
 			refs/heads &&
-		git for-each-ref --shell \
+		git ${GIT_REFS_LIST_CMD} --shell \
 			--format="%(refname) %(taggerdate${f:+:$f})" \
 			refs/tags
 	) >actual &&
@@ -429,16 +436,16 @@ test_expect_success 'Check format "default-local" date fields output' '
 # doesn't exit in error
 test_expect_success 'Check format "relative" date fields output' '
 	f=relative &&
-	(git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-	git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
+	(git ${GIT_REFS_LIST_CMD} --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
+	git ${GIT_REFS_LIST_CMD} --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
 '
 
 # We just check that this is the same as "relative" for now.
 test_expect_success 'Check format "relative-local" date fields output' '
 	test_date relative-local \
-		"$(git for-each-ref --format="%(committerdate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(authordate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(taggerdate:relative)" refs/tags)"
+		"$(git ${GIT_REFS_LIST_CMD} --format="%(committerdate:relative)" refs/heads)" \
+		"$(git ${GIT_REFS_LIST_CMD} --format="%(authordate:relative)" refs/heads)" \
+		"$(git ${GIT_REFS_LIST_CMD} --format="%(taggerdate:relative)" refs/tags)"
 '
 
 test_expect_success 'Check format "short" date fields output' '
@@ -488,7 +495,7 @@ test_expect_success 'Check format "raw-local" date fields output' '
 
 test_expect_success 'Check format of strftime date fields' '
 	echo "my date is 2006-07-04" >expected &&
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 	  --format="%(authordate:format:my date is %Y-%m-%d)" \
 	  refs/heads >actual &&
 	test_cmp expected actual
@@ -496,7 +503,7 @@ test_expect_success 'Check format of strftime date fields' '
 
 test_expect_success 'Check format of strftime-local date fields' '
 	echo "my date is 2006-07-03" >expected &&
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 	  --format="%(authordate:format-local:my date is %Y-%m-%d)" \
 	  refs/heads >actual &&
 	test_cmp expected actual
@@ -504,11 +511,11 @@ test_expect_success 'Check format of strftime-local date fields' '
 
 test_expect_success 'exercise strftime with odd fields' '
 	echo >expected &&
-	git for-each-ref --format="%(authordate:format:)" refs/heads >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:format:)" refs/heads >actual &&
 	test_cmp expected actual &&
 	long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" &&
 	echo $long >expected &&
-	git for-each-ref --format="%(authordate:format:$long)" refs/heads >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(authordate:format:$long)" refs/heads >actual &&
 	test_cmp expected actual
 '
 
@@ -519,7 +526,7 @@ refs/tags/testtag
 EOF
 
 test_expect_success 'Verify ascending sort' '
-	git for-each-ref --format="%(refname)" --sort=refname >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --sort=refname >actual &&
 	test_cmp expected actual
 '
 
@@ -531,13 +538,13 @@ refs/heads/main
 EOF
 
 test_expect_success 'Verify descending sort' '
-	git for-each-ref --format="%(refname)" --sort=-refname >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --sort=-refname >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'Give help even with invalid sort atoms' '
-	test_expect_code 129 git for-each-ref --sort=bogus -h >actual 2>&1 &&
-	grep "^usage: git for-each-ref" actual
+	test_expect_code 129 git ${GIT_REFS_LIST_CMD} --sort=bogus -h >actual 2>&1 &&
+	grep "^usage: git ${GIT_REFS_LIST_CMD}" actual
 '
 
 cat >expected <<\EOF
@@ -548,7 +555,7 @@ EOF
 test_expect_success 'exercise patterns with prefixes' '
 	git tag testtag-2 &&
 	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" \
 		refs/tags/testtag refs/tags/testtag-2 >actual &&
 	test_cmp expected actual
 '
@@ -561,7 +568,7 @@ EOF
 test_expect_success 'exercise glob patterns with prefixes' '
 	git tag testtag-2 &&
 	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" \
 		refs/tags/testtag "refs/tags/testtag-*" >actual &&
 	test_cmp expected actual
 '
@@ -578,7 +585,7 @@ test_expect_success 'exercise patterns with prefix exclusions' '
 		git tag "$tag" || return 1
 	done &&
 	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" \
 		refs/tags/ --exclude=refs/tags/foo >actual &&
 	test_cmp expected actual
 '
@@ -596,7 +603,7 @@ test_expect_success 'exercise patterns with pattern exclusions' '
 		git tag "$tag" || return 1
 	done &&
 	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" \
 		refs/tags/ --exclude="refs/tags/foo/t*" >actual &&
 	test_cmp expected actual
 '
@@ -608,17 +615,17 @@ cat >expected <<\EOF
 EOF
 
 test_expect_success 'Quoting style: shell' '
-	git for-each-ref --shell --format="%(refname)" >actual &&
+	git ${GIT_REFS_LIST_CMD} --shell --format="%(refname)" >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'Quoting style: perl' '
-	git for-each-ref --perl --format="%(refname)" >actual &&
+	git ${GIT_REFS_LIST_CMD} --perl --format="%(refname)" >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'Quoting style: python' '
-	git for-each-ref --python --format="%(refname)" >actual &&
+	git ${GIT_REFS_LIST_CMD} --python --format="%(refname)" >actual &&
 	test_cmp expected actual
 '
 
@@ -629,13 +636,13 @@ cat >expected <<\EOF
 EOF
 
 test_expect_success 'Quoting style: tcl' '
-	git for-each-ref --tcl --format="%(refname)" >actual &&
+	git ${GIT_REFS_LIST_CMD} --tcl --format="%(refname)" >actual &&
 	test_cmp expected actual
 '
 
 for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
 	test_expect_success "more than one quoting style: $i" "
-		test_must_fail git for-each-ref $i 2>err &&
+		test_must_fail git ${GIT_REFS_LIST_CMD} $i 2>err &&
 		grep '^error: more than one quoting style' err
 	"
 done
@@ -659,8 +666,8 @@ test_atom head push:track '[behind 1]'
 test_atom head push:trackshort '<'
 
 test_expect_success 'Check that :track[short] cannot be used with other atoms' '
-	test_must_fail git for-each-ref --format="%(refname:track)" 2>/dev/null &&
-	test_must_fail git for-each-ref --format="%(refname:trackshort)" 2>/dev/null
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(refname:track)" 2>/dev/null &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(refname:trackshort)" 2>/dev/null
 '
 
 test_expect_success 'Check that :track[short] works when upstream is invalid' '
@@ -670,14 +677,14 @@ test_expect_success 'Check that :track[short] works when upstream is invalid' '
 	EOF
 	test_when_finished "git config branch.main.merge refs/heads/main" &&
 	git config branch.main.merge refs/heads/does-not-exist &&
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(upstream:track)$LF%(upstream:trackshort)" \
 		refs/heads >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success 'Check for invalid refname format' '
-	test_must_fail git for-each-ref --format="%(refname:INVALID)"
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(refname:INVALID)"
 '
 
 test_expect_success 'set up color tests' '
@@ -694,24 +701,24 @@ test_expect_success 'set up color tests' '
 '
 
 test_expect_success TTY '%(color) shows color with a tty' '
-	test_terminal git for-each-ref --format="$color_format" >actual.raw &&
+	test_terminal git ${GIT_REFS_LIST_CMD} --format="$color_format" >actual.raw &&
 	test_decode_color <actual.raw >actual &&
 	test_cmp expected.color actual
 '
 
 test_expect_success '%(color) does not show color without tty' '
-	TERM=vt100 git for-each-ref --format="$color_format" >actual &&
+	TERM=vt100 git ${GIT_REFS_LIST_CMD} --format="$color_format" >actual &&
 	test_cmp expected.bare actual
 '
 
 test_expect_success '--color can override tty check' '
-	git for-each-ref --color --format="$color_format" >actual.raw &&
+	git ${GIT_REFS_LIST_CMD} --color --format="$color_format" >actual.raw &&
 	test_decode_color <actual.raw >actual &&
 	test_cmp expected.color actual
 '
 
 test_expect_success 'color.ui=always does not override tty check' '
-	git -c color.ui=always for-each-ref --format="$color_format" >actual &&
+	git -c color.ui=always ${GIT_REFS_LIST_CMD} --format="$color_format" >actual &&
 	test_cmp expected.bare actual
 '
 
@@ -732,7 +739,7 @@ test_expect_success 'describe atom vs git describe' '
 	(
 		cd describe-repo &&
 
-		git for-each-ref --format="%(objectname)" \
+		git ${GIT_REFS_LIST_CMD} --format="%(objectname)" \
 			refs/tags/ >obj &&
 		while read hash
 		do
@@ -747,7 +754,7 @@ test_expect_success 'describe atom vs git describe' '
 		test_path_exists expect-contains-good &&
 		test_path_exists expect-contains-bad &&
 
-		git for-each-ref --format="%(objectname) %(describe)" \
+		git ${GIT_REFS_LIST_CMD} --format="%(objectname) %(describe)" \
 			refs/tags/ >actual 2>err &&
 		test_cmp expect actual &&
 		test_must_be_empty err
@@ -758,7 +765,7 @@ test_expect_success 'describe:tags vs describe --tags' '
 	(
 		cd describe-repo &&
 		git describe --tags >expect &&
-		git for-each-ref --format="%(describe:tags)" \
+		git ${GIT_REFS_LIST_CMD} --format="%(describe:tags)" \
 				refs/heads/master >actual &&
 		test_cmp expect actual
 	)
@@ -772,7 +779,7 @@ test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
 		#	  recent tag reachable from it
 		test_commit --no-tag file &&
 		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
+		git ${GIT_REFS_LIST_CMD} --format="%(describe:abbrev=14)" \
 			refs/heads/master >actual &&
 		test_cmp expect actual &&
 
@@ -784,7 +791,7 @@ test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
 		#	  the name of the tag
 		git tag -a -m tagged tagname &&
 		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
+		git ${GIT_REFS_LIST_CMD} --format="%(describe:abbrev=14)" \
 			refs/heads/master >actual &&
 		test_cmp expect actual &&
 		test tagname = $(cat actual)
@@ -796,7 +803,7 @@ test_expect_success 'describe:match=... vs describe --match ...' '
 		cd describe-repo &&
 		git tag -a -m "tag foo" tag-foo &&
 		git describe --match "*-foo" >expect &&
-		git for-each-ref --format="%(describe:match="*-foo")" \
+		git ${GIT_REFS_LIST_CMD} --format="%(describe:match="*-foo")" \
 			refs/heads/master >actual &&
 		test_cmp expect actual
 	)
@@ -807,7 +814,7 @@ test_expect_success 'describe:exclude:... vs describe --exclude ...' '
 		cd describe-repo &&
 		git tag -a -m "tag bar" tag-bar &&
 		git describe --exclude "*-bar" >expect &&
-		git for-each-ref --format="%(describe:exclude="*-bar")" \
+		git ${GIT_REFS_LIST_CMD} --format="%(describe:exclude="*-bar")" \
 			refs/heads/master >actual &&
 		test_cmp expect actual
 	)
@@ -824,7 +831,7 @@ test_expect_success 'deref with describe atom' '
 
 		tagtwo
 		EOF
-		git for-each-ref --format="%(*describe)" >actual &&
+		git ${GIT_REFS_LIST_CMD} --format="%(*describe)" >actual &&
 		test_cmp expect actual
 	)
 '
@@ -837,7 +844,7 @@ test_expect_success 'err on bad describe atom arg' '
 		cat >expect <<-\EOF &&
 		fatal: unrecognized %(describe) argument: baz
 		EOF
-		test_must_fail git for-each-ref --format="%(describe:baz)" \
+		test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(describe:baz)" \
 			refs/heads/master 2>actual &&
 		test_cmp expect actual &&
 
@@ -846,7 +853,7 @@ test_expect_success 'err on bad describe atom arg' '
 		cat >expect <<-\EOF &&
 		fatal: unrecognized %(describe) argument: qux=1,abbrev=14
 		EOF
-		test_must_fail git for-each-ref \
+		test_must_fail git ${GIT_REFS_LIST_CMD} \
 			--format="%(describe:tags,qux=1,abbrev=14)" \
 			ref/heads/master 2>actual &&
 		test_cmp expect actual
@@ -866,7 +873,7 @@ test_expect_success 'Check ambiguous head and tag refs (strict)' '
 	git commit -m "Branch" &&
 	setdate_and_increment &&
 	git tag -m "Tagging at $datestamp" main &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	git ${GIT_REFS_LIST_CMD} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
 	test_cmp expected actual
 '
 
@@ -877,7 +884,7 @@ EOF
 
 test_expect_success 'Check ambiguous head and tag refs (loose)' '
 	git config --bool core.warnambiguousrefs false &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	git ${GIT_REFS_LIST_CMD} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
 	test_cmp expected actual
 '
 
@@ -890,7 +897,7 @@ test_expect_success 'Check ambiguous head and tag refs II (loose)' '
 	git checkout main &&
 	git tag ambiguous testtag^0 &&
 	git branch ambiguous testtag^0 &&
-	git for-each-ref --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
+	git ${GIT_REFS_LIST_CMD} --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
 	test_cmp expected actual
 '
 
@@ -923,7 +930,7 @@ test_expect_success 'an unusual tag with an incomplete line' '
 	bogo=$(git cat-file tag bogo) &&
 	bogo=$(printf "%s" "$bogo" | git mktag) &&
 	git tag -f bogo "$bogo" &&
-	git for-each-ref --format "%(body)" refs/tags/bogo
+	git ${GIT_REFS_LIST_CMD} --format "%(body)" refs/tags/bogo
 
 '
 
@@ -1000,7 +1007,7 @@ test_atom refs/tags/signed-empty contents "$sig"
 
 test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
 	git cat-file tag refs/tags/signed-empty >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-empty >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw)" refs/tags/signed-empty >actual &&
 	sanitize_pgp <expected >expected.clean &&
 	echo >>expected.clean &&
 	sanitize_pgp <actual >actual.clean &&
@@ -1018,7 +1025,7 @@ $sig"
 
 test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
 	git cat-file tag refs/tags/signed-short >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-short >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw)" refs/tags/signed-short >actual &&
 	sanitize_pgp <expected >expected.clean &&
 	echo >>expected.clean &&
 	sanitize_pgp <actual >actual.clean &&
@@ -1040,7 +1047,7 @@ $sig"
 
 test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
 	git cat-file tag refs/tags/signed-long >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-long >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw)" refs/tags/signed-long >actual &&
 	sanitize_pgp <expected >expected.clean &&
 	echo >>expected.clean &&
 	sanitize_pgp <actual >actual.clean &&
@@ -1062,10 +1069,10 @@ test_atom refs/mytrees/first contents ""
 test_expect_success 'basic atom: refs/mytrees/first raw' '
 	git cat-file tree refs/mytrees/first >expected &&
 	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/mytrees/first >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw)" refs/mytrees/first >actual &&
 	test_cmp expected actual &&
 	git cat-file -s refs/mytrees/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/mytrees/first >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw:size)" refs/mytrees/first >actual &&
 	test_cmp expected actual
 '
 
@@ -1079,10 +1086,10 @@ test_atom refs/myblobs/first contents ""
 test_expect_success 'basic atom: refs/myblobs/first raw' '
 	git cat-file blob refs/myblobs/first >expected &&
 	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/myblobs/first >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw)" refs/myblobs/first >actual &&
 	test_cmp expected actual &&
 	git cat-file -s refs/myblobs/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/myblobs/first >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw:size)" refs/myblobs/first >actual &&
 	test_cmp expected actual
 '
 
@@ -1127,7 +1134,7 @@ test_expect_success 'Verify sorts with raw' '
 	refs/myblobs/blob4
 	refs/heads/main
 	EOF
-	git for-each-ref --format="%(refname)" --sort=raw \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --sort=raw \
 		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
 	test_cmp expected actual
 '
@@ -1146,7 +1153,7 @@ test_expect_success 'Verify sorts with raw:size' '
 	refs/mytrees/first
 	refs/heads/main
 	EOF
-	git for-each-ref --format="%(refname)" --sort=raw:size \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --sort=raw:size \
 		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
 	test_cmp expected actual
 '
@@ -1166,7 +1173,7 @@ test_expect_success 'validate raw atom with %(if:equals)' '
 	not equals
 	not equals
 	EOF
-	git for-each-ref --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
 		refs/myblobs/ refs/heads/ >actual &&
 	test_cmp expected actual
 '
@@ -1186,7 +1193,7 @@ test_expect_success 'validate raw atom with %(if:notequals)' '
 	refs/myblobs/blob8
 	refs/myblobs/first
 	EOF
-	git for-each-ref --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
 		refs/myblobs/ refs/heads/ >actual &&
 	test_cmp expected actual
 '
@@ -1203,55 +1210,55 @@ test_expect_success 'empty raw refs with %(if)' '
 	refs/myblobs/blob8 empty
 	refs/myblobs/first not empty
 	EOF
-	git for-each-ref --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
 		refs/myblobs/ >actual &&
 	test_cmp expected actual
 '
 
 test_expect_success '%(raw) with --python must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --python
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(raw)" --python
 '
 
 test_expect_success '%(raw) with --tcl must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --tcl
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(raw)" --tcl
 '
 
 test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' '
-	git for-each-ref --format="\$name= %(raw);
+	git ${GIT_REFS_LIST_CMD} --format="\$name= %(raw);
 print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
 	cmp blob1 actual &&
-	git for-each-ref --format="\$name= %(raw);
+	git ${GIT_REFS_LIST_CMD} --format="\$name= %(raw);
 print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
 	cmp blob3 actual &&
-	git for-each-ref --format="\$name= %(raw);
+	git ${GIT_REFS_LIST_CMD} --format="\$name= %(raw);
 print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
 	cmp blob8 actual &&
-	git for-each-ref --format="\$name= %(raw);
+	git ${GIT_REFS_LIST_CMD} --format="\$name= %(raw);
 print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
 	cmp one actual &&
 	git cat-file tree refs/mytrees/first > expected &&
-	git for-each-ref --format="\$name= %(raw);
+	git ${GIT_REFS_LIST_CMD} --format="\$name= %(raw);
 print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
 	cmp expected actual
 '
 
 test_expect_success '%(raw) with --shell must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --shell
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(raw)" --shell
 '
 
 test_expect_success '%(raw) with --shell and --sort=raw must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --sort=raw --shell
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(raw)" --sort=raw --shell
 '
 
 test_expect_success '%(raw:size) with --shell' '
-	git for-each-ref --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
-	git for-each-ref --format="%(raw:size)" --shell >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
+	git ${GIT_REFS_LIST_CMD} --format="%(raw:size)" --shell >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'for-each-ref --format compare with cat-file --batch' '
+test_expect_success "${GIT_REFS_LIST_CMD} --format compare with cat-file --batch" '
 	git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
-	git for-each-ref --format="%(objectname) %(objecttype) %(objectsize)
+	git ${GIT_REFS_LIST_CMD} --format="%(objectname) %(objecttype) %(objectsize)
 %(raw)" refs/mytrees/first >actual &&
 	test_cmp expected actual
 '
@@ -1262,7 +1269,7 @@ test_expect_success 'verify sorts with contents:size' '
 	refs/heads/newtag
 	refs/heads/ambiguous
 	EOF
-	git for-each-ref --format="%(refname)" \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" \
 		--sort=contents:size refs/heads/ >actual &&
 	test_cmp expect actual
 '
@@ -1294,7 +1301,7 @@ test_expect_success 'Verify sort with multiple keys' '
 	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
 	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
 	EOF
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
 		--sort=-refname \
 		--sort=taggeremail \
@@ -1314,7 +1321,7 @@ test_expect_success 'equivalent sorts fall back on refname' '
 	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
 	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
 	EOF
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
 		--sort=taggerdate \
 		"refs/tags/multi-*" >actual &&
@@ -1332,7 +1339,7 @@ test_expect_success '--no-sort cancels the previous sort keys' '
 	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
 	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
 	EOF
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
 		--sort=-refname \
 		--sort=taggeremail \
@@ -1356,7 +1363,7 @@ test_expect_success '--no-sort without subsequent --sort prints expected refs' '
 
 	# Sort the results with `sort` for a consistent comparison against
 	# expected
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(refname)" \
 		--no-sort \
 		"refs/tags/multi-*" | sort >actual &&
@@ -1387,7 +1394,7 @@ test_expect_success 'sort by date defaults to full timestamp' '
 	1707341660 refs/tags/custom-dates-1
 	EOF
 
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(creatordate:unix) %(refname)" \
 		--sort=creatordate \
 		"refs/tags/custom-dates-*" >actual &&
@@ -1402,7 +1409,7 @@ test_expect_success 'sort by custom date format' '
 	21:34:20 refs/tags/custom-dates-1
 	EOF
 
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(creatordate:format:%H:%M:%S) %(refname)" \
 		--sort="creatordate:format:%H:%M:%S" \
 		"refs/tags/custom-dates-*" >actual &&
@@ -1411,10 +1418,10 @@ test_expect_success 'sort by custom date format' '
 
 test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
 	test_when_finished "git checkout main" &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
 	sed -e "s/^\* /  /" actual >expect &&
 	git checkout --orphan orphaned-branch &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
 	test_cmp expect actual
 '
 
@@ -1451,9 +1458,9 @@ test_trailer_option () {
 	title=$1 option=$2
 	cat >expect
 	test_expect_success $prereq "$title" '
-		git for-each-ref --format="%($option)" refs/heads/main >actual &&
+		git ${GIT_REFS_LIST_CMD} --format="%($option)" refs/heads/main >actual &&
 		test_cmp expect actual &&
-		git for-each-ref --format="%(contents:$option)" refs/heads/main >actual &&
+		git ${GIT_REFS_LIST_CMD} --format="%(contents:$option)" refs/heads/main >actual &&
 		test_cmp expect actual
 	'
 }
@@ -1583,7 +1590,7 @@ test_expect_success 'multiple %(trailers) use their own options' '
 	EOF
 	t1="%(trailers:key=one,key_value_separator=W,separator=X)" &&
 	t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" &&
-	git for-each-ref --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
 	cat >expect <<-\EOF &&
 	oneWfooXoneWbar
 	twoYbazZtwoYqux
@@ -1596,9 +1603,9 @@ test_failing_trailer_option () {
 	cat >expect
 	test_expect_success "$title" '
 		# error message cannot be checked under i18n
-		test_must_fail git for-each-ref --format="%($option)" refs/heads/main 2>actual &&
+		test_must_fail git ${GIT_REFS_LIST_CMD} --format="%($option)" refs/heads/main 2>actual &&
 		test_cmp expect actual &&
-		test_must_fail git for-each-ref --format="%(contents:$option)" refs/heads/main 2>actual &&
+		test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(contents:$option)" refs/heads/main 2>actual &&
 		test_cmp expect actual
 	'
 }
@@ -1617,14 +1624,14 @@ test_expect_success 'if arguments, %(contents:trailers) shows error if colon is
 	cat >expect <<-EOF &&
 	fatal: unrecognized %(contents) argument: trailersonly
 	EOF
-	test_must_fail git for-each-ref --format="%(contents:trailersonly)" 2>actual &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(contents:trailersonly)" 2>actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'basic atom: head contents:trailers' '
-	git for-each-ref --format="%(contents:trailers)" refs/heads/main >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(contents:trailers)" refs/heads/main >actual &&
 	sanitize_pgp <actual >actual.clean &&
-	# git for-each-ref ends with a blank line
+	# git ${GIT_REFS_LIST_CMD} ends with a blank line
 	cat >expect <<-EOF &&
 	$(cat trailers)
 
@@ -1633,23 +1640,23 @@ test_expect_success 'basic atom: head contents:trailers' '
 '
 
 test_expect_success 'basic atom: rest must fail' '
-	test_must_fail git for-each-ref --format="%(rest)" refs/heads/main
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(rest)" refs/heads/main
 '
 
 test_expect_success 'HEAD atom does not take arguments' '
-	test_must_fail git for-each-ref --format="%(HEAD:foo)" 2>err &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(HEAD:foo)" 2>err &&
 	echo "fatal: %(HEAD) does not take arguments" >expect &&
 	test_cmp expect err
 '
 
 test_expect_success 'subject atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(subject:foo)" 2>err &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(subject:foo)" 2>err &&
 	echo "fatal: unrecognized %(subject) argument: foo" >expect &&
 	test_cmp expect err
 '
 
 test_expect_success 'refname atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(refname:foo)" 2>err &&
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(refname:foo)" 2>err &&
 	echo "fatal: unrecognized %(refname) argument: foo" >expect &&
 	test_cmp expect err
 '
@@ -1673,7 +1680,7 @@ test_expect_success 'trailer parsing not fooled by --- line' '
 		echo "trailer: right" &&
 		echo
 	} >expect &&
-	git for-each-ref --format="%(trailers)" refs/heads/main >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(trailers)" refs/heads/main >actual &&
 	test_cmp expect actual
 '
 
@@ -1686,7 +1693,7 @@ refs/heads/main
 EOF
 
 test_expect_success 'Verify usage of %(symref) atom' '
-	git for-each-ref --format="%(symref)" refs/heads/sym >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref)" refs/heads/sym >actual &&
 	test_cmp expected actual
 '
 
@@ -1695,7 +1702,7 @@ heads/main
 EOF
 
 test_expect_success 'Verify usage of %(symref:short) atom' '
-	git for-each-ref --format="%(symref:short)" refs/heads/sym >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:short)" refs/heads/sym >actual &&
 	test_cmp expected actual
 '
 
@@ -1705,12 +1712,12 @@ heads/main
 EOF
 
 test_expect_success 'Verify usage of %(symref:lstrip) atom' '
-	git for-each-ref --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
 	test_cmp expected actual &&
 
-	git for-each-ref --format="%(symref:strip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:strip=2)" refs/heads/sym > actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
 	test_cmp expected actual
 '
 
@@ -1720,8 +1727,8 @@ refs/heads
 EOF
 
 test_expect_success 'Verify usage of %(symref:rstrip) atom' '
-	git for-each-ref --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
 	test_cmp expected actual
 '
 
@@ -1745,38 +1752,38 @@ test_expect_success ':remotename and :remoteref' '
 			"%(push:remoteref)=refs/heads/pushed/main"
 		do
 			echo "${pair#*=}" >expect &&
-			git for-each-ref --format="${pair%=*}" \
+			git ${GIT_REFS_LIST_CMD} --format="${pair%=*}" \
 				refs/heads/main >actual &&
 			test_cmp expect actual || exit 1
 		done &&
 		git branch push-simple &&
 		git config branch.push-simple.pushRemote from &&
-		actual="$(git for-each-ref \
+		actual="$(git ${GIT_REFS_LIST_CMD} \
 			--format="%(push:remotename),%(push:remoteref)" \
 			refs/heads/push-simple)" &&
 		test from, = "$actual"
 	)
 '
 
-test_expect_success 'for-each-ref --ignore-case ignores case' '
-	git for-each-ref --format="%(refname)" refs/heads/MAIN >actual &&
+test_expect_success "${GIT_REFS_LIST_CMD} --ignore-case ignores case" '
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" refs/heads/MAIN >actual &&
 	test_must_be_empty actual &&
 
 	echo refs/heads/main >expect &&
-	git for-each-ref --format="%(refname)" --ignore-case \
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --ignore-case \
 		refs/heads/MAIN >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'for-each-ref --omit-empty works' '
-	git for-each-ref --format="%(refname)" >actual &&
+test_expect_success "${GIT_REFS_LIST_CMD} --omit-empty works" '
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" >actual &&
 	test_line_count -gt 1 actual &&
-	git for-each-ref --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
 	echo refs/heads/main >expect &&
 	test_cmp expect actual
 '
 
-test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
+test_expect_success "${GIT_REFS_LIST_CMD} --ignore-case works on multiple sort keys" '
 	# name refs numerically to avoid case-insensitive filesystem conflicts
 	nr=0 &&
 	for email in a A b B
@@ -1789,7 +1796,7 @@ test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
 			return 1
 		done
 	done &&
-	git for-each-ref --ignore-case \
+	git ${GIT_REFS_LIST_CMD} --ignore-case \
 		--format="%(taggeremail) %(subject) %(refname)" \
 		--sort=refname \
 		--sort=subject \
@@ -1816,13 +1823,13 @@ test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
 	test_cmp expect actual
 '
 
-test_expect_success 'for-each-ref reports broken tags' '
+test_expect_success "${GIT_REFS_LIST_CMD} reports broken tags" '
 	git tag -m "good tag" broken-tag-good HEAD &&
 	git cat-file tag broken-tag-good >good &&
 	sed s/commit/blob/ <good >bad &&
 	bad=$(git hash-object -w -t tag bad) &&
 	git update-ref refs/tags/broken-tag-bad $bad &&
-	test_must_fail git for-each-ref --format="%(*objectname)" \
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(*objectname)" \
 		refs/tags/broken-tag-*
 '
 
@@ -1884,21 +1891,21 @@ test_expect_success 'set up tag with signature and trailers' '
 # use "separator=" here to suppress the terminating newline
 test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo'
 
-test_expect_success 'git for-each-ref --stdin: empty' '
+test_expect_success "git ${GIT_REFS_LIST_CMD} --stdin: empty" '
 	>in &&
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	git for-each-ref --format="%(refname)" >expect &&
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --stdin <in >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" >expect &&
 	test_cmp expect actual
 '
 
-test_expect_success 'git for-each-ref --stdin: fails if extra args' '
+test_expect_success "git ${GIT_REFS_LIST_CMD} --stdin: fails if extra args" '
 	>in &&
-	test_must_fail git for-each-ref --format="%(refname)" \
+	test_must_fail git ${GIT_REFS_LIST_CMD} --format="%(refname)" \
 		--stdin refs/heads/extra <in 2>err &&
 	grep "unknown arguments supplied with --stdin" err
 '
 
-test_expect_success 'git for-each-ref --stdin: matches' '
+test_expect_success "git ${GIT_REFS_LIST_CMD} --stdin: matches" '
 	cat >in <<-EOF &&
 	refs/tags/multi*
 	refs/heads/amb*
@@ -1917,24 +1924,24 @@ test_expect_success 'git for-each-ref --stdin: matches' '
 	refs/tags/multiline
 	EOF
 
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --stdin <in >actual &&
 	test_cmp expect actual
 '
 
-test_expect_success 'git for-each-ref with non-existing refs' '
+test_expect_success "git ${GIT_REFS_LIST_CMD} with non-existing refs" '
 	cat >in <<-EOF &&
 	refs/heads/this-ref-does-not-exist
 	refs/tags/bogus
 	EOF
 
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
+	git ${GIT_REFS_LIST_CMD} --format="%(refname)" --stdin <in >actual &&
 	test_must_be_empty actual &&
 
-	xargs git for-each-ref --format="%(refname)" <in >actual &&
+	xargs git ${GIT_REFS_LIST_CMD} --format="%(refname)" <in >actual &&
 	test_must_be_empty actual
 '
 
-test_expect_success 'git for-each-ref with nested tags' '
+test_expect_success "git ${GIT_REFS_LIST_CMD} with nested tags" '
 	git tag -am "Normal tag" nested/base HEAD &&
 	git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
 	git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
@@ -1950,14 +1957,14 @@ test_expect_success 'git for-each-ref with nested tags' '
 	refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
 	EOF
 
-	git for-each-ref \
+	git ${GIT_REFS_LIST_CMD} \
 		--format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
 		refs/tags/nested/ >actual &&
 	test_cmp expect actual
 '
 
 test_expect_success 'is-base atom with non-commits' '
-	git for-each-ref --format="%(is-base:HEAD) %(refname)" >out 2>err &&
+	git ${GIT_REFS_LIST_CMD} --format="%(is-base:HEAD) %(refname)" >out 2>err &&
 	grep "(HEAD) refs/heads/main" out &&
 
 	test_line_count = 2 err &&
@@ -2034,7 +2041,7 @@ test_expect_success GPGSSH 'setup for signature atom using ssh' '
 test_expect_success GPG2 'bare signature atom' '
 	git verify-commit first-signed 2>expect &&
 	echo  >>expect &&
-	git for-each-ref refs/tags/first-signed \
+	git ${GIT_REFS_LIST_CMD} refs/tags/first-signed \
 		--format="%(signature)" >actual &&
 	test_cmp expect actual
 '
@@ -2048,7 +2055,7 @@ test_expect_success GPG 'show good signature with custom format' '
 	73D758744BE721698EC54E8713B6F51ECDDE430D
 	73D758744BE721698EC54E8713B6F51ECDDE430D
 	EOF
-	git for-each-ref refs/tags/first-signed \
+	git ${GIT_REFS_LIST_CMD} refs/tags/first-signed \
 		--format="$GRADE_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2063,7 +2070,7 @@ test_expect_success GPGSSH 'show good signature with custom format with ssh' '
 
 	EOF
 	sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
-	git for-each-ref refs/tags/eighth-signed-ssh \
+	git ${GIT_REFS_LIST_CMD} refs/tags/eighth-signed-ssh \
 		--format="$GRADE_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2082,7 +2089,7 @@ test_expect_success GPG 'signature atom with grade option and bad signature' '
 
 
 	EOF
-	git for-each-ref refs/tags/third-signed \
+	git ${GIT_REFS_LIST_CMD} refs/tags/third-signed \
 		--format="$GRADE_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2095,7 +2102,7 @@ test_expect_success GPG 'show untrusted signature with custom format' '
 	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
 	D4BE22311AD3131E5EDA29A461092E85B7227189
 	EOF
-	git for-each-ref refs/tags/fourth-signed \
+	git ${GIT_REFS_LIST_CMD} refs/tags/fourth-signed \
 		--format="$GRADE_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2108,7 +2115,7 @@ test_expect_success GPG 'show untrusted signature with undefined trust level' '
 	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
 	D4BE22311AD3131E5EDA29A461092E85B7227189
 	EOF
-	git for-each-ref refs/tags/fourth-signed \
+	git ${GIT_REFS_LIST_CMD} refs/tags/fourth-signed \
 		--format="$TRUSTLEVEL_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2121,7 +2128,7 @@ test_expect_success GPG 'show untrusted signature with ultimate trust level' '
 	73D758744BE721698EC54E8713B6F51ECDDE430D
 	73D758744BE721698EC54E8713B6F51ECDDE430D
 	EOF
-	git for-each-ref refs/tags/sixth-signed \
+	git ${GIT_REFS_LIST_CMD} refs/tags/sixth-signed \
 		--format="$TRUSTLEVEL_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2134,7 +2141,7 @@ test_expect_success GPG 'show unknown signature with custom format' '
 
 
 	EOF
-	GNUPGHOME="$GNUPGHOME_NOT_USED" git for-each-ref \
+	GNUPGHOME="$GNUPGHOME_NOT_USED" git ${GIT_REFS_LIST_CMD} \
 		refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual &&
 	test_cmp expect actual
 '
@@ -2147,7 +2154,7 @@ test_expect_success GPG 'show lack of signature with custom format' '
 
 
 	EOF
-	git for-each-ref refs/tags/seventh-unsigned \
+	git ${GIT_REFS_LIST_CMD} refs/tags/seventh-unsigned \
 		--format="$GRADE_FORMAT" >actual &&
 	test_cmp expect actual
 '
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v2 1/2] builtin/refs: add list subcommand
  2025-07-17  7:50   ` [GSoC][RFC PATCH v2 1/2] builtin/refs: add " Meet Soni
@ 2025-07-17 16:48     ` Eric Sunshine
  2025-07-23  5:01       ` Meet Soni
  0 siblings, 1 reply; 65+ messages in thread
From: Eric Sunshine @ 2025-07-17 16:48 UTC (permalink / raw)
  To: Meet Soni
  Cc: git, ps, shejialuo, karthik.188, gitster, Christian Couder,
	Victoria Dye
On Thu, Jul 17, 2025 at 3:50 AM Meet Soni <meetsoni3017@gmail.com> wrote:
> Git's reference management is distributed across multiple commands. As
> part of an ongoing effort to consolidate and modernize reference
> handling, introduce a `list` subcommand under the `git refs` umbrella as
> a replacement for `git for-each-ref`.
>
> Implement `cmd_refs_list` as a thin wrapper around `cmd_for_each_ref`
> instead of duplicating its logic. Forward all arguments to the existing
> function to ensure behavior is identical.
>
> Add documentation for the new command. To keep the documentation DRY and
> consistent with `git-for-each-ref(1)`, refactor the shared command
> options into a standalone file. Use the AsciiDoc `include::` macro to
> embed these options in both man pages.
>
> This prevents duplication in both code and documentation, ensuring that
> `refs list` benefits from any future fixes to the underlying
> `for-each-ref` machinery and its shared documentation.
>
> Mentored-by: Patrick Steinhardt <ps@pks.im>
> Mentored-by: shejialuo <shejialuo@gmail.com>
> Mentored-by: Karthik Nayak <karthik.188@gmail.com>
> Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
> ---
> diff --git a/Documentation/refs-list-options.adoc b/Documentation/refs-list-options.adoc
> @@ -0,0 +1,80 @@
> +// Shared options for for-each-ref and refs list
> +<pattern>...::
> +       If one or more patterns are given, only refs are shown that
> +       match against at least one pattern, either using fnmatch(3) or
> +       literally, in the latter case matching completely or from the
> +       beginning up to a slash.
Nit: We probably don't want the "// Shared options for..." comment. If
someone needs to know which commands share this documentation, a
suitable "grep" invocation will provide the same information but will
be correct at that point in time, whereas a comment like this one can
become stale over time.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v2 2/2] t: add test for git refs list subcommand
  2025-07-17  7:50   ` [GSoC][RFC PATCH v2 2/2] t: add test for git refs " Meet Soni
@ 2025-07-17 21:01     ` Junio C Hamano
  2025-07-23  5:17       ` Meet Soni
  0 siblings, 1 reply; 65+ messages in thread
From: Junio C Hamano @ 2025-07-17 21:01 UTC (permalink / raw)
  To: Meet Soni
  Cc: git, ps, shejialuo, karthik.188, Taylor Blau, Jeff King,
	Kousik Sanagavarapu
Meet Soni <meetsoni3017@gmail.com> writes:
> +. ./test-lib.sh
> +
> +GIT_REFS_LIST_CMD='refs list'
> +. "$TEST_DIRECTORY"/t6300-for-each-ref.sh
> ...
> -	git for-each-ref refs/tags/fourth-signed \
> +	git ${GIT_REFS_LIST_CMD} refs/tags/fourth-signed \
I know where your bias comes from ;-) but if this were
> -	git for-each-ref refs/tags/fourth-signed \
> +	$git_for_each_ref refs/tags/fourth-signed \
it would have been easier to read the resulting test, as
t6300-for-each-ref is and has been primarily about "git
for-each-ref" command, and the new test script that overrides
    git_for_each_ref="git refs list"
before including t6300 would be a good demonstration that the new
"git ref list" command can stand in as its replacement.
> +GIT_REFS_LIST_CMD='refs list'
> +. "$TEST_DIRECTORY"/t6300-for-each-ref.sh
> diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
> index ce9af79ab1..74a030371c 100755
> --- a/t/t6300-for-each-ref.sh
> +++ b/t/t6300-for-each-ref.sh
> @@ -5,7 +5,9 @@
>  
>  test_description='for-each-ref test'
>  
> -. ./test-lib.sh
> +. "${TEST_DIRECTORY:-.}/test-lib.sh"
This is probably wrong.
Nobody promises that including test-lib.sh is and will forever be
idempotent.  While this patch may not give t6300 a serious
regression right now, I am not sure what future subtle breakage we
are looking at for t1461.
Probably this should model itself after how 8752d11d (git-blame: Use
the same tests for git-blame as for git-annotate, 2006-03-05) moved
a lot from existing t8001 to annotate-tests and so that they can be
shared with new t8002.
Thanks.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v2 1/2] builtin/refs: add list subcommand
  2025-07-17 16:48     ` Eric Sunshine
@ 2025-07-23  5:01       ` Meet Soni
  0 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-07-23  5:01 UTC (permalink / raw)
  To: Eric Sunshine
  Cc: git, ps, shejialuo, karthik.188, gitster, Christian Couder,
	Victoria Dye
On Thu, 17 Jul 2025 at 22:18, Eric Sunshine <sunshine@sunshineco.com> wrote:
>
> On Thu, Jul 17, 2025 at 3:50 AM Meet Soni <meetsoni3017@gmail.com> wrote:
[snip]
> > diff --git a/Documentation/refs-list-options.adoc b/Documentation/refs-list-options.adoc
> > @@ -0,0 +1,80 @@
> > +// Shared options for for-each-ref and refs list
> > +<pattern>...::
> > +       If one or more patterns are given, only refs are shown that
> > +       match against at least one pattern, either using fnmatch(3) or
> > +       literally, in the latter case matching completely or from the
> > +       beginning up to a slash.
>
> Nit: We probably don't want the "// Shared options for..." comment. If
> someone needs to know which commands share this documentation, a
> suitable "grep" invocation will provide the same information but will
> be correct at that point in time, whereas a comment like this one can
> become stale over time.
Makes sense, I'll remove it in the next version.
Apologies for the delayed response, I was away last week.
Thanks
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v2 2/2] t: add test for git refs list subcommand
  2025-07-17 21:01     ` Junio C Hamano
@ 2025-07-23  5:17       ` Meet Soni
  2025-07-23 15:03         ` Junio C Hamano
  0 siblings, 1 reply; 65+ messages in thread
From: Meet Soni @ 2025-07-23  5:17 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: git, ps, shejialuo, karthik.188, Taylor Blau, Jeff King,
	Kousik Sanagavarapu
On Fri, 18 Jul 2025 at 02:31, Junio C Hamano <gitster@pobox.com> wrote:
>
> Meet Soni <meetsoni3017@gmail.com> writes:
>
> > +. ./test-lib.sh
> > +
> > +GIT_REFS_LIST_CMD='refs list'
> > +. "$TEST_DIRECTORY"/t6300-for-each-ref.sh
> > ...
> > -     git for-each-ref refs/tags/fourth-signed \
> > +     git ${GIT_REFS_LIST_CMD} refs/tags/fourth-signed \
>
> I know where your bias comes from ;-) but if this were
>
> > -     git for-each-ref refs/tags/fourth-signed \
> > +     $git_for_each_ref refs/tags/fourth-signed \
>
> it would have been easier to read the resulting test, as
> t6300-for-each-ref is and has been primarily about "git
> for-each-ref" command, and the new test script that overrides
>
>     git_for_each_ref="git refs list"
>
> before including t6300 would be a good demonstration that the new
> "git ref list" command can stand in as its replacement.
>
> > +GIT_REFS_LIST_CMD='refs list'
> > +. "$TEST_DIRECTORY"/t6300-for-each-ref.sh
> > diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
> > index ce9af79ab1..74a030371c 100755
> > --- a/t/t6300-for-each-ref.sh
> > +++ b/t/t6300-for-each-ref.sh
> > @@ -5,7 +5,9 @@
> >
> >  test_description='for-each-ref test'
> >
> > -. ./test-lib.sh
> > +. "${TEST_DIRECTORY:-.}/test-lib.sh"
>
> This is probably wrong.
>
> Nobody promises that including test-lib.sh is and will forever be
> idempotent.  While this patch may not give t6300 a serious
> regression right now, I am not sure what future subtle breakage we
> are looking at for t1461.
>
> Probably this should model itself after how 8752d11d (git-blame: Use
> the same tests for git-blame as for git-annotate, 2006-03-05) moved
> a lot from existing t8001 to annotate-tests and so that they can be
> shared with new t8002.
>
> Thanks.
Apologies for the delayed response, I was away last week.
Thanks for the review. The suggestions for the test suite make sense.
I'll refactor it accordingly and send out a v3.
Thanks.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v3 0/3] Add refs list subcommand
  2025-07-17  7:50 ` [GSoC][RFC PATCH v2 " Meet Soni
  2025-07-17  7:50   ` [GSoC][RFC PATCH v2 1/2] builtin/refs: add " Meet Soni
  2025-07-17  7:50   ` [GSoC][RFC PATCH v2 2/2] t: add test for git refs " Meet Soni
@ 2025-07-23  6:43   ` Meet Soni
  2025-07-23  6:43     ` [GSoC][RFC PATCH v3 1/3] builtin/refs: add " Meet Soni
                       ` (3 more replies)
  2 siblings, 4 replies; 65+ messages in thread
From: Meet Soni @ 2025-07-23  6:43 UTC (permalink / raw)
  To: git; +Cc: ps, shejialuo, karthik.188, gitster, sunshine, Meet Soni
Hello everyone,
This is the third version of the patch series that introduces the `git
refs list` subcommand as a modern alternative to `git for-each-ref`.
Thank you to everyone who provided valuable feedback on the patch
series.
Changes in v3:
  - Restructured the patch series from two commits into three, providing
    a cleaner separation between the feature implementation, the test
    refactoring, and the new test itself.
  - Reworked the testing approach to follow the established pattern for
    shared tests (like git-blame/annotate). The core test logic now
    resides in a shared file (`for-each-ref-tests.sh`), which is sourced
    by both the original `t6300-for-each-ref.sh` and the new
    `t1461-refs-list.sh` test drivers.
  - Adopted the suggested `$git_for_each_ref` variable convention in the
    test suite for better clarity and to demonstrate the drop-in
    compatibility.
  - Removed unwanted comment.
---
(v1 cover-letter text)
This patch series introduces `git refs list` as a modern replacement for
`git for-each-ref`, as part of an effort to consolidate ref-related
functionality under a unified `git refs` command.
Git's ref-related operations are currently handled by several distinct
commands, such as `git show-ref`, `git for-each-ref`, `git update-ref`,
`git pack-refs`, etc. This distribution has a few practical drawbacks:
- Users need to rely on multiple commands for related tasks involving
  refs.
- The commands may differ slightly in behavior and option syntax,
  leading to inconsistency.
We propose a long-term consolidation effort to bring ref-related
subcommands under the umbrella of a single command: `git refs`.
The implementation of `git refs list` is functionally identical to `git
for-each-ref`. It reuses the same internal logic (cmd_for_each_ref) to
ensure complete backward compatibility. The purpose of this patch is not
to introduce new behavior but to provide an alternate entry point under
the consolidated `git refs` namespace.
The motivation behind this change is twofold:
- Consolidation: Centralizing ref-related operations makes them easier
  to discover, use, and maintain.
- Evolution: While the initial goal is parity with existing commands,
  this consolidation allows for careful reconsideration of which
  features are essential. Over time, we can:
  - Remove legacy or obscure options that are no longer needed.
  - Add improvements that wouldn't make sense to bolt onto legacy
    commands.
  - Offering a more consistent and user-friendly surface.
To verify backward compatibility, this patch also includes a test
`t/t1461-refs-list.sh`, which runs the full `t6300-for-each-ref.sh` test
using `git refs list`. The test uses ${GIT_REFS_LIST_CMD:-for-each-ref}
to allow substitution without duplicating tests.
This patch is deliberately conservative: it introduces no behavioral
changes and leaves `for-each-ref` untouched. The goal is to lay
groundwork and demonstrate viability of ref consolidation within `git
refs`.
Going forward, I'd like to initiate a discussion on what the ideal
surface of `git refs list` should look like. Which options and features
from `for-each-ref` should be carried over? Are there any that are
obsolete or overly niche? What improvements might be worth considering
now that we have a new, consolidated interface?
Feedback on this, especially from those who rely on `for-each-ref` in
scripts or tooling would be very helpful.
Meet Soni (3):
  builtin/refs: add list subcommand
  t6300: refactor tests to be shareable
  t: add test for git refs list subcommand
 Documentation/git-for-each-ref.adoc  |   80 +-
 Documentation/git-refs.adoc          |   16 +
 Documentation/refs-list-options.adoc |   79 +
 builtin/for-each-ref.c               |   24 +-
 builtin/refs.c                       |   35 +
 t/for-each-ref-tests.sh              | 2141 ++++++++++++++++++++++++++
 t/meson.build                        |    1 +
 t/t1461-refs-list.sh                 |    8 +
 t/t6300-for-each-ref.sh              | 2140 +------------------------
 9 files changed, 2303 insertions(+), 2221 deletions(-)
 create mode 100644 Documentation/refs-list-options.adoc
 create mode 100644 t/for-each-ref-tests.sh
 create mode 100755 t/t1461-refs-list.sh
Range-diff against v2:
1:  b2d3026520 ! 1:  547b0bbf8f builtin/refs: add list subcommand
    @@ Documentation/git-refs.adoc: The following options are specific to 'git refs ver
     
      ## Documentation/refs-list-options.adoc (new) ##
     @@
    -+// Shared options for for-each-ref and refs list
     +<pattern>...::
     +	If one or more patterns are given, only refs are shown that
     +	match against at least one pattern, either using fnmatch(3) or
2:  2d6534841f < -:  ---------- t: add test for git refs list subcommand
-:  ---------- > 2:  5d5057ff98 t6300: refactor tests to be shareable
-:  ---------- > 3:  b9cb9cdf48 t: add test for git refs list subcommand
-- 
2.34.1
^ permalink raw reply	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v3 1/3] builtin/refs: add list subcommand
  2025-07-23  6:43   ` [GSoC][RFC PATCH v3 0/3] Add " Meet Soni
@ 2025-07-23  6:43     ` Meet Soni
  2025-07-24  5:58       ` Patrick Steinhardt
  2025-07-23  6:43     ` [GSoC][RFC PATCH v3 2/3] t6300: refactor tests to be shareable Meet Soni
                       ` (2 subsequent siblings)
  3 siblings, 1 reply; 65+ messages in thread
From: Meet Soni @ 2025-07-23  6:43 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, Meet Soni,
	Taylor Blau, Christian Couder, Victoria Dye
Git's reference management is distributed across multiple commands. As
part of an ongoing effort to consolidate and modernize reference
handling, introduce a `list` subcommand under the `git refs` umbrella as
a replacement for `git for-each-ref`.
Implement `cmd_refs_list` as a thin wrapper around `cmd_for_each_ref`
instead of duplicating its logic. Forward all arguments to the existing
function to ensure behavior is identical.
Add documentation for the new command. To keep the documentation DRY and
consistent with `git-for-each-ref(1)`, refactor the shared command
options into a standalone file. Use the AsciiDoc `include::` macro to
embed these options in both man pages.
This prevents duplication in both code and documentation, ensuring that
`refs list` benefits from any future fixes to the underlying
`for-each-ref` machinery and its shared documentation.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 Documentation/git-for-each-ref.adoc  | 80 +---------------------------
 Documentation/git-refs.adoc          | 16 ++++++
 Documentation/refs-list-options.adoc | 79 +++++++++++++++++++++++++++
 builtin/for-each-ref.c               | 24 +++++++--
 builtin/refs.c                       | 35 ++++++++++++
 5 files changed, 151 insertions(+), 83 deletions(-)
 create mode 100644 Documentation/refs-list-options.adoc
diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc
index 5ef89fc0fe..f7bbc1902a 100644
--- a/Documentation/git-for-each-ref.adoc
+++ b/Documentation/git-for-each-ref.adoc
@@ -28,85 +28,7 @@ host language allowing their direct evaluation in that language.
 
 OPTIONS
 -------
-<pattern>...::
-	If one or more patterns are given, only refs are shown that
-	match against at least one pattern, either using fnmatch(3) or
-	literally, in the latter case matching completely or from the
-	beginning up to a slash.
-
---stdin::
-	If `--stdin` is supplied, then the list of patterns is read from
-	standard input instead of from the argument list.
-
---count=<count>::
-	By default the command shows all refs that match
-	`<pattern>`.  This option makes it stop after showing
-	that many refs.
-
---sort=<key>::
-	A field name to sort on.  Prefix `-` to sort in
-	descending order of the value.  When unspecified,
-	`refname` is used.  You may use the --sort=<key> option
-	multiple times, in which case the last key becomes the primary
-	key.
-
---format=<format>::
-	A string that interpolates `%(fieldname)` from a ref being shown and
-	the object it points at. In addition, the string literal `%%`
-	renders as `%` and `%xx` - where `xx` are hex digits - renders as
-	the character with hex code `xx`. For example, `%00` interpolates to
-	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
-+
-When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
-TAB %(refname)`.
-
---color[=<when>]::
-	Respect any colors specified in the `--format` option. The
-	`<when>` field must be one of `always`, `never`, or `auto` (if
-	`<when>` is absent, behave as if `always` was given).
-
---shell::
---perl::
---python::
---tcl::
-	If given, strings that substitute `%(fieldname)`
-	placeholders are quoted as string literals suitable for
-	the specified host language.  This is meant to produce
-	a scriptlet that can directly be `eval`ed.
-
---points-at=<object>::
-	Only list refs which points at the given object.
-
---merged[=<object>]::
-	Only list refs whose tips are reachable from the
-	specified commit (HEAD if not specified).
-
---no-merged[=<object>]::
-	Only list refs whose tips are not reachable from the
-	specified commit (HEAD if not specified).
-
---contains[=<object>]::
-	Only list refs which contain the specified commit (HEAD if not
-	specified).
-
---no-contains[=<object>]::
-	Only list refs which don't contain the specified commit (HEAD
-	if not specified).
-
---ignore-case::
-	Sorting and filtering refs are case insensitive.
-
---omit-empty::
-	Do not print a newline after formatted refs where the format expands
-	to the empty string.
-
---exclude=<pattern>::
-	If one or more patterns are given, only refs which do not match
-	any excluded pattern(s) are shown. Matching is done using the
-	same rules as `<pattern>` above.
-
---include-root-refs::
-	List root refs (HEAD and pseudorefs) apart from regular refs.
+include::refs-list-options.adoc[]
 
 FIELD NAMES
 -----------
diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
index 4d6dc994f9..ded90f435b 100644
--- a/Documentation/git-refs.adoc
+++ b/Documentation/git-refs.adoc
@@ -11,6 +11,13 @@ SYNOPSIS
 [synopsis]
 git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
 git refs verify [--strict] [--verbose]
+git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
+	      [(--sort=<key>)...] [--format=<format>]
+	      [--include-root-refs] [ --stdin | <pattern>... ]
+	      [--points-at=<object>]
+	      [--merged[=<object>]] [--no-merged[=<object>]]
+	      [--contains[=<object>]] [--no-contains[=<object>]]
+	      [--exclude=<pattern> ...]
 
 DESCRIPTION
 -----------
@@ -26,6 +33,11 @@ migrate::
 verify::
 	Verify reference database consistency.
 
+list::
+	List references in the repository with support for filtering,
+	formatting, and sorting. This subcommand is an alias for
+	linkgit:git-for-each-ref[1] and offers identical functionality.
+
 OPTIONS
 -------
 
@@ -57,6 +69,10 @@ The following options are specific to 'git refs verify':
 --verbose::
 	When verifying the reference database consistency, be chatty.
 
+The following options are specific to 'git refs list':
+
+include::refs-list-options.adoc[]
+
 KNOWN LIMITATIONS
 -----------------
 
diff --git a/Documentation/refs-list-options.adoc b/Documentation/refs-list-options.adoc
new file mode 100644
index 0000000000..5f3a85bf64
--- /dev/null
+++ b/Documentation/refs-list-options.adoc
@@ -0,0 +1,79 @@
+<pattern>...::
+	If one or more patterns are given, only refs are shown that
+	match against at least one pattern, either using fnmatch(3) or
+	literally, in the latter case matching completely or from the
+	beginning up to a slash.
+
+--stdin::
+	If `--stdin` is supplied, then the list of patterns is read from
+	standard input instead of from the argument list.
+
+--count=<count>::
+	By default the command shows all refs that match
+	`<pattern>`.  This option makes it stop after showing
+	that many refs.
+
+--sort=<key>::
+	A field name to sort on.  Prefix `-` to sort in
+	descending order of the value.  When unspecified,
+	`refname` is used.  You may use the --sort=<key> option
+	multiple times, in which case the last key becomes the primary
+	key.
+
+--format=<format>::
+	A string that interpolates `%(fieldname)` from a ref being shown and
+	the object it points at. In addition, the string literal `%%`
+	renders as `%` and `%xx` - where `xx` are hex digits - renders as
+	the character with hex code `xx`. For example, `%00` interpolates to
+	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
++
+When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
+TAB %(refname)`.
+
+--color[=<when>]::
+	Respect any colors specified in the `--format` option. The
+	`<when>` field must be one of `always`, `never`, or `auto` (if
+	`<when>` is absent, behave as if `always` was given).
+
+--shell::
+--perl::
+--python::
+--tcl::
+	If given, strings that substitute `%(fieldname)`
+	placeholders are quoted as string literals suitable for
+	the specified host language.  This is meant to produce
+	a scriptlet that can directly be `eval`ed.
+
+--points-at=<object>::
+	Only list refs which points at the given object.
+
+--merged[=<object>]::
+	Only list refs whose tips are reachable from the
+	specified commit (HEAD if not specified).
+
+--no-merged[=<object>]::
+	Only list refs whose tips are not reachable from the
+	specified commit (HEAD if not specified).
+
+--contains[=<object>]::
+	Only list refs which contain the specified commit (HEAD if not
+	specified).
+
+--no-contains[=<object>]::
+	Only list refs which don't contain the specified commit (HEAD
+	if not specified).
+
+--ignore-case::
+	Sorting and filtering refs are case insensitive.
+
+--omit-empty::
+	Do not print a newline after formatted refs where the format expands
+	to the empty string.
+
+--exclude=<pattern>::
+	If one or more patterns are given, only refs which do not match
+	any excluded pattern(s) are shown. Matching is done using the
+	same rules as `<pattern>` above.
+
+--include-root-refs::
+	List root refs (HEAD and pseudorefs) apart from regular refs.
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 3d2207ec77..d7d8279049 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -16,11 +16,27 @@ static char const * const for_each_ref_usage[] = {
 	NULL
 };
 
+#define REFS_LIST_USAGE \
+	N_("git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n" \
+	   "              [(--sort=<key>)...] [--format=<format>]\n" \
+	   "              [--include-root-refs] [ --stdin | <pattern>... ]\n" \
+	   "              [--points-at=<object>]\n" \
+	   "              [--merged[=<object>]] [--no-merged[=<object>]]\n" \
+	   "              [--contains[=<object>]] [--no-contains[=<object>]]\n" \
+	   "              [--exclude=<pattern> ...]")
+
+static char const * const refs_list_usage[] = {
+	REFS_LIST_USAGE,
+	NULL
+};
+
 int cmd_for_each_ref(int argc,
 		     const char **argv,
 		     const char *prefix,
 		     struct repository *repo)
 {
+	int cmd_is_refs_list = !strcmp(argv[0], "refs list");
+	const char *const *opt_usage = cmd_is_refs_list ? refs_list_usage : for_each_ref_usage;
 	struct ref_sorting *sorting;
 	struct string_list sorting_options = STRING_LIST_INIT_DUP;
 	int icase = 0, include_root_refs = 0, from_stdin = 0;
@@ -67,17 +83,17 @@ int cmd_for_each_ref(int argc,
 	/* Set default (refname) sorting */
 	string_list_append(&sorting_options, "refname");
 
-	parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
+	parse_options(argc, argv, prefix, opts, opt_usage, 0);
 	if (format.array_opts.max_count < 0) {
 		error("invalid --count argument: `%d'", format.array_opts.max_count);
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(opt_usage, opts);
 	}
 	if (HAS_MULTI_BITS(format.quote_style)) {
 		error("more than one quoting style?");
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(opt_usage, opts);
 	}
 	if (verify_ref_format(&format))
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(opt_usage, opts);
 
 	sorting = ref_sorting_options(&sorting_options);
 	ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
diff --git a/builtin/refs.c b/builtin/refs.c
index 998d2a2c1c..41e29d1b5f 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -3,6 +3,7 @@
 #include "config.h"
 #include "fsck.h"
 #include "parse-options.h"
+#include "ref-filter.h"
 #include "refs.h"
 #include "strbuf.h"
 #include "worktree.h"
@@ -13,6 +14,15 @@
 #define REFS_VERIFY_USAGE \
 	N_("git refs verify [--strict] [--verbose]")
 
+#define REFS_LIST_USAGE \
+	N_("git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n" \
+	   "              [(--sort=<key>)...] [--format=<format>]\n" \
+	   "              [--include-root-refs] [ --stdin | <pattern>... ]\n" \
+	   "              [--points-at=<object>]\n" \
+	   "              [--merged[=<object>]] [--no-merged[=<object>]]\n" \
+	   "              [--contains[=<object>]] [--no-contains[=<object>]]\n" \
+	   "              [--exclude=<pattern> ...]")
+
 static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
 			    struct repository *repo UNUSED)
 {
@@ -101,6 +111,29 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
 	return ret;
 }
 
+static int cmd_refs_list(int argc, const char **argv, const char *prefix,
+		  struct repository *repo)
+{
+	struct strvec args = STRVEC_INIT;
+	const char **args_copy;
+	int ret;
+
+	strvec_push(&args, "refs list");
+
+	for (int i = 1; i < argc; i++)
+		strvec_push(&args, argv[i]);
+
+	CALLOC_ARRAY(args_copy, args.nr + 1);
+	COPY_ARRAY(args_copy, args.v, args.nr);
+
+	ret = cmd_for_each_ref(args.nr, args_copy, prefix, repo);
+
+	strvec_clear(&args);
+	free(args_copy);
+
+	return ret;
+}
+
 int cmd_refs(int argc,
 	     const char **argv,
 	     const char *prefix,
@@ -109,12 +142,14 @@ int cmd_refs(int argc,
 	const char * const refs_usage[] = {
 		REFS_MIGRATE_USAGE,
 		REFS_VERIFY_USAGE,
+		REFS_LIST_USAGE,
 		NULL,
 	};
 	parse_opt_subcommand_fn *fn = NULL;
 	struct option opts[] = {
 		OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate),
 		OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify),
+		OPT_SUBCOMMAND("list", &fn, cmd_refs_list),
 		OPT_END(),
 	};
 
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v3 2/3] t6300: refactor tests to be shareable
  2025-07-23  6:43   ` [GSoC][RFC PATCH v3 0/3] Add " Meet Soni
  2025-07-23  6:43     ` [GSoC][RFC PATCH v3 1/3] builtin/refs: add " Meet Soni
@ 2025-07-23  6:43     ` Meet Soni
  2025-07-23  6:43     ` [GSoC][RFC PATCH v3 3/3] t: add test for git refs list subcommand Meet Soni
  2025-07-31  9:00     ` [GSoC][RFC PATCH v4 0/5] Add " Meet Soni
  3 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-07-23  6:43 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, Meet Soni,
	Kousik Sanagavarapu, Taylor Blau, Jeff King, Hariom Verma
In preparation for adding tests for the new `git refs list` command,
refactor the existing t6300 test suite to make its logic shareable.
Move the core test logic from `t6300-for-each-ref.sh` into a new
`for-each-ref-tests.sh` file. Inside this new script, replace hardcoded
calls to "git for-each-ref" with the `$git_for_each_ref` variable.
The original `t6300-for-each-ref.sh` script now becomes a simple
"driver". It is responsible for setting the default value of the
variable and then sourcing the test library.
This new structure follows the established pattern used for sharing
tests between `git-blame` and `git-annotate` and prepares the test suite
for the `refs list` tests to be added in a subsequent commit.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 t/for-each-ref-tests.sh | 2141 +++++++++++++++++++++++++++++++++++++++
 t/t6300-for-each-ref.sh | 2140 +-------------------------------------
 2 files changed, 2143 insertions(+), 2138 deletions(-)
 create mode 100644 t/for-each-ref-tests.sh
diff --git a/t/for-each-ref-tests.sh b/t/for-each-ref-tests.sh
new file mode 100644
index 0000000000..e3ad19298a
--- /dev/null
+++ b/t/for-each-ref-tests.sh
@@ -0,0 +1,2141 @@
+git_for_each_ref=${git_for_each_ref:-git for-each-ref}
+GNUPGHOME_NOT_USED=$GNUPGHOME
+. "$TEST_DIRECTORY"/lib-gpg.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
+
+# Mon Jul 3 23:18:43 2006 +0000
+datestamp=1151968723
+setdate_and_increment () {
+    GIT_COMMITTER_DATE="$datestamp +0200"
+    datestamp=$(expr "$datestamp" + 1)
+    GIT_AUTHOR_DATE="$datestamp +0200"
+    datestamp=$(expr "$datestamp" + 1)
+    export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+}
+
+test_object_file_size () {
+	oid=$(git rev-parse "$1")
+	path=".git/objects/$(test_oid_to_path $oid)"
+	test_file_size "$path"
+}
+
+test_expect_success setup '
+	# setup .mailmap
+	cat >.mailmap <<-EOF &&
+	A Thor <athor@example.com> A U Thor <author@example.com>
+	C Mitter <cmitter@example.com> C O Mitter <committer@example.com>
+	EOF
+
+	setdate_and_increment &&
+	echo "Using $datestamp" > one &&
+	git add one &&
+	git commit -m "Initial" &&
+	git branch -M main &&
+	setdate_and_increment &&
+	git tag -a -m "Tagging at $datestamp" testtag &&
+	git update-ref refs/remotes/origin/main main &&
+	git remote add origin nowhere &&
+	git config branch.main.remote origin &&
+	git config branch.main.merge refs/heads/main &&
+	git remote add myfork elsewhere &&
+	git config remote.pushdefault myfork &&
+	git config push.default current
+'
+
+test_atom () {
+	case "$1" in
+		head) ref=refs/heads/main ;;
+		 tag) ref=refs/tags/testtag ;;
+		 sym) ref=refs/heads/sym ;;
+		   *) ref=$1 ;;
+	esac
+	format=$2
+	test_do=test_expect_${4:-success}
+
+	printf '%s\n' "$3" >expected
+	$test_do $PREREQ "basic atom: $ref $format" '
+		${git_for_each_ref} --format="%($format)" "$ref" >actual &&
+		sanitize_pgp <actual >actual.clean &&
+		test_cmp expected actual.clean
+	'
+
+	# Automatically test "contents:size" atom after testing "contents"
+	if test "$format" = "contents"
+	then
+		# for commit leg, $3 is changed there
+		expect=$(printf '%s' "$3" | wc -c)
+		$test_do $PREREQ "basic atom: $ref contents:size" '
+			type=$(git cat-file -t "$ref") &&
+			case $type in
+			tag)
+				# We cannot use $3 as it expects sanitize_pgp to run
+				git cat-file tag $ref >out &&
+				expect=$(tail -n +6 out | wc -c) &&
+				rm -f out ;;
+			tree | blob)
+				expect="" ;;
+			commit)
+				: "use the calculated expect" ;;
+			*)
+				BUG "unknown object type" ;;
+			esac &&
+			# Leave $expect unquoted to lose possible leading whitespaces
+			echo $expect >expected &&
+			${git_for_each_ref} --format="%(contents:size)" "$ref" >actual &&
+			test_cmp expected actual
+		'
+	fi
+}
+
+hexlen=$(test_oid hexsz)
+
+test_atom head refname refs/heads/main
+test_atom head refname: refs/heads/main
+test_atom head refname:short main
+test_atom head refname:lstrip=1 heads/main
+test_atom head refname:lstrip=2 main
+test_atom head refname:lstrip=-1 main
+test_atom head refname:lstrip=-2 heads/main
+test_atom head refname:rstrip=1 refs/heads
+test_atom head refname:rstrip=2 refs
+test_atom head refname:rstrip=-1 refs
+test_atom head refname:rstrip=-2 refs/heads
+test_atom head refname:strip=1 heads/main
+test_atom head refname:strip=2 main
+test_atom head refname:strip=-1 main
+test_atom head refname:strip=-2 heads/main
+test_atom head upstream refs/remotes/origin/main
+test_atom head upstream:short origin/main
+test_atom head upstream:lstrip=2 origin/main
+test_atom head upstream:lstrip=-2 origin/main
+test_atom head upstream:rstrip=2 refs/remotes
+test_atom head upstream:rstrip=-2 refs/remotes
+test_atom head upstream:strip=2 origin/main
+test_atom head upstream:strip=-2 origin/main
+test_atom head push refs/remotes/myfork/main
+test_atom head push:short myfork/main
+test_atom head push:lstrip=1 remotes/myfork/main
+test_atom head push:lstrip=-1 main
+test_atom head push:rstrip=1 refs/remotes/myfork
+test_atom head push:rstrip=-1 refs
+test_atom head push:strip=1 remotes/myfork/main
+test_atom head push:strip=-1 main
+test_atom head objecttype commit
+test_atom head objectsize $((131 + hexlen))
+test_atom head objectsize:disk $(test_object_file_size refs/heads/main)
+test_atom head deltabase $ZERO_OID
+test_atom head objectname $(git rev-parse refs/heads/main)
+test_atom head objectname:short $(git rev-parse --short refs/heads/main)
+test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
+test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
+test_atom head tree $(git rev-parse refs/heads/main^{tree})
+test_atom head tree:short $(git rev-parse --short refs/heads/main^{tree})
+test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/main^{tree})
+test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/main^{tree})
+test_atom head parent ''
+test_atom head parent:short ''
+test_atom head parent:short=1 ''
+test_atom head parent:short=10 ''
+test_atom head numparent 0
+test_atom head object ''
+test_atom head type ''
+test_atom head raw "$(git cat-file commit refs/heads/main)
+"
+test_atom head '*objectname' ''
+test_atom head '*objecttype' ''
+test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
+test_atom head authorname 'A U Thor'
+test_atom head authorname:mailmap 'A Thor'
+test_atom head authoremail '<author@example.com>'
+test_atom head authoremail:trim 'author@example.com'
+test_atom head authoremail:localpart 'author'
+test_atom head authoremail:trim,localpart 'author'
+test_atom head authoremail:mailmap '<athor@example.com>'
+test_atom head authoremail:mailmap,trim 'athor@example.com'
+test_atom head authoremail:trim,mailmap 'athor@example.com'
+test_atom head authoremail:mailmap,localpart 'athor'
+test_atom head authoremail:localpart,mailmap 'athor'
+test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor'
+test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
+test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
+test_atom head committername 'C O Mitter'
+test_atom head committername:mailmap 'C Mitter'
+test_atom head committeremail '<committer@example.com>'
+test_atom head committeremail:trim 'committer@example.com'
+test_atom head committeremail:localpart 'committer'
+test_atom head committeremail:localpart,trim 'committer'
+test_atom head committeremail:mailmap '<cmitter@example.com>'
+test_atom head committeremail:mailmap,trim 'cmitter@example.com'
+test_atom head committeremail:trim,mailmap 'cmitter@example.com'
+test_atom head committeremail:mailmap,localpart 'cmitter'
+test_atom head committeremail:localpart,mailmap 'cmitter'
+test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter'
+test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
+test_atom head tag ''
+test_atom head tagger ''
+test_atom head taggername ''
+test_atom head taggeremail ''
+test_atom head taggeremail:trim ''
+test_atom head taggeremail:localpart ''
+test_atom head taggerdate ''
+test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
+test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
+test_atom head subject 'Initial'
+test_atom head subject:sanitize 'Initial'
+test_atom head contents:subject 'Initial'
+test_atom head body ''
+test_atom head contents:body ''
+test_atom head contents:signature ''
+test_atom head contents 'Initial
+'
+test_atom head HEAD '*'
+
+test_atom tag refname refs/tags/testtag
+test_atom tag refname:short testtag
+test_atom tag upstream ''
+test_atom tag push ''
+test_atom tag objecttype tag
+test_atom tag objectsize $((114 + hexlen))
+test_atom tag objectsize:disk $(test_object_file_size refs/tags/testtag)
+test_atom tag '*objectsize:disk' $(test_object_file_size refs/heads/main)
+test_atom tag deltabase $ZERO_OID
+test_atom tag '*deltabase' $ZERO_OID
+test_atom tag objectname $(git rev-parse refs/tags/testtag)
+test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
+test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
+test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
+test_atom tag tree ''
+test_atom tag tree:short ''
+test_atom tag tree:short=1 ''
+test_atom tag tree:short=10 ''
+test_atom tag parent ''
+test_atom tag parent:short ''
+test_atom tag parent:short=1 ''
+test_atom tag parent:short=10 ''
+test_atom tag numparent ''
+test_atom tag object $(git rev-parse refs/tags/testtag^0)
+test_atom tag type 'commit'
+test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{})
+test_atom tag '*objecttype' 'commit'
+test_atom tag author ''
+test_atom tag authorname ''
+test_atom tag authorname:mailmap ''
+test_atom tag authoremail ''
+test_atom tag authoremail:trim ''
+test_atom tag authoremail:localpart ''
+test_atom tag authoremail:trim,localpart ''
+test_atom tag authoremail:mailmap ''
+test_atom tag authoremail:mailmap,trim ''
+test_atom tag authoremail:trim,mailmap ''
+test_atom tag authoremail:mailmap,localpart ''
+test_atom tag authoremail:localpart,mailmap ''
+test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim ''
+test_atom tag authordate ''
+test_atom tag committer ''
+test_atom tag committername ''
+test_atom tag committername:mailmap ''
+test_atom tag committeremail ''
+test_atom tag committeremail:trim ''
+test_atom tag committeremail:localpart ''
+test_atom tag committeremail:localpart,trim ''
+test_atom tag committeremail:mailmap ''
+test_atom tag committeremail:mailmap,trim ''
+test_atom tag committeremail:trim,mailmap ''
+test_atom tag committeremail:mailmap,localpart ''
+test_atom tag committeremail:localpart,mailmap ''
+test_atom tag committeremail:trim,mailmap,trim,trim,localpart ''
+test_atom tag committerdate ''
+test_atom tag tag 'testtag'
+test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
+test_atom tag taggername 'C O Mitter'
+test_atom tag taggername:mailmap 'C Mitter'
+test_atom tag taggeremail '<committer@example.com>'
+test_atom tag taggeremail:trim 'committer@example.com'
+test_atom tag taggeremail:localpart 'committer'
+test_atom tag taggeremail:trim,localpart 'committer'
+test_atom tag taggeremail:mailmap '<cmitter@example.com>'
+test_atom tag taggeremail:mailmap,trim 'cmitter@example.com'
+test_atom tag taggeremail:trim,mailmap 'cmitter@example.com'
+test_atom tag taggeremail:mailmap,localpart 'cmitter'
+test_atom tag taggeremail:localpart,mailmap 'cmitter'
+test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter'
+test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
+test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag subject 'Tagging at 1151968727'
+test_atom tag subject:sanitize 'Tagging-at-1151968727'
+test_atom tag contents:subject 'Tagging at 1151968727'
+test_atom tag body ''
+test_atom tag contents:body ''
+test_atom tag contents:signature ''
+test_atom tag contents 'Tagging at 1151968727
+'
+test_atom tag HEAD ' '
+
+test_expect_success 'basic atom: refs/tags/testtag *raw' '
+	git cat-file commit refs/tags/testtag^{} >expected &&
+	${git_for_each_ref} --format="%(*raw)" refs/tags/testtag >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_expect_success 'Check invalid atoms names are errors' '
+	test_must_fail ${git_for_each_ref} --format="%(INVALID)" refs/heads
+'
+
+test_expect_success 'Check format specifiers are ignored in naming date atoms' '
+	${git_for_each_ref} --format="%(authordate)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:default) %(authordate)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate) %(authordate:default)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:default) %(authordate:default)" refs/heads
+'
+
+test_expect_success 'Check valid format specifiers for date fields' '
+	${git_for_each_ref} --format="%(authordate:default)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:relative)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:short)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:local)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:iso8601)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:rfc2822)" refs/heads
+'
+
+test_expect_success 'Check invalid format specifiers are errors' '
+	test_must_fail ${git_for_each_ref} --format="%(authordate:INVALID)" refs/heads
+'
+
+test_expect_success 'arguments to %(objectname:short=) must be positive integers' '
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=0)" &&
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=-1)" &&
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=foo)"
+'
+
+test_bad_atom () {
+	case "$1" in
+	head) ref=refs/heads/main ;;
+	 tag) ref=refs/tags/testtag ;;
+	 sym) ref=refs/heads/sym ;;
+	   *) ref=$1 ;;
+	esac
+	format=$2
+	test_do=test_expect_${4:-success}
+
+	printf '%s\n' "$3" >expect
+	$test_do $PREREQ "err basic atom: $ref $format" '
+		test_must_fail ${git_for_each_ref} \
+			--format="%($format)" "$ref" 2>error &&
+		test_cmp expect error
+	'
+}
+
+test_bad_atom head 'authoremail:foo' \
+	'fatal: unrecognized %(authoremail) argument: foo'
+
+test_bad_atom head 'authoremail:mailmap,trim,bar' \
+	'fatal: unrecognized %(authoremail) argument: bar'
+
+test_bad_atom head 'authoremail:trim,' \
+	'fatal: unrecognized %(authoremail) argument: '
+
+test_bad_atom head 'authoremail:mailmaptrim' \
+	'fatal: unrecognized %(authoremail) argument: trim'
+
+test_bad_atom head 'committeremail: ' \
+	'fatal: unrecognized %(committeremail) argument:  '
+
+test_bad_atom head 'committeremail: trim,foo' \
+	'fatal: unrecognized %(committeremail) argument:  trim,foo'
+
+test_bad_atom head 'committeremail:mailmap,localpart ' \
+	'fatal: unrecognized %(committeremail) argument:  '
+
+test_bad_atom head 'committeremail:trim_localpart' \
+	'fatal: unrecognized %(committeremail) argument: _localpart'
+
+test_bad_atom head 'committeremail:localpart,,,trim' \
+	'fatal: unrecognized %(committeremail) argument: ,,trim'
+
+test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \
+	'fatal: unrecognized %(taggeremail) argument:  foo '
+
+test_bad_atom tag 'taggeremail:trim,localpart,' \
+	'fatal: unrecognized %(taggeremail) argument: '
+
+test_bad_atom tag 'taggeremail:mailmap;localpart trim' \
+	'fatal: unrecognized %(taggeremail) argument: ;localpart trim'
+
+test_bad_atom tag 'taggeremail:localpart trim' \
+	'fatal: unrecognized %(taggeremail) argument:  trim'
+
+test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \
+	'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim'
+
+test_date () {
+	f=$1 &&
+	committer_date=$2 &&
+	author_date=$3 &&
+	tagger_date=$4 &&
+	cat >expected <<-EOF &&
+	'refs/heads/main' '$committer_date' '$author_date'
+	'refs/tags/testtag' '$tagger_date'
+	EOF
+	(
+		${git_for_each_ref} --shell \
+			--format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
+			refs/heads &&
+		${git_for_each_ref} --shell \
+			--format="%(refname) %(taggerdate${f:+:$f})" \
+			refs/tags
+	) >actual &&
+	test_cmp expected actual
+}
+
+test_expect_success 'Check unformatted date fields output' '
+	test_date "" \
+		"Tue Jul 4 01:18:43 2006 +0200" \
+		"Tue Jul 4 01:18:44 2006 +0200" \
+		"Tue Jul 4 01:18:45 2006 +0200"
+'
+
+test_expect_success 'Check format "default" formatted date fields output' '
+	test_date default \
+		"Tue Jul 4 01:18:43 2006 +0200" \
+		"Tue Jul 4 01:18:44 2006 +0200" \
+		"Tue Jul 4 01:18:45 2006 +0200"
+'
+
+test_expect_success 'Check format "default-local" date fields output' '
+	test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006"
+'
+
+# Don't know how to do relative check because I can't know when this script
+# is going to be run and can't fake the current time to git, and hence can't
+# provide expected output.  Instead, I'll just make sure that "relative"
+# doesn't exit in error
+test_expect_success 'Check format "relative" date fields output' '
+	f=relative &&
+	(${git_for_each_ref} --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
+	${git_for_each_ref} --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
+'
+
+# We just check that this is the same as "relative" for now.
+test_expect_success 'Check format "relative-local" date fields output' '
+	test_date relative-local \
+		"$(${git_for_each_ref} --format="%(committerdate:relative)" refs/heads)" \
+		"$(${git_for_each_ref} --format="%(authordate:relative)" refs/heads)" \
+		"$(${git_for_each_ref} --format="%(taggerdate:relative)" refs/tags)"
+'
+
+test_expect_success 'Check format "short" date fields output' '
+	test_date short 2006-07-04 2006-07-04 2006-07-04
+'
+
+test_expect_success 'Check format "short-local" date fields output' '
+	test_date short-local 2006-07-03 2006-07-03 2006-07-03
+'
+
+test_expect_success 'Check format "local" date fields output' '
+	test_date local \
+		"Mon Jul 3 23:18:43 2006" \
+		"Mon Jul 3 23:18:44 2006" \
+		"Mon Jul 3 23:18:45 2006"
+'
+
+test_expect_success 'Check format "iso8601" date fields output' '
+	test_date iso8601 \
+		"2006-07-04 01:18:43 +0200" \
+		"2006-07-04 01:18:44 +0200" \
+		"2006-07-04 01:18:45 +0200"
+'
+
+test_expect_success 'Check format "iso8601-local" date fields output' '
+	test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000"
+'
+
+test_expect_success 'Check format "rfc2822" date fields output' '
+	test_date rfc2822 \
+		"Tue, 4 Jul 2006 01:18:43 +0200" \
+		"Tue, 4 Jul 2006 01:18:44 +0200" \
+		"Tue, 4 Jul 2006 01:18:45 +0200"
+'
+
+test_expect_success 'Check format "rfc2822-local" date fields output' '
+	test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000"
+'
+
+test_expect_success 'Check format "raw" date fields output' '
+	test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200"
+'
+
+test_expect_success 'Check format "raw-local" date fields output' '
+	test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000"
+'
+
+test_expect_success 'Check format of strftime date fields' '
+	echo "my date is 2006-07-04" >expected &&
+	${git_for_each_ref} \
+	  --format="%(authordate:format:my date is %Y-%m-%d)" \
+	  refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Check format of strftime-local date fields' '
+	echo "my date is 2006-07-03" >expected &&
+	${git_for_each_ref} \
+	  --format="%(authordate:format-local:my date is %Y-%m-%d)" \
+	  refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'exercise strftime with odd fields' '
+	echo >expected &&
+	${git_for_each_ref} --format="%(authordate:format:)" refs/heads >actual &&
+	test_cmp expected actual &&
+	long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" &&
+	echo $long >expected &&
+	${git_for_each_ref} --format="%(authordate:format:$long)" refs/heads >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/heads/main
+refs/remotes/origin/main
+refs/tags/testtag
+EOF
+
+test_expect_success 'Verify ascending sort' '
+	${git_for_each_ref} --format="%(refname)" --sort=refname >actual &&
+	test_cmp expected actual
+'
+
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/remotes/origin/main
+refs/heads/main
+EOF
+
+test_expect_success 'Verify descending sort' '
+	${git_for_each_ref} --format="%(refname)" --sort=-refname >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Give help even with invalid sort atoms' '
+	test_expect_code 129 ${git_for_each_ref} --sort=bogus -h >actual 2>&1 &&
+	grep "^usage: ${git_for_each_ref}" actual
+'
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/tags/testtag-2
+EOF
+
+test_expect_success 'exercise patterns with prefixes' '
+	git tag testtag-2 &&
+	test_when_finished "git tag -d testtag-2" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/testtag refs/tags/testtag-2 >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/tags/testtag-2
+EOF
+
+test_expect_success 'exercise glob patterns with prefixes' '
+	git tag testtag-2 &&
+	test_when_finished "git tag -d testtag-2" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/testtag "refs/tags/testtag-*" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/bar
+refs/tags/baz
+refs/tags/testtag
+EOF
+
+test_expect_success 'exercise patterns with prefix exclusions' '
+	for tag in foo/one foo/two foo/three bar baz
+	do
+		git tag "$tag" || return 1
+	done &&
+	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/ --exclude=refs/tags/foo >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/bar
+refs/tags/baz
+refs/tags/foo/one
+refs/tags/testtag
+EOF
+
+test_expect_success 'exercise patterns with pattern exclusions' '
+	for tag in foo/one foo/two foo/three bar baz
+	do
+		git tag "$tag" || return 1
+	done &&
+	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/ --exclude="refs/tags/foo/t*" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+'refs/heads/main'
+'refs/remotes/origin/main'
+'refs/tags/testtag'
+EOF
+
+test_expect_success 'Quoting style: shell' '
+	${git_for_each_ref} --shell --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Quoting style: perl' '
+	${git_for_each_ref} --perl --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Quoting style: python' '
+	${git_for_each_ref} --python --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+"refs/heads/main"
+"refs/remotes/origin/main"
+"refs/tags/testtag"
+EOF
+
+test_expect_success 'Quoting style: tcl' '
+	${git_for_each_ref} --tcl --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
+	test_expect_success "more than one quoting style: $i" "
+		test_must_fail ${git_for_each_ref} $i 2>err &&
+		grep '^error: more than one quoting style' err
+	"
+done
+
+test_expect_success 'setup for upstream:track[short]' '
+	test_commit two
+'
+
+test_atom head upstream:track '[ahead 1]'
+test_atom head upstream:trackshort '>'
+test_atom head upstream:track,nobracket 'ahead 1'
+test_atom head upstream:nobracket,track 'ahead 1'
+
+test_expect_success 'setup for push:track[short]' '
+	test_commit third &&
+	git update-ref refs/remotes/myfork/main main &&
+	git reset main~1
+'
+
+test_atom head push:track '[behind 1]'
+test_atom head push:trackshort '<'
+
+test_expect_success 'Check that :track[short] cannot be used with other atoms' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:track)" 2>/dev/null &&
+	test_must_fail ${git_for_each_ref} --format="%(refname:trackshort)" 2>/dev/null
+'
+
+test_expect_success 'Check that :track[short] works when upstream is invalid' '
+	cat >expected <<-\EOF &&
+	[gone]
+
+	EOF
+	test_when_finished "git config branch.main.merge refs/heads/main" &&
+	git config branch.main.merge refs/heads/does-not-exist &&
+	${git_for_each_ref} \
+		--format="%(upstream:track)$LF%(upstream:trackshort)" \
+		refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Check for invalid refname format' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:INVALID)"
+'
+
+test_expect_success 'set up color tests' '
+	cat >expected.color <<-EOF &&
+	$(git rev-parse --short refs/heads/main) <GREEN>main<RESET>
+	$(git rev-parse --short refs/remotes/myfork/main) <GREEN>myfork/main<RESET>
+	$(git rev-parse --short refs/remotes/origin/main) <GREEN>origin/main<RESET>
+	$(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET>
+	$(git rev-parse --short refs/tags/third) <GREEN>third<RESET>
+	$(git rev-parse --short refs/tags/two) <GREEN>two<RESET>
+	EOF
+	sed "s/<[^>]*>//g" <expected.color >expected.bare &&
+	color_format="%(objectname:short) %(color:green)%(refname:short)"
+'
+
+test_expect_success TTY '%(color) shows color with a tty' '
+	test_terminal ${git_for_each_ref} --format="$color_format" >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	test_cmp expected.color actual
+'
+
+test_expect_success '%(color) does not show color without tty' '
+	TERM=vt100 ${git_for_each_ref} --format="$color_format" >actual &&
+	test_cmp expected.bare actual
+'
+
+test_expect_success '--color can override tty check' '
+	${git_for_each_ref} --color --format="$color_format" >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	test_cmp expected.color actual
+'
+
+test_expect_success 'color.ui=always does not override tty check' '
+	git -c color.ui=always ${git_for_each_ref#git} --format="$color_format" >actual &&
+	test_cmp expected.bare actual
+'
+
+test_expect_success 'setup for describe atom tests' '
+	git init -b master describe-repo &&
+	(
+		cd describe-repo &&
+
+		test_commit --no-tag one &&
+		git tag tagone &&
+
+		test_commit --no-tag two &&
+		git tag -a -m "tag two" tagtwo
+	)
+'
+
+test_expect_success 'describe atom vs git describe' '
+	(
+		cd describe-repo &&
+
+		${git_for_each_ref} --format="%(objectname)" \
+			refs/tags/ >obj &&
+		while read hash
+		do
+			if desc=$(git describe $hash)
+			then
+				: >expect-contains-good
+			else
+				: >expect-contains-bad
+			fi &&
+			echo "$hash $desc" || return 1
+		done <obj >expect &&
+		test_path_exists expect-contains-good &&
+		test_path_exists expect-contains-bad &&
+
+		${git_for_each_ref} --format="%(objectname) %(describe)" \
+			refs/tags/ >actual 2>err &&
+		test_cmp expect actual &&
+		test_must_be_empty err
+	)
+'
+
+test_expect_success 'describe:tags vs describe --tags' '
+	(
+		cd describe-repo &&
+		git describe --tags >expect &&
+		${git_for_each_ref} --format="%(describe:tags)" \
+				refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
+	(
+		cd describe-repo &&
+
+		# Case 1: We have commits between HEAD and the most
+		#	  recent tag reachable from it
+		test_commit --no-tag file &&
+		git describe --abbrev=14 >expect &&
+		${git_for_each_ref} --format="%(describe:abbrev=14)" \
+			refs/heads/master >actual &&
+		test_cmp expect actual &&
+
+		# Make sure the hash used is at least 14 digits long
+		sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart &&
+		test 15 -le $(wc -c <hexpart) &&
+
+		# Case 2: We have a tag at HEAD, describe directly gives
+		#	  the name of the tag
+		git tag -a -m tagged tagname &&
+		git describe --abbrev=14 >expect &&
+		${git_for_each_ref} --format="%(describe:abbrev=14)" \
+			refs/heads/master >actual &&
+		test_cmp expect actual &&
+		test tagname = $(cat actual)
+	)
+'
+
+test_expect_success 'describe:match=... vs describe --match ...' '
+	(
+		cd describe-repo &&
+		git tag -a -m "tag foo" tag-foo &&
+		git describe --match "*-foo" >expect &&
+		${git_for_each_ref} --format="%(describe:match="*-foo")" \
+			refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'describe:exclude:... vs describe --exclude ...' '
+	(
+		cd describe-repo &&
+		git tag -a -m "tag bar" tag-bar &&
+		git describe --exclude "*-bar" >expect &&
+		${git_for_each_ref} --format="%(describe:exclude="*-bar")" \
+			refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'deref with describe atom' '
+	(
+		cd describe-repo &&
+		cat >expect <<-\EOF &&
+
+		tagname
+		tagname
+		tagname
+
+		tagtwo
+		EOF
+		${git_for_each_ref} --format="%(*describe)" >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'err on bad describe atom arg' '
+	(
+		cd describe-repo &&
+
+		# The bad arg is the only arg passed to describe atom
+		cat >expect <<-\EOF &&
+		fatal: unrecognized %(describe) argument: baz
+		EOF
+		test_must_fail ${git_for_each_ref} --format="%(describe:baz)" \
+			refs/heads/master 2>actual &&
+		test_cmp expect actual &&
+
+		# The bad arg is in the middle of the option string
+		# passed to the describe atom
+		cat >expect <<-\EOF &&
+		fatal: unrecognized %(describe) argument: qux=1,abbrev=14
+		EOF
+		test_must_fail ${git_for_each_ref} \
+			--format="%(describe:tags,qux=1,abbrev=14)" \
+			ref/heads/master 2>actual &&
+		test_cmp expect actual
+	)
+'
+
+cat >expected <<\EOF
+heads/main
+tags/main
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs (strict)' '
+	git config --bool core.warnambiguousrefs true &&
+	git checkout -b newtag &&
+	echo "Using $datestamp" > one &&
+	git add one &&
+	git commit -m "Branch" &&
+	setdate_and_increment &&
+	git tag -m "Tagging at $datestamp" main &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+heads/main
+main
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs (loose)' '
+	git config --bool core.warnambiguousrefs false &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+heads/ambiguous
+ambiguous
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs II (loose)' '
+	git checkout main &&
+	git tag ambiguous testtag^0 &&
+	git branch ambiguous testtag^0 &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'create tag without tagger' '
+	git tag -a -m "Broken tag" taggerless &&
+	git tag -f taggerless $(git cat-file tag taggerless |
+		sed -e "/^tagger /d" |
+		git hash-object --literally --stdin -w -t tag)
+'
+
+test_atom refs/tags/taggerless type 'commit'
+test_atom refs/tags/taggerless tag 'taggerless'
+test_atom refs/tags/taggerless tagger ''
+test_atom refs/tags/taggerless taggername ''
+test_atom refs/tags/taggerless taggeremail ''
+test_atom refs/tags/taggerless taggeremail:trim ''
+test_atom refs/tags/taggerless taggeremail:localpart ''
+test_atom refs/tags/taggerless taggerdate ''
+test_atom refs/tags/taggerless committer ''
+test_atom refs/tags/taggerless committername ''
+test_atom refs/tags/taggerless committeremail ''
+test_atom refs/tags/taggerless committeremail:trim ''
+test_atom refs/tags/taggerless committeremail:localpart ''
+test_atom refs/tags/taggerless committerdate ''
+test_atom refs/tags/taggerless subject 'Broken tag'
+
+test_expect_success 'an unusual tag with an incomplete line' '
+
+	git tag -m "bogo" bogo &&
+	bogo=$(git cat-file tag bogo) &&
+	bogo=$(printf "%s" "$bogo" | git mktag) &&
+	git tag -f bogo "$bogo" &&
+	${git_for_each_ref} --format "%(body)" refs/tags/bogo
+
+'
+
+test_expect_success 'create tag with subject and body content' '
+	cat >>msg <<-\EOF &&
+		the subject line
+
+		first body line
+		second body line
+	EOF
+	git tag -F msg subject-body
+'
+test_atom refs/tags/subject-body subject 'the subject line'
+test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
+test_atom refs/tags/subject-body body 'first body line
+second body line
+'
+test_atom refs/tags/subject-body contents 'the subject line
+
+first body line
+second body line
+'
+
+test_expect_success 'create tag with multiline subject' '
+	cat >msg <<-\EOF &&
+		first subject line
+		second subject line
+
+		first body line
+		second body line
+	EOF
+	git tag -F msg multiline
+'
+test_atom refs/tags/multiline subject 'first subject line second subject line'
+test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
+test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
+test_atom refs/tags/multiline body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:signature ''
+test_atom refs/tags/multiline contents 'first subject line
+second subject line
+
+first body line
+second body line
+'
+
+test_expect_success GPG 'create signed tags' '
+	git tag -s -m "" signed-empty &&
+	git tag -s -m "subject line" signed-short &&
+	cat >msg <<-\EOF &&
+	subject line
+
+	body contents
+	EOF
+	git tag -s -F msg signed-long
+'
+
+sig='-----BEGIN PGP SIGNATURE-----
+-----END PGP SIGNATURE-----
+'
+
+PREREQ=GPG
+test_atom refs/tags/signed-empty subject ''
+test_atom refs/tags/signed-empty subject:sanitize ''
+test_atom refs/tags/signed-empty contents:subject ''
+test_atom refs/tags/signed-empty body "$sig"
+test_atom refs/tags/signed-empty contents:body ''
+test_atom refs/tags/signed-empty contents:signature "$sig"
+test_atom refs/tags/signed-empty contents "$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
+	git cat-file tag refs/tags/signed-empty >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-empty >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_atom refs/tags/signed-short subject 'subject line'
+test_atom refs/tags/signed-short subject:sanitize 'subject-line'
+test_atom refs/tags/signed-short contents:subject 'subject line'
+test_atom refs/tags/signed-short body "$sig"
+test_atom refs/tags/signed-short contents:body ''
+test_atom refs/tags/signed-short contents:signature "$sig"
+test_atom refs/tags/signed-short contents "subject line
+$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
+	git cat-file tag refs/tags/signed-short >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-short >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_atom refs/tags/signed-long subject 'subject line'
+test_atom refs/tags/signed-long subject:sanitize 'subject-line'
+test_atom refs/tags/signed-long contents:subject 'subject line'
+test_atom refs/tags/signed-long body "body contents
+$sig"
+test_atom refs/tags/signed-long contents:body 'body contents
+'
+test_atom refs/tags/signed-long contents:signature "$sig"
+test_atom refs/tags/signed-long contents "subject line
+
+body contents
+$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
+	git cat-file tag refs/tags/signed-long >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-long >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_expect_success 'set up refs pointing to tree and blob' '
+	git update-ref refs/mytrees/first refs/heads/main^{tree} &&
+	git update-ref refs/myblobs/first refs/heads/main:one
+'
+
+test_atom refs/mytrees/first subject ""
+test_atom refs/mytrees/first contents:subject ""
+test_atom refs/mytrees/first body ""
+test_atom refs/mytrees/first contents:body ""
+test_atom refs/mytrees/first contents:signature ""
+test_atom refs/mytrees/first contents ""
+
+test_expect_success 'basic atom: refs/mytrees/first raw' '
+	git cat-file tree refs/mytrees/first >expected &&
+	echo >>expected &&
+	${git_for_each_ref} --format="%(raw)" refs/mytrees/first >actual &&
+	test_cmp expected actual &&
+	git cat-file -s refs/mytrees/first >expected &&
+	${git_for_each_ref} --format="%(raw:size)" refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_atom refs/myblobs/first subject ""
+test_atom refs/myblobs/first contents:subject ""
+test_atom refs/myblobs/first body ""
+test_atom refs/myblobs/first contents:body ""
+test_atom refs/myblobs/first contents:signature ""
+test_atom refs/myblobs/first contents ""
+
+test_expect_success 'basic atom: refs/myblobs/first raw' '
+	git cat-file blob refs/myblobs/first >expected &&
+	echo >>expected &&
+	${git_for_each_ref} --format="%(raw)" refs/myblobs/first >actual &&
+	test_cmp expected actual &&
+	git cat-file -s refs/myblobs/first >expected &&
+	${git_for_each_ref} --format="%(raw:size)" refs/myblobs/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'set up refs pointing to binary blob' '
+	printf "a\0b\0c" >blob1 &&
+	printf "a\0c\0b" >blob2 &&
+	printf "\0a\0b\0c" >blob3 &&
+	printf "abc" >blob4 &&
+	printf "\0 \0 \0 " >blob5 &&
+	printf "\0 \0a\0 " >blob6 &&
+	printf "  " >blob7 &&
+	>blob8 &&
+	obj=$(git hash-object -w blob1) &&
+	git update-ref refs/myblobs/blob1 "$obj" &&
+	obj=$(git hash-object -w blob2) &&
+	git update-ref refs/myblobs/blob2 "$obj" &&
+	obj=$(git hash-object -w blob3) &&
+	git update-ref refs/myblobs/blob3 "$obj" &&
+	obj=$(git hash-object -w blob4) &&
+	git update-ref refs/myblobs/blob4 "$obj" &&
+	obj=$(git hash-object -w blob5) &&
+	git update-ref refs/myblobs/blob5 "$obj" &&
+	obj=$(git hash-object -w blob6) &&
+	git update-ref refs/myblobs/blob6 "$obj" &&
+	obj=$(git hash-object -w blob7) &&
+	git update-ref refs/myblobs/blob7 "$obj" &&
+	obj=$(git hash-object -w blob8) &&
+	git update-ref refs/myblobs/blob8 "$obj"
+'
+
+test_expect_success 'Verify sorts with raw' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob8
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/blob3
+	refs/myblobs/blob7
+	refs/mytrees/first
+	refs/myblobs/first
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob4
+	refs/heads/main
+	EOF
+	${git_for_each_ref} --format="%(refname)" --sort=raw \
+		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Verify sorts with raw:size' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob8
+	refs/myblobs/blob7
+	refs/myblobs/blob4
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob3
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/first
+	refs/mytrees/first
+	refs/heads/main
+	EOF
+	${git_for_each_ref} --format="%(refname)" --sort=raw:size \
+		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:equals)' '
+	cat >expected <<-EOF &&
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	refs/myblobs/blob4
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	EOF
+	${git_for_each_ref} --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
+		refs/myblobs/ refs/heads/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:notequals)' '
+	cat >expected <<-EOF &&
+	refs/heads/ambiguous
+	refs/heads/main
+	refs/heads/newtag
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob3
+	equals
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/blob7
+	refs/myblobs/blob8
+	refs/myblobs/first
+	EOF
+	${git_for_each_ref} --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
+		refs/myblobs/ refs/heads/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'empty raw refs with %(if)' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob1 not empty
+	refs/myblobs/blob2 not empty
+	refs/myblobs/blob3 not empty
+	refs/myblobs/blob4 not empty
+	refs/myblobs/blob5 not empty
+	refs/myblobs/blob6 not empty
+	refs/myblobs/blob7 empty
+	refs/myblobs/blob8 empty
+	refs/myblobs/first not empty
+	EOF
+	${git_for_each_ref} --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
+		refs/myblobs/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '%(raw) with --python must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --python
+'
+
+test_expect_success '%(raw) with --tcl must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --tcl
+'
+
+test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' '
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
+	cmp blob1 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
+	cmp blob3 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
+	cmp blob8 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
+	cmp one actual &&
+	git cat-file tree refs/mytrees/first > expected &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
+	cmp expected actual
+'
+
+test_expect_success '%(raw) with --shell must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --shell
+'
+
+test_expect_success '%(raw) with --shell and --sort=raw must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --sort=raw --shell
+'
+
+test_expect_success '%(raw:size) with --shell' '
+	${git_for_each_ref} --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
+	${git_for_each_ref} --format="%(raw:size)" --shell >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --format compare with cat-file --batch" '
+	git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
+	${git_for_each_ref} --format="%(objectname) %(objecttype) %(objectsize)
+%(raw)" refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'verify sorts with contents:size' '
+	cat >expect <<-\EOF &&
+	refs/heads/main
+	refs/heads/newtag
+	refs/heads/ambiguous
+	EOF
+	${git_for_each_ref} --format="%(refname)" \
+		--sort=contents:size refs/heads/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'set up multiple-sort tags' '
+	for when in 100000 200000
+	do
+		for email in user1 user2
+		do
+			for ref in ref1 ref2
+			do
+				GIT_COMMITTER_DATE="@$when +0000" \
+				GIT_COMMITTER_EMAIL="$email@example.com" \
+				git tag -m "tag $ref-$when-$email" \
+				multi-$ref-$when-$email || return 1
+			done
+		done
+	done
+'
+
+test_expect_success 'Verify sort with multiple keys' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=-refname \
+		--sort=taggeremail \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'equivalent sorts fall back on refname' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '--no-sort cancels the previous sort keys' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=-refname \
+		--sort=taggeremail \
+		--no-sort \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '--no-sort without subsequent --sort prints expected refs' '
+	cat >expected <<-\EOF &&
+	refs/tags/multi-ref1-100000-user1
+	refs/tags/multi-ref1-100000-user2
+	refs/tags/multi-ref1-200000-user1
+	refs/tags/multi-ref1-200000-user2
+	refs/tags/multi-ref2-100000-user1
+	refs/tags/multi-ref2-100000-user2
+	refs/tags/multi-ref2-200000-user1
+	refs/tags/multi-ref2-200000-user2
+	EOF
+
+	# Sort the results with `sort` for a consistent comparison against
+	# expected
+	${git_for_each_ref} \
+		--format="%(refname)" \
+		--no-sort \
+		"refs/tags/multi-*" | sort >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'set up custom date sorting' '
+	# Dates:
+	# - Wed Feb 07 2024 21:34:20 +0000
+	# - Tue Dec 14 1999 00:05:22 +0000
+	# - Fri Jun 04 2021 11:26:51 +0000
+	# - Mon Jan 22 2007 16:44:01 GMT+0000
+	i=1 &&
+	for when in 1707341660 945129922 1622806011 1169484241
+	do
+		GIT_COMMITTER_DATE="@$when +0000" \
+		GIT_COMMITTER_EMAIL="user@example.com" \
+		git tag -m "tag $when" custom-dates-$i &&
+		i=$(($i+1)) || return 1
+	done
+'
+
+test_expect_success 'sort by date defaults to full timestamp' '
+	cat >expected <<-\EOF &&
+	945129922 refs/tags/custom-dates-2
+	1169484241 refs/tags/custom-dates-4
+	1622806011 refs/tags/custom-dates-3
+	1707341660 refs/tags/custom-dates-1
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(creatordate:unix) %(refname)" \
+		--sort=creatordate \
+		"refs/tags/custom-dates-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'sort by custom date format' '
+	cat >expected <<-\EOF &&
+	00:05:22 refs/tags/custom-dates-2
+	11:26:51 refs/tags/custom-dates-3
+	16:44:01 refs/tags/custom-dates-4
+	21:34:20 refs/tags/custom-dates-1
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(creatordate:format:%H:%M:%S) %(refname)" \
+		--sort="creatordate:format:%H:%M:%S" \
+		"refs/tags/custom-dates-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
+	test_when_finished "git checkout main" &&
+	${git_for_each_ref} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	sed -e "s/^\* /  /" actual >expect &&
+	git checkout --orphan orphaned-branch &&
+	${git_for_each_ref} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	test_cmp expect actual
+'
+
+cat >trailers <<EOF
+Reviewed-by: A U Thor <author@example.com>
+Signed-off-by: A U Thor <author@example.com>
+[ v2 updated patch description ]
+Acked-by: A U Thor
+  <author@example.com>
+EOF
+
+unfold () {
+	perl -0pe 's/\n\s+/ /g'
+}
+
+test_expect_success 'set up trailers for next test' '
+	echo "Some contents" > two &&
+	git add two &&
+	git commit -F - <<-EOF
+	trailers: this commit message has trailers
+
+	Some message contents
+
+	$(cat trailers)
+	EOF
+'
+
+test_trailer_option () {
+	if test "$#" -eq 3
+	then
+		prereq="$1"
+		shift
+	fi &&
+	title=$1 option=$2
+	cat >expect
+	test_expect_success $prereq "$title" '
+		${git_for_each_ref} --format="%($option)" refs/heads/main >actual &&
+		test_cmp expect actual &&
+		${git_for_each_ref} --format="%(contents:$option)" refs/heads/main >actual &&
+		test_cmp expect actual
+	'
+}
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' \
+	'trailers:unfold' <<-EOF
+	$(unfold <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only) shows only "key: value" trailers' \
+	'trailers:only' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=no,only=true) shows only "key: value" trailers' \
+	'trailers:only=no,only=true' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=yes) shows only "key: value" trailers' \
+	'trailers:only=yes' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=no) shows all trailers' \
+	'trailers:only=no' <<-EOF
+	$(cat trailers)
+
+	EOF
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:only) and %(trailers:unfold) work together' \
+	'trailers:only,unfold' <<-EOF
+	$(grep -v patch.description <trailers | unfold)
+
+	EOF
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) and %(trailers:only) work together' \
+	'trailers:unfold,only' <<-EOF
+	$(grep -v patch.description <trailers | unfold)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) shows that trailer' \
+	'trailers:key=Signed-off-by' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) is case insensitive' \
+	'trailers:key=SiGned-oFf-bY' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo:) trailing colon also works' \
+	'trailers:key=Signed-off-by:' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) multiple keys' \
+	'trailers:key=Reviewed-by:,key=Signed-off-by' <<-EOF
+	Reviewed-by: A U Thor <author@example.com>
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=nonexistent) becomes empty' \
+	'trailers:key=Shined-off-by:' <<-EOF
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) handles multiple lines even if folded' \
+	'trailers:key=Acked-by' <<-EOF
+	$(grep -v patch.description <trailers | grep -v Signed-off-by | grep -v Reviewed-by)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,unfold) properly unfolds' \
+	'trailers:key=Signed-Off-by,unfold' <<-EOF
+	$(unfold <trailers | grep Signed-off-by)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,only=no) also includes nontrailer lines' \
+	'trailers:key=Signed-off-by,only=no' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+	$(grep patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,valueonly) shows only value' \
+	'trailers:key=Signed-off-by,valueonly' <<-EOF
+	A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:separator) changes separator' \
+	'trailers:separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by: A U Thor <author@example.com>,Signed-off-by: A U Thor <author@example.com>
+	EOF
+
+test_trailer_option '%(trailers:key_value_separator) changes key-value separator' \
+	'trailers:key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by,A U Thor <author@example.com>
+	Signed-off-by,A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:separator,key_value_separator) changes both separators' \
+	'trailers:separator=%x2C,key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by,A U Thor <author@example.com>,Signed-off-by,A U Thor <author@example.com>
+	EOF
+
+test_expect_success 'multiple %(trailers) use their own options' '
+	git tag -F - tag-with-trailers <<-\EOF &&
+	body
+
+	one: foo
+	one: bar
+	two: baz
+	two: qux
+	EOF
+	t1="%(trailers:key=one,key_value_separator=W,separator=X)" &&
+	t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" &&
+	${git_for_each_ref} --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
+	cat >expect <<-\EOF &&
+	oneWfooXoneWbar
+	twoYbazZtwoYqux
+	EOF
+	test_cmp expect actual
+'
+
+test_failing_trailer_option () {
+	title=$1 option=$2
+	cat >expect
+	test_expect_success "$title" '
+		# error message cannot be checked under i18n
+		test_must_fail ${git_for_each_ref} --format="%($option)" refs/heads/main 2>actual &&
+		test_cmp expect actual &&
+		test_must_fail ${git_for_each_ref} --format="%(contents:$option)" refs/heads/main 2>actual &&
+		test_cmp expect actual
+	'
+}
+
+test_failing_trailer_option '%(trailers) rejects unknown trailers arguments' \
+	'trailers:unsupported' <<-\EOF
+	fatal: unknown %(trailers) argument: unsupported
+	EOF
+
+test_failing_trailer_option '%(trailers:key) without value is error' \
+	'trailers:key' <<-\EOF
+	fatal: expected %(trailers:key=<value>)
+	EOF
+
+test_expect_success 'if arguments, %(contents:trailers) shows error if colon is missing' '
+	cat >expect <<-EOF &&
+	fatal: unrecognized %(contents) argument: trailersonly
+	EOF
+	test_must_fail ${git_for_each_ref} --format="%(contents:trailersonly)" 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'basic atom: head contents:trailers' '
+	${git_for_each_ref} --format="%(contents:trailers)" refs/heads/main >actual &&
+	sanitize_pgp <actual >actual.clean &&
+	# ${git_for_each_ref} ends with a blank line
+	cat >expect <<-EOF &&
+	$(cat trailers)
+
+	EOF
+	test_cmp expect actual.clean
+'
+
+test_expect_success 'basic atom: rest must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(rest)" refs/heads/main
+'
+
+test_expect_success 'HEAD atom does not take arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(HEAD:foo)" 2>err &&
+	echo "fatal: %(HEAD) does not take arguments" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'subject atom rejects unknown arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(subject:foo)" 2>err &&
+	echo "fatal: unrecognized %(subject) argument: foo" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'refname atom rejects unknown arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:foo)" 2>err &&
+	echo "fatal: unrecognized %(refname) argument: foo" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'trailer parsing not fooled by --- line' '
+	git commit --allow-empty -F - <<-\EOF &&
+	this is the subject
+
+	This is the body. The message has a "---" line which would confuse a
+	message+patch parser. But here we know we have only a commit message,
+	so we get it right.
+
+	trailer: wrong
+	---
+	This is more body.
+
+	trailer: right
+	EOF
+
+	{
+		echo "trailer: right" &&
+		echo
+	} >expect &&
+	${git_for_each_ref} --format="%(trailers)" refs/heads/main >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'Add symbolic ref for the following tests' '
+	git symbolic-ref refs/heads/sym refs/heads/main
+'
+
+cat >expected <<EOF
+refs/heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref) atom' '
+	${git_for_each_ref} --format="%(symref)" refs/heads/sym >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref:short) atom' '
+	${git_for_each_ref} --format="%(symref:short)" refs/heads/sym >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+main
+heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref:lstrip) atom' '
+	${git_for_each_ref} --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual &&
+
+	${git_for_each_ref} --format="%(symref:strip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+refs
+refs/heads
+EOF
+
+test_expect_success 'Verify usage of %(symref:rstrip) atom' '
+	${git_for_each_ref} --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual
+'
+
+test_expect_success ':remotename and :remoteref' '
+	git init remote-tests &&
+	(
+		cd remote-tests &&
+		test_commit initial &&
+		git branch -M main &&
+		git remote add from fifth.coffee:blub &&
+		git config branch.main.remote from &&
+		git config branch.main.merge refs/heads/stable &&
+		git remote add to southridge.audio:repo &&
+		git config remote.to.push "refs/heads/*:refs/heads/pushed/*" &&
+		git config branch.main.pushRemote to &&
+		for pair in "%(upstream)=refs/remotes/from/stable" \
+			"%(upstream:remotename)=from" \
+			"%(upstream:remoteref)=refs/heads/stable" \
+			"%(push)=refs/remotes/to/pushed/main" \
+			"%(push:remotename)=to" \
+			"%(push:remoteref)=refs/heads/pushed/main"
+		do
+			echo "${pair#*=}" >expect &&
+			${git_for_each_ref} --format="${pair%=*}" \
+				refs/heads/main >actual &&
+			test_cmp expect actual || exit 1
+		done &&
+		git branch push-simple &&
+		git config branch.push-simple.pushRemote from &&
+		actual="$(${git_for_each_ref} \
+			--format="%(push:remotename),%(push:remoteref)" \
+			refs/heads/push-simple)" &&
+		test from, = "$actual"
+	)
+'
+
+test_expect_success "${git_for_each_ref} --ignore-case ignores case" '
+	${git_for_each_ref} --format="%(refname)" refs/heads/MAIN >actual &&
+	test_must_be_empty actual &&
+
+	echo refs/heads/main >expect &&
+	${git_for_each_ref} --format="%(refname)" --ignore-case \
+		refs/heads/MAIN >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --omit-empty works" '
+	${git_for_each_ref} --format="%(refname)" >actual &&
+	test_line_count -gt 1 actual &&
+	${git_for_each_ref} --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
+	echo refs/heads/main >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --ignore-case works on multiple sort keys" '
+	# name refs numerically to avoid case-insensitive filesystem conflicts
+	nr=0 &&
+	for email in a A b B
+	do
+		for subject in a A b B
+		do
+			GIT_COMMITTER_EMAIL="$email@example.com" \
+			git tag -m "tag $subject" icase-$(printf %02d $nr) &&
+			nr=$((nr+1))||
+			return 1
+		done
+	done &&
+	${git_for_each_ref} --ignore-case \
+		--format="%(taggeremail) %(subject) %(refname)" \
+		--sort=refname \
+		--sort=subject \
+		--sort=taggeremail \
+		refs/tags/icase-* >actual &&
+	cat >expect <<-\EOF &&
+	<a@example.com> tag a refs/tags/icase-00
+	<a@example.com> tag A refs/tags/icase-01
+	<A@example.com> tag a refs/tags/icase-04
+	<A@example.com> tag A refs/tags/icase-05
+	<a@example.com> tag b refs/tags/icase-02
+	<a@example.com> tag B refs/tags/icase-03
+	<A@example.com> tag b refs/tags/icase-06
+	<A@example.com> tag B refs/tags/icase-07
+	<b@example.com> tag a refs/tags/icase-08
+	<b@example.com> tag A refs/tags/icase-09
+	<B@example.com> tag a refs/tags/icase-12
+	<B@example.com> tag A refs/tags/icase-13
+	<b@example.com> tag b refs/tags/icase-10
+	<b@example.com> tag B refs/tags/icase-11
+	<B@example.com> tag b refs/tags/icase-14
+	<B@example.com> tag B refs/tags/icase-15
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} reports broken tags" '
+	git tag -m "good tag" broken-tag-good HEAD &&
+	git cat-file tag broken-tag-good >good &&
+	sed s/commit/blob/ <good >bad &&
+	bad=$(git hash-object -w -t tag bad) &&
+	git update-ref refs/tags/broken-tag-bad $bad &&
+	test_must_fail ${git_for_each_ref} --format="%(*objectname)" \
+		refs/tags/broken-tag-*
+'
+
+test_expect_success 'set up tag with signature and no blank lines' '
+	git tag -F - fake-sig-no-blanks <<-\EOF
+	this is the subject
+	-----BEGIN PGP SIGNATURE-----
+	not a real signature, but we just care about the
+	subject/body parsing. It is important here that
+	there are no blank lines in the signature.
+	-----END PGP SIGNATURE-----
+	EOF
+'
+
+test_atom refs/tags/fake-sig-no-blanks contents:subject 'this is the subject'
+test_atom refs/tags/fake-sig-no-blanks contents:body ''
+test_atom refs/tags/fake-sig-no-blanks contents:signature "$sig"
+
+test_expect_success 'set up tag with CRLF signature' '
+	append_cr <<-\EOF |
+	this is the subject
+	-----BEGIN PGP SIGNATURE-----
+
+	not a real signature, but we just care about
+	the subject/body parsing. It is important here
+	that there is a blank line separating this
+	from the signature header.
+	-----END PGP SIGNATURE-----
+	EOF
+	git tag -F - --cleanup=verbatim fake-sig-crlf
+'
+
+test_atom refs/tags/fake-sig-crlf contents:subject 'this is the subject'
+test_atom refs/tags/fake-sig-crlf contents:body ''
+
+# CRLF is retained in the signature, so we have to pass our expected value
+# through append_cr. But test_atom requires a shell string, which means command
+# substitution, and the shell will strip trailing newlines from the output of
+# the substitution. Hack around it by adding and then removing a dummy line.
+sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
+sig_crlf=${sig_crlf%dummy}
+test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
+
+test_expect_success 'set up tag with signature and trailers' '
+	git tag -F - fake-sig-trailer <<-\EOF
+	this is the subject
+
+	this is the body
+
+	My-Trailer: foo
+	-----BEGIN PGP SIGNATURE-----
+
+	not a real signature, but we just care about the
+	subject/body/trailer parsing.
+	-----END PGP SIGNATURE-----
+	EOF
+'
+
+# use "separator=" here to suppress the terminating newline
+test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo'
+
+test_expect_success "${git_for_each_ref} --stdin: empty" '
+	>in &&
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	${git_for_each_ref} --format="%(refname)" >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --stdin: fails if extra args" '
+	>in &&
+	test_must_fail ${git_for_each_ref} --format="%(refname)" \
+		--stdin refs/heads/extra <in 2>err &&
+	grep "unknown arguments supplied with --stdin" err
+'
+
+test_expect_success "${git_for_each_ref} --stdin: matches" '
+	cat >in <<-EOF &&
+	refs/tags/multi*
+	refs/heads/amb*
+	EOF
+
+	cat >expect <<-EOF &&
+	refs/heads/ambiguous
+	refs/tags/multi-ref1-100000-user1
+	refs/tags/multi-ref1-100000-user2
+	refs/tags/multi-ref1-200000-user1
+	refs/tags/multi-ref1-200000-user2
+	refs/tags/multi-ref2-100000-user1
+	refs/tags/multi-ref2-100000-user2
+	refs/tags/multi-ref2-200000-user1
+	refs/tags/multi-ref2-200000-user2
+	refs/tags/multiline
+	EOF
+
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} with non-existing refs" '
+	cat >in <<-EOF &&
+	refs/heads/this-ref-does-not-exist
+	refs/tags/bogus
+	EOF
+
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	test_must_be_empty actual &&
+
+	xargs ${git_for_each_ref} --format="%(refname)" <in >actual &&
+	test_must_be_empty actual
+'
+
+test_expect_success "${git_for_each_ref} with nested tags" '
+	git tag -am "Normal tag" nested/base HEAD &&
+	git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
+	git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
+
+	head_oid="$(git rev-parse HEAD)" &&
+	base_tag_oid="$(git rev-parse refs/tags/nested/base)" &&
+	nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" &&
+	nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" &&
+
+	cat >expect <<-EOF &&
+	refs/tags/nested/base $base_tag_oid tag $head_oid commit
+	refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit
+	refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
+		refs/tags/nested/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'is-base atom with non-commits' '
+	${git_for_each_ref} --format="%(is-base:HEAD) %(refname)" >out 2>err &&
+	grep "(HEAD) refs/heads/main" out &&
+
+	test_line_count = 2 err &&
+	grep "error: object .* is a commit, not a blob" err &&
+	grep "error: bad tag pointer to" err
+'
+
+GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
+TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
+
+test_expect_success GPG 'setup for signature atom using gpg' '
+	git checkout -b signed &&
+
+	test_when_finished "test_unconfig commit.gpgSign" &&
+
+	echo "1" >file &&
+	git add file &&
+	test_tick &&
+	git commit -S -m "file: 1" &&
+	git tag first-signed &&
+
+	echo "2" >file &&
+	test_tick &&
+	git commit -a -m "file: 2" &&
+	git tag second-unsigned &&
+
+	git config commit.gpgSign 1 &&
+	echo "3" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 3" &&
+	git tag third-unsigned &&
+
+	test_tick &&
+	git rebase -f HEAD^^ && git tag second-signed HEAD^ &&
+	git tag third-signed &&
+
+	echo "4" >file &&
+	test_tick &&
+	git commit -a -SB7227189 -m "file: 4" &&
+	git tag fourth-signed &&
+
+	echo "5" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 5" &&
+	git tag fifth-unsigned &&
+
+	echo "6" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 6" &&
+
+	test_tick &&
+	git rebase -f HEAD^^ &&
+	git tag fifth-signed HEAD^ &&
+	git tag sixth-signed &&
+
+	echo "7" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 7" &&
+	git tag seventh-unsigned
+'
+
+test_expect_success GPGSSH 'setup for signature atom using ssh' '
+	test_when_finished "test_unconfig gpg.format user.signingkey" &&
+
+	test_config gpg.format ssh &&
+	test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+	echo "8" >file &&
+	test_tick &&
+	git add file &&
+	git commit -S -m "file: 8" &&
+	git tag eighth-signed-ssh
+'
+
+test_expect_success GPG2 'bare signature atom' '
+	git verify-commit first-signed 2>expect &&
+	echo  >>expect &&
+	${git_for_each_ref} refs/tags/first-signed \
+		--format="%(signature)" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show good signature with custom format' '
+	git verify-commit first-signed &&
+	cat >expect <<-\EOF &&
+	G
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	EOF
+	${git_for_each_ref} refs/tags/first-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+test_expect_success GPGSSH 'show good signature with custom format with ssh' '
+	test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+	FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+	cat >expect.tmpl <<-\EOF &&
+	G
+	FINGERPRINT
+	principal with number 1
+	FINGERPRINT
+
+	EOF
+	sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
+	${git_for_each_ref} refs/tags/eighth-signed-ssh \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'signature atom with grade option and bad signature' '
+	git cat-file commit third-signed >raw &&
+	sed -e "s/^file: 3/file: 3 forged/" raw >forged1 &&
+	FORGED1=$(git hash-object -w -t commit forged1) &&
+	git update-ref refs/tags/third-signed "$FORGED1" &&
+	test_must_fail git verify-commit "$FORGED1" &&
+
+	cat >expect <<-\EOF &&
+	B
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+
+
+	EOF
+	${git_for_each_ref} refs/tags/third-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with custom format' '
+	cat >expect <<-\EOF &&
+	U
+	65A0EEA02E30CAD7
+	Eris Discordia <discord@example.net>
+	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+	D4BE22311AD3131E5EDA29A461092E85B7227189
+	EOF
+	${git_for_each_ref} refs/tags/fourth-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with undefined trust level' '
+	cat >expect <<-\EOF &&
+	undefined
+	65A0EEA02E30CAD7
+	Eris Discordia <discord@example.net>
+	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+	D4BE22311AD3131E5EDA29A461092E85B7227189
+	EOF
+	${git_for_each_ref} refs/tags/fourth-signed \
+		--format="$TRUSTLEVEL_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with ultimate trust level' '
+	cat >expect <<-\EOF &&
+	ultimate
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	EOF
+	${git_for_each_ref} refs/tags/sixth-signed \
+		--format="$TRUSTLEVEL_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show unknown signature with custom format' '
+	cat >expect <<-\EOF &&
+	E
+	13B6F51ECDDE430D
+
+
+
+	EOF
+	GNUPGHOME="$GNUPGHOME_NOT_USED" ${git_for_each_ref} \
+		refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show lack of signature with custom format' '
+	cat >expect <<-\EOF &&
+	N
+
+
+
+
+	EOF
+	${git_for_each_ref} refs/tags/seventh-unsigned \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index ce9af79ab1..1d9809114d 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -6,2150 +6,14 @@
 test_description='for-each-ref test'
 
 . ./test-lib.sh
-GNUPGHOME_NOT_USED=$GNUPGHOME
-. "$TEST_DIRECTORY"/lib-gpg.sh
-. "$TEST_DIRECTORY"/lib-terminal.sh
 
-# Mon Jul 3 23:18:43 2006 +0000
-datestamp=1151968723
-setdate_and_increment () {
-    GIT_COMMITTER_DATE="$datestamp +0200"
-    datestamp=$(expr "$datestamp" + 1)
-    GIT_AUTHOR_DATE="$datestamp +0200"
-    datestamp=$(expr "$datestamp" + 1)
-    export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
-}
-
-test_object_file_size () {
-	oid=$(git rev-parse "$1")
-	path=".git/objects/$(test_oid_to_path $oid)"
-	test_file_size "$path"
-}
-
-test_expect_success setup '
-	# setup .mailmap
-	cat >.mailmap <<-EOF &&
-	A Thor <athor@example.com> A U Thor <author@example.com>
-	C Mitter <cmitter@example.com> C O Mitter <committer@example.com>
-	EOF
-
-	setdate_and_increment &&
-	echo "Using $datestamp" > one &&
-	git add one &&
-	git commit -m "Initial" &&
-	git branch -M main &&
-	setdate_and_increment &&
-	git tag -a -m "Tagging at $datestamp" testtag &&
-	git update-ref refs/remotes/origin/main main &&
-	git remote add origin nowhere &&
-	git config branch.main.remote origin &&
-	git config branch.main.merge refs/heads/main &&
-	git remote add myfork elsewhere &&
-	git config remote.pushdefault myfork &&
-	git config push.default current
-'
-
-test_atom () {
-	case "$1" in
-		head) ref=refs/heads/main ;;
-		 tag) ref=refs/tags/testtag ;;
-		 sym) ref=refs/heads/sym ;;
-		   *) ref=$1 ;;
-	esac
-	format=$2
-	test_do=test_expect_${4:-success}
-
-	printf '%s\n' "$3" >expected
-	$test_do $PREREQ "basic atom: $ref $format" '
-		git for-each-ref --format="%($format)" "$ref" >actual &&
-		sanitize_pgp <actual >actual.clean &&
-		test_cmp expected actual.clean
-	'
-
-	# Automatically test "contents:size" atom after testing "contents"
-	if test "$format" = "contents"
-	then
-		# for commit leg, $3 is changed there
-		expect=$(printf '%s' "$3" | wc -c)
-		$test_do $PREREQ "basic atom: $ref contents:size" '
-			type=$(git cat-file -t "$ref") &&
-			case $type in
-			tag)
-				# We cannot use $3 as it expects sanitize_pgp to run
-				git cat-file tag $ref >out &&
-				expect=$(tail -n +6 out | wc -c) &&
-				rm -f out ;;
-			tree | blob)
-				expect="" ;;
-			commit)
-				: "use the calculated expect" ;;
-			*)
-				BUG "unknown object type" ;;
-			esac &&
-			# Leave $expect unquoted to lose possible leading whitespaces
-			echo $expect >expected &&
-			git for-each-ref --format="%(contents:size)" "$ref" >actual &&
-			test_cmp expected actual
-		'
-	fi
-}
-
-hexlen=$(test_oid hexsz)
-
-test_atom head refname refs/heads/main
-test_atom head refname: refs/heads/main
-test_atom head refname:short main
-test_atom head refname:lstrip=1 heads/main
-test_atom head refname:lstrip=2 main
-test_atom head refname:lstrip=-1 main
-test_atom head refname:lstrip=-2 heads/main
-test_atom head refname:rstrip=1 refs/heads
-test_atom head refname:rstrip=2 refs
-test_atom head refname:rstrip=-1 refs
-test_atom head refname:rstrip=-2 refs/heads
-test_atom head refname:strip=1 heads/main
-test_atom head refname:strip=2 main
-test_atom head refname:strip=-1 main
-test_atom head refname:strip=-2 heads/main
-test_atom head upstream refs/remotes/origin/main
-test_atom head upstream:short origin/main
-test_atom head upstream:lstrip=2 origin/main
-test_atom head upstream:lstrip=-2 origin/main
-test_atom head upstream:rstrip=2 refs/remotes
-test_atom head upstream:rstrip=-2 refs/remotes
-test_atom head upstream:strip=2 origin/main
-test_atom head upstream:strip=-2 origin/main
-test_atom head push refs/remotes/myfork/main
-test_atom head push:short myfork/main
-test_atom head push:lstrip=1 remotes/myfork/main
-test_atom head push:lstrip=-1 main
-test_atom head push:rstrip=1 refs/remotes/myfork
-test_atom head push:rstrip=-1 refs
-test_atom head push:strip=1 remotes/myfork/main
-test_atom head push:strip=-1 main
-test_atom head objecttype commit
-test_atom head objectsize $((131 + hexlen))
-test_atom head objectsize:disk $(test_object_file_size refs/heads/main)
-test_atom head deltabase $ZERO_OID
-test_atom head objectname $(git rev-parse refs/heads/main)
-test_atom head objectname:short $(git rev-parse --short refs/heads/main)
-test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
-test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
-test_atom head tree $(git rev-parse refs/heads/main^{tree})
-test_atom head tree:short $(git rev-parse --short refs/heads/main^{tree})
-test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/main^{tree})
-test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/main^{tree})
-test_atom head parent ''
-test_atom head parent:short ''
-test_atom head parent:short=1 ''
-test_atom head parent:short=10 ''
-test_atom head numparent 0
-test_atom head object ''
-test_atom head type ''
-test_atom head raw "$(git cat-file commit refs/heads/main)
-"
-test_atom head '*objectname' ''
-test_atom head '*objecttype' ''
-test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
-test_atom head authorname 'A U Thor'
-test_atom head authorname:mailmap 'A Thor'
-test_atom head authoremail '<author@example.com>'
-test_atom head authoremail:trim 'author@example.com'
-test_atom head authoremail:localpart 'author'
-test_atom head authoremail:trim,localpart 'author'
-test_atom head authoremail:mailmap '<athor@example.com>'
-test_atom head authoremail:mailmap,trim 'athor@example.com'
-test_atom head authoremail:trim,mailmap 'athor@example.com'
-test_atom head authoremail:mailmap,localpart 'athor'
-test_atom head authoremail:localpart,mailmap 'athor'
-test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor'
-test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
-test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
-test_atom head committername 'C O Mitter'
-test_atom head committername:mailmap 'C Mitter'
-test_atom head committeremail '<committer@example.com>'
-test_atom head committeremail:trim 'committer@example.com'
-test_atom head committeremail:localpart 'committer'
-test_atom head committeremail:localpart,trim 'committer'
-test_atom head committeremail:mailmap '<cmitter@example.com>'
-test_atom head committeremail:mailmap,trim 'cmitter@example.com'
-test_atom head committeremail:trim,mailmap 'cmitter@example.com'
-test_atom head committeremail:mailmap,localpart 'cmitter'
-test_atom head committeremail:localpart,mailmap 'cmitter'
-test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter'
-test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
-test_atom head tag ''
-test_atom head tagger ''
-test_atom head taggername ''
-test_atom head taggeremail ''
-test_atom head taggeremail:trim ''
-test_atom head taggeremail:localpart ''
-test_atom head taggerdate ''
-test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
-test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
-test_atom head subject 'Initial'
-test_atom head subject:sanitize 'Initial'
-test_atom head contents:subject 'Initial'
-test_atom head body ''
-test_atom head contents:body ''
-test_atom head contents:signature ''
-test_atom head contents 'Initial
-'
-test_atom head HEAD '*'
-
-test_atom tag refname refs/tags/testtag
-test_atom tag refname:short testtag
-test_atom tag upstream ''
-test_atom tag push ''
-test_atom tag objecttype tag
-test_atom tag objectsize $((114 + hexlen))
-test_atom tag objectsize:disk $(test_object_file_size refs/tags/testtag)
-test_atom tag '*objectsize:disk' $(test_object_file_size refs/heads/main)
-test_atom tag deltabase $ZERO_OID
-test_atom tag '*deltabase' $ZERO_OID
-test_atom tag objectname $(git rev-parse refs/tags/testtag)
-test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
-test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
-test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
-test_atom tag tree ''
-test_atom tag tree:short ''
-test_atom tag tree:short=1 ''
-test_atom tag tree:short=10 ''
-test_atom tag parent ''
-test_atom tag parent:short ''
-test_atom tag parent:short=1 ''
-test_atom tag parent:short=10 ''
-test_atom tag numparent ''
-test_atom tag object $(git rev-parse refs/tags/testtag^0)
-test_atom tag type 'commit'
-test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{})
-test_atom tag '*objecttype' 'commit'
-test_atom tag author ''
-test_atom tag authorname ''
-test_atom tag authorname:mailmap ''
-test_atom tag authoremail ''
-test_atom tag authoremail:trim ''
-test_atom tag authoremail:localpart ''
-test_atom tag authoremail:trim,localpart ''
-test_atom tag authoremail:mailmap ''
-test_atom tag authoremail:mailmap,trim ''
-test_atom tag authoremail:trim,mailmap ''
-test_atom tag authoremail:mailmap,localpart ''
-test_atom tag authoremail:localpart,mailmap ''
-test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim ''
-test_atom tag authordate ''
-test_atom tag committer ''
-test_atom tag committername ''
-test_atom tag committername:mailmap ''
-test_atom tag committeremail ''
-test_atom tag committeremail:trim ''
-test_atom tag committeremail:localpart ''
-test_atom tag committeremail:localpart,trim ''
-test_atom tag committeremail:mailmap ''
-test_atom tag committeremail:mailmap,trim ''
-test_atom tag committeremail:trim,mailmap ''
-test_atom tag committeremail:mailmap,localpart ''
-test_atom tag committeremail:localpart,mailmap ''
-test_atom tag committeremail:trim,mailmap,trim,trim,localpart ''
-test_atom tag committerdate ''
-test_atom tag tag 'testtag'
-test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
-test_atom tag taggername 'C O Mitter'
-test_atom tag taggername:mailmap 'C Mitter'
-test_atom tag taggeremail '<committer@example.com>'
-test_atom tag taggeremail:trim 'committer@example.com'
-test_atom tag taggeremail:localpart 'committer'
-test_atom tag taggeremail:trim,localpart 'committer'
-test_atom tag taggeremail:mailmap '<cmitter@example.com>'
-test_atom tag taggeremail:mailmap,trim 'cmitter@example.com'
-test_atom tag taggeremail:trim,mailmap 'cmitter@example.com'
-test_atom tag taggeremail:mailmap,localpart 'cmitter'
-test_atom tag taggeremail:localpart,mailmap 'cmitter'
-test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter'
-test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
-test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
-test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
-test_atom tag subject 'Tagging at 1151968727'
-test_atom tag subject:sanitize 'Tagging-at-1151968727'
-test_atom tag contents:subject 'Tagging at 1151968727'
-test_atom tag body ''
-test_atom tag contents:body ''
-test_atom tag contents:signature ''
-test_atom tag contents 'Tagging at 1151968727
-'
-test_atom tag HEAD ' '
-
-test_expect_success 'basic atom: refs/tags/testtag *raw' '
-	git cat-file commit refs/tags/testtag^{} >expected &&
-	git for-each-ref --format="%(*raw)" refs/tags/testtag >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_expect_success 'Check invalid atoms names are errors' '
-	test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
-'
-
-test_expect_success 'for-each-ref does not crash with -h' '
+test_expect_success "for-each-ref does not crash with -h" '
 	test_expect_code 129 git for-each-ref -h >usage &&
 	test_grep "[Uu]sage: git for-each-ref " usage &&
 	test_expect_code 129 nongit git for-each-ref -h >usage &&
 	test_grep "[Uu]sage: git for-each-ref " usage
 '
 
-test_expect_success 'Check format specifiers are ignored in naming date atoms' '
-	git for-each-ref --format="%(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate) %(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate:default)" refs/heads
-'
-
-test_expect_success 'Check valid format specifiers for date fields' '
-	git for-each-ref --format="%(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:relative)" refs/heads &&
-	git for-each-ref --format="%(authordate:short)" refs/heads &&
-	git for-each-ref --format="%(authordate:local)" refs/heads &&
-	git for-each-ref --format="%(authordate:iso8601)" refs/heads &&
-	git for-each-ref --format="%(authordate:rfc2822)" refs/heads
-'
-
-test_expect_success 'Check invalid format specifiers are errors' '
-	test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads
-'
-
-test_expect_success 'arguments to %(objectname:short=) must be positive integers' '
-	test_must_fail git for-each-ref --format="%(objectname:short=0)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=-1)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=foo)"
-'
-
-test_bad_atom () {
-	case "$1" in
-	head) ref=refs/heads/main ;;
-	 tag) ref=refs/tags/testtag ;;
-	 sym) ref=refs/heads/sym ;;
-	   *) ref=$1 ;;
-	esac
-	format=$2
-	test_do=test_expect_${4:-success}
-
-	printf '%s\n' "$3" >expect
-	$test_do $PREREQ "err basic atom: $ref $format" '
-		test_must_fail git for-each-ref \
-			--format="%($format)" "$ref" 2>error &&
-		test_cmp expect error
-	'
-}
-
-test_bad_atom head 'authoremail:foo' \
-	'fatal: unrecognized %(authoremail) argument: foo'
-
-test_bad_atom head 'authoremail:mailmap,trim,bar' \
-	'fatal: unrecognized %(authoremail) argument: bar'
-
-test_bad_atom head 'authoremail:trim,' \
-	'fatal: unrecognized %(authoremail) argument: '
-
-test_bad_atom head 'authoremail:mailmaptrim' \
-	'fatal: unrecognized %(authoremail) argument: trim'
-
-test_bad_atom head 'committeremail: ' \
-	'fatal: unrecognized %(committeremail) argument:  '
-
-test_bad_atom head 'committeremail: trim,foo' \
-	'fatal: unrecognized %(committeremail) argument:  trim,foo'
-
-test_bad_atom head 'committeremail:mailmap,localpart ' \
-	'fatal: unrecognized %(committeremail) argument:  '
-
-test_bad_atom head 'committeremail:trim_localpart' \
-	'fatal: unrecognized %(committeremail) argument: _localpart'
-
-test_bad_atom head 'committeremail:localpart,,,trim' \
-	'fatal: unrecognized %(committeremail) argument: ,,trim'
-
-test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \
-	'fatal: unrecognized %(taggeremail) argument:  foo '
-
-test_bad_atom tag 'taggeremail:trim,localpart,' \
-	'fatal: unrecognized %(taggeremail) argument: '
-
-test_bad_atom tag 'taggeremail:mailmap;localpart trim' \
-	'fatal: unrecognized %(taggeremail) argument: ;localpart trim'
-
-test_bad_atom tag 'taggeremail:localpart trim' \
-	'fatal: unrecognized %(taggeremail) argument:  trim'
-
-test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \
-	'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim'
-
-test_date () {
-	f=$1 &&
-	committer_date=$2 &&
-	author_date=$3 &&
-	tagger_date=$4 &&
-	cat >expected <<-EOF &&
-	'refs/heads/main' '$committer_date' '$author_date'
-	'refs/tags/testtag' '$tagger_date'
-	EOF
-	(
-		git for-each-ref --shell \
-			--format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
-			refs/heads &&
-		git for-each-ref --shell \
-			--format="%(refname) %(taggerdate${f:+:$f})" \
-			refs/tags
-	) >actual &&
-	test_cmp expected actual
-}
-
-test_expect_success 'Check unformatted date fields output' '
-	test_date "" \
-		"Tue Jul 4 01:18:43 2006 +0200" \
-		"Tue Jul 4 01:18:44 2006 +0200" \
-		"Tue Jul 4 01:18:45 2006 +0200"
-'
-
-test_expect_success 'Check format "default" formatted date fields output' '
-	test_date default \
-		"Tue Jul 4 01:18:43 2006 +0200" \
-		"Tue Jul 4 01:18:44 2006 +0200" \
-		"Tue Jul 4 01:18:45 2006 +0200"
-'
-
-test_expect_success 'Check format "default-local" date fields output' '
-	test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006"
-'
-
-# Don't know how to do relative check because I can't know when this script
-# is going to be run and can't fake the current time to git, and hence can't
-# provide expected output.  Instead, I'll just make sure that "relative"
-# doesn't exit in error
-test_expect_success 'Check format "relative" date fields output' '
-	f=relative &&
-	(git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-	git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
-'
-
-# We just check that this is the same as "relative" for now.
-test_expect_success 'Check format "relative-local" date fields output' '
-	test_date relative-local \
-		"$(git for-each-ref --format="%(committerdate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(authordate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(taggerdate:relative)" refs/tags)"
-'
-
-test_expect_success 'Check format "short" date fields output' '
-	test_date short 2006-07-04 2006-07-04 2006-07-04
-'
-
-test_expect_success 'Check format "short-local" date fields output' '
-	test_date short-local 2006-07-03 2006-07-03 2006-07-03
-'
-
-test_expect_success 'Check format "local" date fields output' '
-	test_date local \
-		"Mon Jul 3 23:18:43 2006" \
-		"Mon Jul 3 23:18:44 2006" \
-		"Mon Jul 3 23:18:45 2006"
-'
-
-test_expect_success 'Check format "iso8601" date fields output' '
-	test_date iso8601 \
-		"2006-07-04 01:18:43 +0200" \
-		"2006-07-04 01:18:44 +0200" \
-		"2006-07-04 01:18:45 +0200"
-'
-
-test_expect_success 'Check format "iso8601-local" date fields output' '
-	test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000"
-'
-
-test_expect_success 'Check format "rfc2822" date fields output' '
-	test_date rfc2822 \
-		"Tue, 4 Jul 2006 01:18:43 +0200" \
-		"Tue, 4 Jul 2006 01:18:44 +0200" \
-		"Tue, 4 Jul 2006 01:18:45 +0200"
-'
-
-test_expect_success 'Check format "rfc2822-local" date fields output' '
-	test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000"
-'
-
-test_expect_success 'Check format "raw" date fields output' '
-	test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200"
-'
-
-test_expect_success 'Check format "raw-local" date fields output' '
-	test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000"
-'
-
-test_expect_success 'Check format of strftime date fields' '
-	echo "my date is 2006-07-04" >expected &&
-	git for-each-ref \
-	  --format="%(authordate:format:my date is %Y-%m-%d)" \
-	  refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Check format of strftime-local date fields' '
-	echo "my date is 2006-07-03" >expected &&
-	git for-each-ref \
-	  --format="%(authordate:format-local:my date is %Y-%m-%d)" \
-	  refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'exercise strftime with odd fields' '
-	echo >expected &&
-	git for-each-ref --format="%(authordate:format:)" refs/heads >actual &&
-	test_cmp expected actual &&
-	long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" &&
-	echo $long >expected &&
-	git for-each-ref --format="%(authordate:format:$long)" refs/heads >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/heads/main
-refs/remotes/origin/main
-refs/tags/testtag
-EOF
-
-test_expect_success 'Verify ascending sort' '
-	git for-each-ref --format="%(refname)" --sort=refname >actual &&
-	test_cmp expected actual
-'
-
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/remotes/origin/main
-refs/heads/main
-EOF
-
-test_expect_success 'Verify descending sort' '
-	git for-each-ref --format="%(refname)" --sort=-refname >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Give help even with invalid sort atoms' '
-	test_expect_code 129 git for-each-ref --sort=bogus -h >actual 2>&1 &&
-	grep "^usage: git for-each-ref" actual
-'
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/tags/testtag-2
-EOF
-
-test_expect_success 'exercise patterns with prefixes' '
-	git tag testtag-2 &&
-	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/testtag refs/tags/testtag-2 >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/tags/testtag-2
-EOF
-
-test_expect_success 'exercise glob patterns with prefixes' '
-	git tag testtag-2 &&
-	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/testtag "refs/tags/testtag-*" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/bar
-refs/tags/baz
-refs/tags/testtag
-EOF
-
-test_expect_success 'exercise patterns with prefix exclusions' '
-	for tag in foo/one foo/two foo/three bar baz
-	do
-		git tag "$tag" || return 1
-	done &&
-	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/ --exclude=refs/tags/foo >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/bar
-refs/tags/baz
-refs/tags/foo/one
-refs/tags/testtag
-EOF
-
-test_expect_success 'exercise patterns with pattern exclusions' '
-	for tag in foo/one foo/two foo/three bar baz
-	do
-		git tag "$tag" || return 1
-	done &&
-	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/ --exclude="refs/tags/foo/t*" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-'refs/heads/main'
-'refs/remotes/origin/main'
-'refs/tags/testtag'
-EOF
-
-test_expect_success 'Quoting style: shell' '
-	git for-each-ref --shell --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Quoting style: perl' '
-	git for-each-ref --perl --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Quoting style: python' '
-	git for-each-ref --python --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-"refs/heads/main"
-"refs/remotes/origin/main"
-"refs/tags/testtag"
-EOF
-
-test_expect_success 'Quoting style: tcl' '
-	git for-each-ref --tcl --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
-	test_expect_success "more than one quoting style: $i" "
-		test_must_fail git for-each-ref $i 2>err &&
-		grep '^error: more than one quoting style' err
-	"
-done
-
-test_expect_success 'setup for upstream:track[short]' '
-	test_commit two
-'
-
-test_atom head upstream:track '[ahead 1]'
-test_atom head upstream:trackshort '>'
-test_atom head upstream:track,nobracket 'ahead 1'
-test_atom head upstream:nobracket,track 'ahead 1'
-
-test_expect_success 'setup for push:track[short]' '
-	test_commit third &&
-	git update-ref refs/remotes/myfork/main main &&
-	git reset main~1
-'
-
-test_atom head push:track '[behind 1]'
-test_atom head push:trackshort '<'
-
-test_expect_success 'Check that :track[short] cannot be used with other atoms' '
-	test_must_fail git for-each-ref --format="%(refname:track)" 2>/dev/null &&
-	test_must_fail git for-each-ref --format="%(refname:trackshort)" 2>/dev/null
-'
-
-test_expect_success 'Check that :track[short] works when upstream is invalid' '
-	cat >expected <<-\EOF &&
-	[gone]
-
-	EOF
-	test_when_finished "git config branch.main.merge refs/heads/main" &&
-	git config branch.main.merge refs/heads/does-not-exist &&
-	git for-each-ref \
-		--format="%(upstream:track)$LF%(upstream:trackshort)" \
-		refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Check for invalid refname format' '
-	test_must_fail git for-each-ref --format="%(refname:INVALID)"
-'
-
-test_expect_success 'set up color tests' '
-	cat >expected.color <<-EOF &&
-	$(git rev-parse --short refs/heads/main) <GREEN>main<RESET>
-	$(git rev-parse --short refs/remotes/myfork/main) <GREEN>myfork/main<RESET>
-	$(git rev-parse --short refs/remotes/origin/main) <GREEN>origin/main<RESET>
-	$(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET>
-	$(git rev-parse --short refs/tags/third) <GREEN>third<RESET>
-	$(git rev-parse --short refs/tags/two) <GREEN>two<RESET>
-	EOF
-	sed "s/<[^>]*>//g" <expected.color >expected.bare &&
-	color_format="%(objectname:short) %(color:green)%(refname:short)"
-'
-
-test_expect_success TTY '%(color) shows color with a tty' '
-	test_terminal git for-each-ref --format="$color_format" >actual.raw &&
-	test_decode_color <actual.raw >actual &&
-	test_cmp expected.color actual
-'
-
-test_expect_success '%(color) does not show color without tty' '
-	TERM=vt100 git for-each-ref --format="$color_format" >actual &&
-	test_cmp expected.bare actual
-'
-
-test_expect_success '--color can override tty check' '
-	git for-each-ref --color --format="$color_format" >actual.raw &&
-	test_decode_color <actual.raw >actual &&
-	test_cmp expected.color actual
-'
-
-test_expect_success 'color.ui=always does not override tty check' '
-	git -c color.ui=always for-each-ref --format="$color_format" >actual &&
-	test_cmp expected.bare actual
-'
-
-test_expect_success 'setup for describe atom tests' '
-	git init -b master describe-repo &&
-	(
-		cd describe-repo &&
-
-		test_commit --no-tag one &&
-		git tag tagone &&
-
-		test_commit --no-tag two &&
-		git tag -a -m "tag two" tagtwo
-	)
-'
-
-test_expect_success 'describe atom vs git describe' '
-	(
-		cd describe-repo &&
-
-		git for-each-ref --format="%(objectname)" \
-			refs/tags/ >obj &&
-		while read hash
-		do
-			if desc=$(git describe $hash)
-			then
-				: >expect-contains-good
-			else
-				: >expect-contains-bad
-			fi &&
-			echo "$hash $desc" || return 1
-		done <obj >expect &&
-		test_path_exists expect-contains-good &&
-		test_path_exists expect-contains-bad &&
-
-		git for-each-ref --format="%(objectname) %(describe)" \
-			refs/tags/ >actual 2>err &&
-		test_cmp expect actual &&
-		test_must_be_empty err
-	)
-'
-
-test_expect_success 'describe:tags vs describe --tags' '
-	(
-		cd describe-repo &&
-		git describe --tags >expect &&
-		git for-each-ref --format="%(describe:tags)" \
-				refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
-	(
-		cd describe-repo &&
-
-		# Case 1: We have commits between HEAD and the most
-		#	  recent tag reachable from it
-		test_commit --no-tag file &&
-		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
-			refs/heads/master >actual &&
-		test_cmp expect actual &&
-
-		# Make sure the hash used is at least 14 digits long
-		sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart &&
-		test 15 -le $(wc -c <hexpart) &&
-
-		# Case 2: We have a tag at HEAD, describe directly gives
-		#	  the name of the tag
-		git tag -a -m tagged tagname &&
-		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
-			refs/heads/master >actual &&
-		test_cmp expect actual &&
-		test tagname = $(cat actual)
-	)
-'
-
-test_expect_success 'describe:match=... vs describe --match ...' '
-	(
-		cd describe-repo &&
-		git tag -a -m "tag foo" tag-foo &&
-		git describe --match "*-foo" >expect &&
-		git for-each-ref --format="%(describe:match="*-foo")" \
-			refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'describe:exclude:... vs describe --exclude ...' '
-	(
-		cd describe-repo &&
-		git tag -a -m "tag bar" tag-bar &&
-		git describe --exclude "*-bar" >expect &&
-		git for-each-ref --format="%(describe:exclude="*-bar")" \
-			refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'deref with describe atom' '
-	(
-		cd describe-repo &&
-		cat >expect <<-\EOF &&
-
-		tagname
-		tagname
-		tagname
-
-		tagtwo
-		EOF
-		git for-each-ref --format="%(*describe)" >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'err on bad describe atom arg' '
-	(
-		cd describe-repo &&
-
-		# The bad arg is the only arg passed to describe atom
-		cat >expect <<-\EOF &&
-		fatal: unrecognized %(describe) argument: baz
-		EOF
-		test_must_fail git for-each-ref --format="%(describe:baz)" \
-			refs/heads/master 2>actual &&
-		test_cmp expect actual &&
-
-		# The bad arg is in the middle of the option string
-		# passed to the describe atom
-		cat >expect <<-\EOF &&
-		fatal: unrecognized %(describe) argument: qux=1,abbrev=14
-		EOF
-		test_must_fail git for-each-ref \
-			--format="%(describe:tags,qux=1,abbrev=14)" \
-			ref/heads/master 2>actual &&
-		test_cmp expect actual
-	)
-'
-
-cat >expected <<\EOF
-heads/main
-tags/main
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs (strict)' '
-	git config --bool core.warnambiguousrefs true &&
-	git checkout -b newtag &&
-	echo "Using $datestamp" > one &&
-	git add one &&
-	git commit -m "Branch" &&
-	setdate_and_increment &&
-	git tag -m "Tagging at $datestamp" main &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-heads/main
-main
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs (loose)' '
-	git config --bool core.warnambiguousrefs false &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-heads/ambiguous
-ambiguous
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs II (loose)' '
-	git checkout main &&
-	git tag ambiguous testtag^0 &&
-	git branch ambiguous testtag^0 &&
-	git for-each-ref --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'create tag without tagger' '
-	git tag -a -m "Broken tag" taggerless &&
-	git tag -f taggerless $(git cat-file tag taggerless |
-		sed -e "/^tagger /d" |
-		git hash-object --literally --stdin -w -t tag)
-'
-
-test_atom refs/tags/taggerless type 'commit'
-test_atom refs/tags/taggerless tag 'taggerless'
-test_atom refs/tags/taggerless tagger ''
-test_atom refs/tags/taggerless taggername ''
-test_atom refs/tags/taggerless taggeremail ''
-test_atom refs/tags/taggerless taggeremail:trim ''
-test_atom refs/tags/taggerless taggeremail:localpart ''
-test_atom refs/tags/taggerless taggerdate ''
-test_atom refs/tags/taggerless committer ''
-test_atom refs/tags/taggerless committername ''
-test_atom refs/tags/taggerless committeremail ''
-test_atom refs/tags/taggerless committeremail:trim ''
-test_atom refs/tags/taggerless committeremail:localpart ''
-test_atom refs/tags/taggerless committerdate ''
-test_atom refs/tags/taggerless subject 'Broken tag'
-
-test_expect_success 'an unusual tag with an incomplete line' '
-
-	git tag -m "bogo" bogo &&
-	bogo=$(git cat-file tag bogo) &&
-	bogo=$(printf "%s" "$bogo" | git mktag) &&
-	git tag -f bogo "$bogo" &&
-	git for-each-ref --format "%(body)" refs/tags/bogo
-
-'
-
-test_expect_success 'create tag with subject and body content' '
-	cat >>msg <<-\EOF &&
-		the subject line
-
-		first body line
-		second body line
-	EOF
-	git tag -F msg subject-body
-'
-test_atom refs/tags/subject-body subject 'the subject line'
-test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
-test_atom refs/tags/subject-body body 'first body line
-second body line
-'
-test_atom refs/tags/subject-body contents 'the subject line
-
-first body line
-second body line
-'
-
-test_expect_success 'create tag with multiline subject' '
-	cat >msg <<-\EOF &&
-		first subject line
-		second subject line
-
-		first body line
-		second body line
-	EOF
-	git tag -F msg multiline
-'
-test_atom refs/tags/multiline subject 'first subject line second subject line'
-test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
-test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
-test_atom refs/tags/multiline body 'first body line
-second body line
-'
-test_atom refs/tags/multiline contents:body 'first body line
-second body line
-'
-test_atom refs/tags/multiline contents:signature ''
-test_atom refs/tags/multiline contents 'first subject line
-second subject line
-
-first body line
-second body line
-'
-
-test_expect_success GPG 'create signed tags' '
-	git tag -s -m "" signed-empty &&
-	git tag -s -m "subject line" signed-short &&
-	cat >msg <<-\EOF &&
-	subject line
-
-	body contents
-	EOF
-	git tag -s -F msg signed-long
-'
-
-sig='-----BEGIN PGP SIGNATURE-----
------END PGP SIGNATURE-----
-'
-
-PREREQ=GPG
-test_atom refs/tags/signed-empty subject ''
-test_atom refs/tags/signed-empty subject:sanitize ''
-test_atom refs/tags/signed-empty contents:subject ''
-test_atom refs/tags/signed-empty body "$sig"
-test_atom refs/tags/signed-empty contents:body ''
-test_atom refs/tags/signed-empty contents:signature "$sig"
-test_atom refs/tags/signed-empty contents "$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
-	git cat-file tag refs/tags/signed-empty >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-empty >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_atom refs/tags/signed-short subject 'subject line'
-test_atom refs/tags/signed-short subject:sanitize 'subject-line'
-test_atom refs/tags/signed-short contents:subject 'subject line'
-test_atom refs/tags/signed-short body "$sig"
-test_atom refs/tags/signed-short contents:body ''
-test_atom refs/tags/signed-short contents:signature "$sig"
-test_atom refs/tags/signed-short contents "subject line
-$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
-	git cat-file tag refs/tags/signed-short >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-short >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_atom refs/tags/signed-long subject 'subject line'
-test_atom refs/tags/signed-long subject:sanitize 'subject-line'
-test_atom refs/tags/signed-long contents:subject 'subject line'
-test_atom refs/tags/signed-long body "body contents
-$sig"
-test_atom refs/tags/signed-long contents:body 'body contents
-'
-test_atom refs/tags/signed-long contents:signature "$sig"
-test_atom refs/tags/signed-long contents "subject line
-
-body contents
-$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
-	git cat-file tag refs/tags/signed-long >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-long >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_expect_success 'set up refs pointing to tree and blob' '
-	git update-ref refs/mytrees/first refs/heads/main^{tree} &&
-	git update-ref refs/myblobs/first refs/heads/main:one
-'
-
-test_atom refs/mytrees/first subject ""
-test_atom refs/mytrees/first contents:subject ""
-test_atom refs/mytrees/first body ""
-test_atom refs/mytrees/first contents:body ""
-test_atom refs/mytrees/first contents:signature ""
-test_atom refs/mytrees/first contents ""
-
-test_expect_success 'basic atom: refs/mytrees/first raw' '
-	git cat-file tree refs/mytrees/first >expected &&
-	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/mytrees/first >actual &&
-	test_cmp expected actual &&
-	git cat-file -s refs/mytrees/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_atom refs/myblobs/first subject ""
-test_atom refs/myblobs/first contents:subject ""
-test_atom refs/myblobs/first body ""
-test_atom refs/myblobs/first contents:body ""
-test_atom refs/myblobs/first contents:signature ""
-test_atom refs/myblobs/first contents ""
-
-test_expect_success 'basic atom: refs/myblobs/first raw' '
-	git cat-file blob refs/myblobs/first >expected &&
-	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/myblobs/first >actual &&
-	test_cmp expected actual &&
-	git cat-file -s refs/myblobs/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/myblobs/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'set up refs pointing to binary blob' '
-	printf "a\0b\0c" >blob1 &&
-	printf "a\0c\0b" >blob2 &&
-	printf "\0a\0b\0c" >blob3 &&
-	printf "abc" >blob4 &&
-	printf "\0 \0 \0 " >blob5 &&
-	printf "\0 \0a\0 " >blob6 &&
-	printf "  " >blob7 &&
-	>blob8 &&
-	obj=$(git hash-object -w blob1) &&
-	git update-ref refs/myblobs/blob1 "$obj" &&
-	obj=$(git hash-object -w blob2) &&
-	git update-ref refs/myblobs/blob2 "$obj" &&
-	obj=$(git hash-object -w blob3) &&
-	git update-ref refs/myblobs/blob3 "$obj" &&
-	obj=$(git hash-object -w blob4) &&
-	git update-ref refs/myblobs/blob4 "$obj" &&
-	obj=$(git hash-object -w blob5) &&
-	git update-ref refs/myblobs/blob5 "$obj" &&
-	obj=$(git hash-object -w blob6) &&
-	git update-ref refs/myblobs/blob6 "$obj" &&
-	obj=$(git hash-object -w blob7) &&
-	git update-ref refs/myblobs/blob7 "$obj" &&
-	obj=$(git hash-object -w blob8) &&
-	git update-ref refs/myblobs/blob8 "$obj"
-'
-
-test_expect_success 'Verify sorts with raw' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob8
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/blob3
-	refs/myblobs/blob7
-	refs/mytrees/first
-	refs/myblobs/first
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob4
-	refs/heads/main
-	EOF
-	git for-each-ref --format="%(refname)" --sort=raw \
-		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Verify sorts with raw:size' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob8
-	refs/myblobs/blob7
-	refs/myblobs/blob4
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob3
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/first
-	refs/mytrees/first
-	refs/heads/main
-	EOF
-	git for-each-ref --format="%(refname)" --sort=raw:size \
-		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'validate raw atom with %(if:equals)' '
-	cat >expected <<-EOF &&
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	refs/myblobs/blob4
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	EOF
-	git for-each-ref --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
-		refs/myblobs/ refs/heads/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'validate raw atom with %(if:notequals)' '
-	cat >expected <<-EOF &&
-	refs/heads/ambiguous
-	refs/heads/main
-	refs/heads/newtag
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob3
-	equals
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/blob7
-	refs/myblobs/blob8
-	refs/myblobs/first
-	EOF
-	git for-each-ref --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
-		refs/myblobs/ refs/heads/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'empty raw refs with %(if)' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob1 not empty
-	refs/myblobs/blob2 not empty
-	refs/myblobs/blob3 not empty
-	refs/myblobs/blob4 not empty
-	refs/myblobs/blob5 not empty
-	refs/myblobs/blob6 not empty
-	refs/myblobs/blob7 empty
-	refs/myblobs/blob8 empty
-	refs/myblobs/first not empty
-	EOF
-	git for-each-ref --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
-		refs/myblobs/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '%(raw) with --python must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --python
-'
-
-test_expect_success '%(raw) with --tcl must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --tcl
-'
-
-test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' '
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
-	cmp blob1 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
-	cmp blob3 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
-	cmp blob8 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
-	cmp one actual &&
-	git cat-file tree refs/mytrees/first > expected &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
-	cmp expected actual
-'
-
-test_expect_success '%(raw) with --shell must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --shell
-'
-
-test_expect_success '%(raw) with --shell and --sort=raw must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --sort=raw --shell
-'
-
-test_expect_success '%(raw:size) with --shell' '
-	git for-each-ref --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
-	git for-each-ref --format="%(raw:size)" --shell >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --format compare with cat-file --batch' '
-	git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
-	git for-each-ref --format="%(objectname) %(objecttype) %(objectsize)
-%(raw)" refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'verify sorts with contents:size' '
-	cat >expect <<-\EOF &&
-	refs/heads/main
-	refs/heads/newtag
-	refs/heads/ambiguous
-	EOF
-	git for-each-ref --format="%(refname)" \
-		--sort=contents:size refs/heads/ >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'set up multiple-sort tags' '
-	for when in 100000 200000
-	do
-		for email in user1 user2
-		do
-			for ref in ref1 ref2
-			do
-				GIT_COMMITTER_DATE="@$when +0000" \
-				GIT_COMMITTER_EMAIL="$email@example.com" \
-				git tag -m "tag $ref-$when-$email" \
-				multi-$ref-$when-$email || return 1
-			done
-		done
-	done
-'
-
-test_expect_success 'Verify sort with multiple keys' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=-refname \
-		--sort=taggeremail \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'equivalent sorts fall back on refname' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '--no-sort cancels the previous sort keys' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=-refname \
-		--sort=taggeremail \
-		--no-sort \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '--no-sort without subsequent --sort prints expected refs' '
-	cat >expected <<-\EOF &&
-	refs/tags/multi-ref1-100000-user1
-	refs/tags/multi-ref1-100000-user2
-	refs/tags/multi-ref1-200000-user1
-	refs/tags/multi-ref1-200000-user2
-	refs/tags/multi-ref2-100000-user1
-	refs/tags/multi-ref2-100000-user2
-	refs/tags/multi-ref2-200000-user1
-	refs/tags/multi-ref2-200000-user2
-	EOF
-
-	# Sort the results with `sort` for a consistent comparison against
-	# expected
-	git for-each-ref \
-		--format="%(refname)" \
-		--no-sort \
-		"refs/tags/multi-*" | sort >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'set up custom date sorting' '
-	# Dates:
-	# - Wed Feb 07 2024 21:34:20 +0000
-	# - Tue Dec 14 1999 00:05:22 +0000
-	# - Fri Jun 04 2021 11:26:51 +0000
-	# - Mon Jan 22 2007 16:44:01 GMT+0000
-	i=1 &&
-	for when in 1707341660 945129922 1622806011 1169484241
-	do
-		GIT_COMMITTER_DATE="@$when +0000" \
-		GIT_COMMITTER_EMAIL="user@example.com" \
-		git tag -m "tag $when" custom-dates-$i &&
-		i=$(($i+1)) || return 1
-	done
-'
-
-test_expect_success 'sort by date defaults to full timestamp' '
-	cat >expected <<-\EOF &&
-	945129922 refs/tags/custom-dates-2
-	1169484241 refs/tags/custom-dates-4
-	1622806011 refs/tags/custom-dates-3
-	1707341660 refs/tags/custom-dates-1
-	EOF
-
-	git for-each-ref \
-		--format="%(creatordate:unix) %(refname)" \
-		--sort=creatordate \
-		"refs/tags/custom-dates-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'sort by custom date format' '
-	cat >expected <<-\EOF &&
-	00:05:22 refs/tags/custom-dates-2
-	11:26:51 refs/tags/custom-dates-3
-	16:44:01 refs/tags/custom-dates-4
-	21:34:20 refs/tags/custom-dates-1
-	EOF
-
-	git for-each-ref \
-		--format="%(creatordate:format:%H:%M:%S) %(refname)" \
-		--sort="creatordate:format:%H:%M:%S" \
-		"refs/tags/custom-dates-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
-	test_when_finished "git checkout main" &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
-	sed -e "s/^\* /  /" actual >expect &&
-	git checkout --orphan orphaned-branch &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
-	test_cmp expect actual
-'
-
-cat >trailers <<EOF
-Reviewed-by: A U Thor <author@example.com>
-Signed-off-by: A U Thor <author@example.com>
-[ v2 updated patch description ]
-Acked-by: A U Thor
-  <author@example.com>
-EOF
-
-unfold () {
-	perl -0pe 's/\n\s+/ /g'
-}
-
-test_expect_success 'set up trailers for next test' '
-	echo "Some contents" > two &&
-	git add two &&
-	git commit -F - <<-EOF
-	trailers: this commit message has trailers
-
-	Some message contents
-
-	$(cat trailers)
-	EOF
-'
-
-test_trailer_option () {
-	if test "$#" -eq 3
-	then
-		prereq="$1"
-		shift
-	fi &&
-	title=$1 option=$2
-	cat >expect
-	test_expect_success $prereq "$title" '
-		git for-each-ref --format="%($option)" refs/heads/main >actual &&
-		test_cmp expect actual &&
-		git for-each-ref --format="%(contents:$option)" refs/heads/main >actual &&
-		test_cmp expect actual
-	'
-}
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' \
-	'trailers:unfold' <<-EOF
-	$(unfold <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only) shows only "key: value" trailers' \
-	'trailers:only' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=no,only=true) shows only "key: value" trailers' \
-	'trailers:only=no,only=true' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=yes) shows only "key: value" trailers' \
-	'trailers:only=yes' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=no) shows all trailers' \
-	'trailers:only=no' <<-EOF
-	$(cat trailers)
-
-	EOF
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:only) and %(trailers:unfold) work together' \
-	'trailers:only,unfold' <<-EOF
-	$(grep -v patch.description <trailers | unfold)
-
-	EOF
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) and %(trailers:only) work together' \
-	'trailers:unfold,only' <<-EOF
-	$(grep -v patch.description <trailers | unfold)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) shows that trailer' \
-	'trailers:key=Signed-off-by' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) is case insensitive' \
-	'trailers:key=SiGned-oFf-bY' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo:) trailing colon also works' \
-	'trailers:key=Signed-off-by:' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) multiple keys' \
-	'trailers:key=Reviewed-by:,key=Signed-off-by' <<-EOF
-	Reviewed-by: A U Thor <author@example.com>
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=nonexistent) becomes empty' \
-	'trailers:key=Shined-off-by:' <<-EOF
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) handles multiple lines even if folded' \
-	'trailers:key=Acked-by' <<-EOF
-	$(grep -v patch.description <trailers | grep -v Signed-off-by | grep -v Reviewed-by)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,unfold) properly unfolds' \
-	'trailers:key=Signed-Off-by,unfold' <<-EOF
-	$(unfold <trailers | grep Signed-off-by)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,only=no) also includes nontrailer lines' \
-	'trailers:key=Signed-off-by,only=no' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-	$(grep patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,valueonly) shows only value' \
-	'trailers:key=Signed-off-by,valueonly' <<-EOF
-	A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:separator) changes separator' \
-	'trailers:separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by: A U Thor <author@example.com>,Signed-off-by: A U Thor <author@example.com>
-	EOF
-
-test_trailer_option '%(trailers:key_value_separator) changes key-value separator' \
-	'trailers:key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by,A U Thor <author@example.com>
-	Signed-off-by,A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:separator,key_value_separator) changes both separators' \
-	'trailers:separator=%x2C,key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by,A U Thor <author@example.com>,Signed-off-by,A U Thor <author@example.com>
-	EOF
-
-test_expect_success 'multiple %(trailers) use their own options' '
-	git tag -F - tag-with-trailers <<-\EOF &&
-	body
-
-	one: foo
-	one: bar
-	two: baz
-	two: qux
-	EOF
-	t1="%(trailers:key=one,key_value_separator=W,separator=X)" &&
-	t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" &&
-	git for-each-ref --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
-	cat >expect <<-\EOF &&
-	oneWfooXoneWbar
-	twoYbazZtwoYqux
-	EOF
-	test_cmp expect actual
-'
-
-test_failing_trailer_option () {
-	title=$1 option=$2
-	cat >expect
-	test_expect_success "$title" '
-		# error message cannot be checked under i18n
-		test_must_fail git for-each-ref --format="%($option)" refs/heads/main 2>actual &&
-		test_cmp expect actual &&
-		test_must_fail git for-each-ref --format="%(contents:$option)" refs/heads/main 2>actual &&
-		test_cmp expect actual
-	'
-}
-
-test_failing_trailer_option '%(trailers) rejects unknown trailers arguments' \
-	'trailers:unsupported' <<-\EOF
-	fatal: unknown %(trailers) argument: unsupported
-	EOF
-
-test_failing_trailer_option '%(trailers:key) without value is error' \
-	'trailers:key' <<-\EOF
-	fatal: expected %(trailers:key=<value>)
-	EOF
-
-test_expect_success 'if arguments, %(contents:trailers) shows error if colon is missing' '
-	cat >expect <<-EOF &&
-	fatal: unrecognized %(contents) argument: trailersonly
-	EOF
-	test_must_fail git for-each-ref --format="%(contents:trailersonly)" 2>actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'basic atom: head contents:trailers' '
-	git for-each-ref --format="%(contents:trailers)" refs/heads/main >actual &&
-	sanitize_pgp <actual >actual.clean &&
-	# git for-each-ref ends with a blank line
-	cat >expect <<-EOF &&
-	$(cat trailers)
-
-	EOF
-	test_cmp expect actual.clean
-'
-
-test_expect_success 'basic atom: rest must fail' '
-	test_must_fail git for-each-ref --format="%(rest)" refs/heads/main
-'
-
-test_expect_success 'HEAD atom does not take arguments' '
-	test_must_fail git for-each-ref --format="%(HEAD:foo)" 2>err &&
-	echo "fatal: %(HEAD) does not take arguments" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'subject atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(subject:foo)" 2>err &&
-	echo "fatal: unrecognized %(subject) argument: foo" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'refname atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(refname:foo)" 2>err &&
-	echo "fatal: unrecognized %(refname) argument: foo" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'trailer parsing not fooled by --- line' '
-	git commit --allow-empty -F - <<-\EOF &&
-	this is the subject
-
-	This is the body. The message has a "---" line which would confuse a
-	message+patch parser. But here we know we have only a commit message,
-	so we get it right.
-
-	trailer: wrong
-	---
-	This is more body.
-
-	trailer: right
-	EOF
-
-	{
-		echo "trailer: right" &&
-		echo
-	} >expect &&
-	git for-each-ref --format="%(trailers)" refs/heads/main >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'Add symbolic ref for the following tests' '
-	git symbolic-ref refs/heads/sym refs/heads/main
-'
-
-cat >expected <<EOF
-refs/heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref) atom' '
-	git for-each-ref --format="%(symref)" refs/heads/sym >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref:short) atom' '
-	git for-each-ref --format="%(symref:short)" refs/heads/sym >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-main
-heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref:lstrip) atom' '
-	git for-each-ref --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual &&
-
-	git for-each-ref --format="%(symref:strip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-refs
-refs/heads
-EOF
-
-test_expect_success 'Verify usage of %(symref:rstrip) atom' '
-	git for-each-ref --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual
-'
-
-test_expect_success ':remotename and :remoteref' '
-	git init remote-tests &&
-	(
-		cd remote-tests &&
-		test_commit initial &&
-		git branch -M main &&
-		git remote add from fifth.coffee:blub &&
-		git config branch.main.remote from &&
-		git config branch.main.merge refs/heads/stable &&
-		git remote add to southridge.audio:repo &&
-		git config remote.to.push "refs/heads/*:refs/heads/pushed/*" &&
-		git config branch.main.pushRemote to &&
-		for pair in "%(upstream)=refs/remotes/from/stable" \
-			"%(upstream:remotename)=from" \
-			"%(upstream:remoteref)=refs/heads/stable" \
-			"%(push)=refs/remotes/to/pushed/main" \
-			"%(push:remotename)=to" \
-			"%(push:remoteref)=refs/heads/pushed/main"
-		do
-			echo "${pair#*=}" >expect &&
-			git for-each-ref --format="${pair%=*}" \
-				refs/heads/main >actual &&
-			test_cmp expect actual || exit 1
-		done &&
-		git branch push-simple &&
-		git config branch.push-simple.pushRemote from &&
-		actual="$(git for-each-ref \
-			--format="%(push:remotename),%(push:remoteref)" \
-			refs/heads/push-simple)" &&
-		test from, = "$actual"
-	)
-'
-
-test_expect_success 'for-each-ref --ignore-case ignores case' '
-	git for-each-ref --format="%(refname)" refs/heads/MAIN >actual &&
-	test_must_be_empty actual &&
-
-	echo refs/heads/main >expect &&
-	git for-each-ref --format="%(refname)" --ignore-case \
-		refs/heads/MAIN >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --omit-empty works' '
-	git for-each-ref --format="%(refname)" >actual &&
-	test_line_count -gt 1 actual &&
-	git for-each-ref --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
-	echo refs/heads/main >expect &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
-	# name refs numerically to avoid case-insensitive filesystem conflicts
-	nr=0 &&
-	for email in a A b B
-	do
-		for subject in a A b B
-		do
-			GIT_COMMITTER_EMAIL="$email@example.com" \
-			git tag -m "tag $subject" icase-$(printf %02d $nr) &&
-			nr=$((nr+1))||
-			return 1
-		done
-	done &&
-	git for-each-ref --ignore-case \
-		--format="%(taggeremail) %(subject) %(refname)" \
-		--sort=refname \
-		--sort=subject \
-		--sort=taggeremail \
-		refs/tags/icase-* >actual &&
-	cat >expect <<-\EOF &&
-	<a@example.com> tag a refs/tags/icase-00
-	<a@example.com> tag A refs/tags/icase-01
-	<A@example.com> tag a refs/tags/icase-04
-	<A@example.com> tag A refs/tags/icase-05
-	<a@example.com> tag b refs/tags/icase-02
-	<a@example.com> tag B refs/tags/icase-03
-	<A@example.com> tag b refs/tags/icase-06
-	<A@example.com> tag B refs/tags/icase-07
-	<b@example.com> tag a refs/tags/icase-08
-	<b@example.com> tag A refs/tags/icase-09
-	<B@example.com> tag a refs/tags/icase-12
-	<B@example.com> tag A refs/tags/icase-13
-	<b@example.com> tag b refs/tags/icase-10
-	<b@example.com> tag B refs/tags/icase-11
-	<B@example.com> tag b refs/tags/icase-14
-	<B@example.com> tag B refs/tags/icase-15
-	EOF
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref reports broken tags' '
-	git tag -m "good tag" broken-tag-good HEAD &&
-	git cat-file tag broken-tag-good >good &&
-	sed s/commit/blob/ <good >bad &&
-	bad=$(git hash-object -w -t tag bad) &&
-	git update-ref refs/tags/broken-tag-bad $bad &&
-	test_must_fail git for-each-ref --format="%(*objectname)" \
-		refs/tags/broken-tag-*
-'
-
-test_expect_success 'set up tag with signature and no blank lines' '
-	git tag -F - fake-sig-no-blanks <<-\EOF
-	this is the subject
-	-----BEGIN PGP SIGNATURE-----
-	not a real signature, but we just care about the
-	subject/body parsing. It is important here that
-	there are no blank lines in the signature.
-	-----END PGP SIGNATURE-----
-	EOF
-'
-
-test_atom refs/tags/fake-sig-no-blanks contents:subject 'this is the subject'
-test_atom refs/tags/fake-sig-no-blanks contents:body ''
-test_atom refs/tags/fake-sig-no-blanks contents:signature "$sig"
-
-test_expect_success 'set up tag with CRLF signature' '
-	append_cr <<-\EOF |
-	this is the subject
-	-----BEGIN PGP SIGNATURE-----
-
-	not a real signature, but we just care about
-	the subject/body parsing. It is important here
-	that there is a blank line separating this
-	from the signature header.
-	-----END PGP SIGNATURE-----
-	EOF
-	git tag -F - --cleanup=verbatim fake-sig-crlf
-'
-
-test_atom refs/tags/fake-sig-crlf contents:subject 'this is the subject'
-test_atom refs/tags/fake-sig-crlf contents:body ''
-
-# CRLF is retained in the signature, so we have to pass our expected value
-# through append_cr. But test_atom requires a shell string, which means command
-# substitution, and the shell will strip trailing newlines from the output of
-# the substitution. Hack around it by adding and then removing a dummy line.
-sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
-sig_crlf=${sig_crlf%dummy}
-test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
-
-test_expect_success 'set up tag with signature and trailers' '
-	git tag -F - fake-sig-trailer <<-\EOF
-	this is the subject
-
-	this is the body
-
-	My-Trailer: foo
-	-----BEGIN PGP SIGNATURE-----
-
-	not a real signature, but we just care about the
-	subject/body/trailer parsing.
-	-----END PGP SIGNATURE-----
-	EOF
-'
-
-# use "separator=" here to suppress the terminating newline
-test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo'
-
-test_expect_success 'git for-each-ref --stdin: empty' '
-	>in &&
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	git for-each-ref --format="%(refname)" >expect &&
-	test_cmp expect actual
-'
-
-test_expect_success 'git for-each-ref --stdin: fails if extra args' '
-	>in &&
-	test_must_fail git for-each-ref --format="%(refname)" \
-		--stdin refs/heads/extra <in 2>err &&
-	grep "unknown arguments supplied with --stdin" err
-'
-
-test_expect_success 'git for-each-ref --stdin: matches' '
-	cat >in <<-EOF &&
-	refs/tags/multi*
-	refs/heads/amb*
-	EOF
-
-	cat >expect <<-EOF &&
-	refs/heads/ambiguous
-	refs/tags/multi-ref1-100000-user1
-	refs/tags/multi-ref1-100000-user2
-	refs/tags/multi-ref1-200000-user1
-	refs/tags/multi-ref1-200000-user2
-	refs/tags/multi-ref2-100000-user1
-	refs/tags/multi-ref2-100000-user2
-	refs/tags/multi-ref2-200000-user1
-	refs/tags/multi-ref2-200000-user2
-	refs/tags/multiline
-	EOF
-
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'git for-each-ref with non-existing refs' '
-	cat >in <<-EOF &&
-	refs/heads/this-ref-does-not-exist
-	refs/tags/bogus
-	EOF
-
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	test_must_be_empty actual &&
-
-	xargs git for-each-ref --format="%(refname)" <in >actual &&
-	test_must_be_empty actual
-'
-
-test_expect_success 'git for-each-ref with nested tags' '
-	git tag -am "Normal tag" nested/base HEAD &&
-	git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
-	git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
-
-	head_oid="$(git rev-parse HEAD)" &&
-	base_tag_oid="$(git rev-parse refs/tags/nested/base)" &&
-	nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" &&
-	nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" &&
-
-	cat >expect <<-EOF &&
-	refs/tags/nested/base $base_tag_oid tag $head_oid commit
-	refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit
-	refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
-	EOF
-
-	git for-each-ref \
-		--format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
-		refs/tags/nested/ >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'is-base atom with non-commits' '
-	git for-each-ref --format="%(is-base:HEAD) %(refname)" >out 2>err &&
-	grep "(HEAD) refs/heads/main" out &&
-
-	test_line_count = 2 err &&
-	grep "error: object .* is a commit, not a blob" err &&
-	grep "error: bad tag pointer to" err
-'
-
-GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
-TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
-
-test_expect_success GPG 'setup for signature atom using gpg' '
-	git checkout -b signed &&
-
-	test_when_finished "test_unconfig commit.gpgSign" &&
-
-	echo "1" >file &&
-	git add file &&
-	test_tick &&
-	git commit -S -m "file: 1" &&
-	git tag first-signed &&
-
-	echo "2" >file &&
-	test_tick &&
-	git commit -a -m "file: 2" &&
-	git tag second-unsigned &&
-
-	git config commit.gpgSign 1 &&
-	echo "3" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 3" &&
-	git tag third-unsigned &&
-
-	test_tick &&
-	git rebase -f HEAD^^ && git tag second-signed HEAD^ &&
-	git tag third-signed &&
-
-	echo "4" >file &&
-	test_tick &&
-	git commit -a -SB7227189 -m "file: 4" &&
-	git tag fourth-signed &&
-
-	echo "5" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 5" &&
-	git tag fifth-unsigned &&
-
-	echo "6" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 6" &&
-
-	test_tick &&
-	git rebase -f HEAD^^ &&
-	git tag fifth-signed HEAD^ &&
-	git tag sixth-signed &&
-
-	echo "7" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 7" &&
-	git tag seventh-unsigned
-'
-
-test_expect_success GPGSSH 'setup for signature atom using ssh' '
-	test_when_finished "test_unconfig gpg.format user.signingkey" &&
-
-	test_config gpg.format ssh &&
-	test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
-	echo "8" >file &&
-	test_tick &&
-	git add file &&
-	git commit -S -m "file: 8" &&
-	git tag eighth-signed-ssh
-'
-
-test_expect_success GPG2 'bare signature atom' '
-	git verify-commit first-signed 2>expect &&
-	echo  >>expect &&
-	git for-each-ref refs/tags/first-signed \
-		--format="%(signature)" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show good signature with custom format' '
-	git verify-commit first-signed &&
-	cat >expect <<-\EOF &&
-	G
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	EOF
-	git for-each-ref refs/tags/first-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-test_expect_success GPGSSH 'show good signature with custom format with ssh' '
-	test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
-	FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
-	cat >expect.tmpl <<-\EOF &&
-	G
-	FINGERPRINT
-	principal with number 1
-	FINGERPRINT
-
-	EOF
-	sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
-	git for-each-ref refs/tags/eighth-signed-ssh \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'signature atom with grade option and bad signature' '
-	git cat-file commit third-signed >raw &&
-	sed -e "s/^file: 3/file: 3 forged/" raw >forged1 &&
-	FORGED1=$(git hash-object -w -t commit forged1) &&
-	git update-ref refs/tags/third-signed "$FORGED1" &&
-	test_must_fail git verify-commit "$FORGED1" &&
-
-	cat >expect <<-\EOF &&
-	B
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-
-
-	EOF
-	git for-each-ref refs/tags/third-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with custom format' '
-	cat >expect <<-\EOF &&
-	U
-	65A0EEA02E30CAD7
-	Eris Discordia <discord@example.net>
-	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
-	D4BE22311AD3131E5EDA29A461092E85B7227189
-	EOF
-	git for-each-ref refs/tags/fourth-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with undefined trust level' '
-	cat >expect <<-\EOF &&
-	undefined
-	65A0EEA02E30CAD7
-	Eris Discordia <discord@example.net>
-	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
-	D4BE22311AD3131E5EDA29A461092E85B7227189
-	EOF
-	git for-each-ref refs/tags/fourth-signed \
-		--format="$TRUSTLEVEL_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with ultimate trust level' '
-	cat >expect <<-\EOF &&
-	ultimate
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	EOF
-	git for-each-ref refs/tags/sixth-signed \
-		--format="$TRUSTLEVEL_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show unknown signature with custom format' '
-	cat >expect <<-\EOF &&
-	E
-	13B6F51ECDDE430D
-
-
-
-	EOF
-	GNUPGHOME="$GNUPGHOME_NOT_USED" git for-each-ref \
-		refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show lack of signature with custom format' '
-	cat >expect <<-\EOF &&
-	N
-
-
-
-
-	EOF
-	git for-each-ref refs/tags/seventh-unsigned \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
+. "$TEST_DIRECTORY"/for-each-ref-tests.sh
 
 test_done
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v3 3/3] t: add test for git refs list subcommand
  2025-07-23  6:43   ` [GSoC][RFC PATCH v3 0/3] Add " Meet Soni
  2025-07-23  6:43     ` [GSoC][RFC PATCH v3 1/3] builtin/refs: add " Meet Soni
  2025-07-23  6:43     ` [GSoC][RFC PATCH v3 2/3] t6300: refactor tests to be shareable Meet Soni
@ 2025-07-23  6:43     ` Meet Soni
  2025-07-31  9:00     ` [GSoC][RFC PATCH v4 0/5] Add " Meet Soni
  3 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-07-23  6:43 UTC (permalink / raw)
  To: git; +Cc: ps, shejialuo, karthik.188, gitster, sunshine, Meet Soni
Add a test script, `t/t1461-refs-list.sh`, for the new `git refs list`
command.
This script acts as a simple driver, leveraging the shared test library
created in the preceding commit. It works by overriding the
`$git_for_each_ref` variable to "git refs list" and then sourcing the
shared library (`t/for-each-ref-tests.sh`).
This approach ensures that `git refs list` is tested against the
entire comprehensive test suite of `git for-each-ref`, verifying
that it acts as a compatible drop-in replacement.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 t/meson.build        | 1 +
 t/t1461-refs-list.sh | 8 ++++++++
 2 files changed, 9 insertions(+)
 create mode 100755 t/t1461-refs-list.sh
diff --git a/t/meson.build b/t/meson.build
index 50e89e764a..c959c039d0 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -224,6 +224,7 @@ integration_tests = [
   't1450-fsck.sh',
   't1451-fsck-buffer.sh',
   't1460-refs-migrate.sh',
+  't1461-refs-list.sh',
   't1500-rev-parse.sh',
   't1501-work-tree.sh',
   't1502-rev-parse-parseopt.sh',
diff --git a/t/t1461-refs-list.sh b/t/t1461-refs-list.sh
new file mode 100755
index 0000000000..36e3d81e59
--- /dev/null
+++ b/t/t1461-refs-list.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+test_description='git refs list tests'
+
+. ./test-lib.sh
+
+git_for_each_ref='git refs list'
+. "$TEST_DIRECTORY"/for-each-ref-tests.sh
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v2 2/2] t: add test for git refs list subcommand
  2025-07-23  5:17       ` Meet Soni
@ 2025-07-23 15:03         ` Junio C Hamano
  0 siblings, 0 replies; 65+ messages in thread
From: Junio C Hamano @ 2025-07-23 15:03 UTC (permalink / raw)
  To: Meet Soni
  Cc: git, ps, shejialuo, karthik.188, Taylor Blau, Jeff King,
	Kousik Sanagavarapu
Meet Soni <meetsoni3017@gmail.com> writes:
> Apologies for the delayed response, I was away last week.
No need to apologize.  Everybody knows that summer is a slow season.
Thanks for your contribution; enjoy polishing your topic.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v3 1/3] builtin/refs: add list subcommand
  2025-07-23  6:43     ` [GSoC][RFC PATCH v3 1/3] builtin/refs: add " Meet Soni
@ 2025-07-24  5:58       ` Patrick Steinhardt
  2025-07-24 16:01         ` Junio C Hamano
  2025-07-25 11:10         ` Meet Soni
  0 siblings, 2 replies; 65+ messages in thread
From: Patrick Steinhardt @ 2025-07-24  5:58 UTC (permalink / raw)
  To: Meet Soni
  Cc: git, shejialuo, karthik.188, gitster, sunshine, Taylor Blau,
	Christian Couder, Victoria Dye
On Wed, Jul 23, 2025 at 12:13:11PM +0530, Meet Soni wrote:
> diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc
> index 5ef89fc0fe..f7bbc1902a 100644
> --- a/Documentation/git-for-each-ref.adoc
> +++ b/Documentation/git-for-each-ref.adoc
Tiny nit, not worth a reroll by itself: it would have been nice to move
the extraction of the common options from our docs into a separate,
preparatory commit.
> diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
> index 3d2207ec77..d7d8279049 100644
> --- a/builtin/for-each-ref.c
> +++ b/builtin/for-each-ref.c
> @@ -16,11 +16,27 @@ static char const * const for_each_ref_usage[] = {
>  	NULL
>  };
>  
> +#define REFS_LIST_USAGE \
> +	N_("git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n" \
> +	   "              [(--sort=<key>)...] [--format=<format>]\n" \
> +	   "              [--include-root-refs] [ --stdin | <pattern>... ]\n" \
> +	   "              [--points-at=<object>]\n" \
> +	   "              [--merged[=<object>]] [--no-merged[=<object>]]\n" \
> +	   "              [--contains[=<object>]] [--no-contains[=<object>]]\n" \
> +	   "              [--exclude=<pattern> ...]")
> +
> +static char const * const refs_list_usage[] = {
> +	REFS_LIST_USAGE,
> +	NULL
> +};
Shouldn't the usage strings for git-for-each-ref(1) and git-refs-list(1)
be the same, except for the command name?
>  int cmd_for_each_ref(int argc,
>  		     const char **argv,
>  		     const char *prefix,
>  		     struct repository *repo)
>  {
> +	int cmd_is_refs_list = !strcmp(argv[0], "refs list");
> +	const char *const *opt_usage = cmd_is_refs_list ? refs_list_usage : for_each_ref_usage;
>  	struct ref_sorting *sorting;
>  	struct string_list sorting_options = STRING_LIST_INIT_DUP;
>  	int icase = 0, include_root_refs = 0, from_stdin = 0;
This follows the same pattern we have in "builtin/blame.c". It's not
exactly pretty that git-for-each-ref(1) is aware of git-refs(1) now, but
I think it's the pragmatic thing to do.
> diff --git a/builtin/refs.c b/builtin/refs.c
> index 998d2a2c1c..41e29d1b5f 100644
> --- a/builtin/refs.c
> +++ b/builtin/refs.c
> @@ -13,6 +14,15 @@
>  #define REFS_VERIFY_USAGE \
>  	N_("git refs verify [--strict] [--verbose]")
>  
> +#define REFS_LIST_USAGE \
> +	N_("git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n" \
> +	   "              [(--sort=<key>)...] [--format=<format>]\n" \
> +	   "              [--include-root-refs] [ --stdin | <pattern>... ]\n" \
> +	   "              [--points-at=<object>]\n" \
> +	   "              [--merged[=<object>]] [--no-merged[=<object>]]\n" \
> +	   "              [--contains[=<object>]] [--no-contains[=<object>]]\n" \
> +	   "              [--exclude=<pattern> ...]")
> +
>  static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
>  			    struct repository *repo UNUSED)
>  {
Hm, this one is a bit unfortunate though, as it feels like it's just a
matter of time before the two `REFS_LIST_USAGE` defines drift apart.
Might be worth it to move them to a shared place.
Alternatively, we could pull out the logic of `cmd_for_each_ref()` into
a separate function that also receives the usage array. Not sure whether
that is worth the hassle though.
Another alternative would be to just say `git refs list [<options>]`.
This here 
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v3 1/3] builtin/refs: add list subcommand
  2025-07-24  5:58       ` Patrick Steinhardt
@ 2025-07-24 16:01         ` Junio C Hamano
  2025-07-25 11:10         ` Meet Soni
  1 sibling, 0 replies; 65+ messages in thread
From: Junio C Hamano @ 2025-07-24 16:01 UTC (permalink / raw)
  To: Patrick Steinhardt
  Cc: Meet Soni, git, shejialuo, karthik.188, sunshine, Taylor Blau,
	Christian Couder, Victoria Dye
Patrick Steinhardt <ps@pks.im> writes:
> On Wed, Jul 23, 2025 at 12:13:11PM +0530, Meet Soni wrote:
>> diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc
>> index 5ef89fc0fe..f7bbc1902a 100644
>> --- a/Documentation/git-for-each-ref.adoc
>> +++ b/Documentation/git-for-each-ref.adoc
>
> Tiny nit, not worth a reroll by itself: it would have been nice to move
> the extraction of the common options from our docs into a separate,
> preparatory commit.
True.
>> diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
>> index 3d2207ec77..d7d8279049 100644
>> --- a/builtin/for-each-ref.c
>> +++ b/builtin/for-each-ref.c
>> @@ -16,11 +16,27 @@ static char const * const for_each_ref_usage[] = {
>>  	NULL
>>  };
>>  
>> +#define REFS_LIST_USAGE \
>> +	N_("git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n" \
>> +	   "              [(--sort=<key>)...] [--format=<format>]\n" \
>> +	   "              [--include-root-refs] [ --stdin | <pattern>... ]\n" \
>> +	   "              [--points-at=<object>]\n" \
>> +	   "              [--merged[=<object>]] [--no-merged[=<object>]]\n" \
>> +	   "              [--contains[=<object>]] [--no-contains[=<object>]]\n" \
>> +	   "              [--exclude=<pattern> ...]")
>> +
>> +static char const * const refs_list_usage[] = {
>> +	REFS_LIST_USAGE,
>> +	NULL
>> +};
>
> Shouldn't the usage strings for git-for-each-ref(1) and git-refs-list(1)
> be the same, except for the command name?
A great observation, but where would it lead us?  Something like
        #define COMMON_USAGE_FOR_EACH_REF \
                   " [--count=<count>] [--shell|--perl|--python|--tcl]\n" \
		   "              [(--sort=<key>)...] [--format=<format>]\n" \
                   ... \
                   "              [--exclude=<pattern> ...]"
        #define REFS_LIST_USAGE \
                N_("git refs list" COMMON_USAGE_FOR_EACH_REF);
        #define FOR_EACH_REF_USAGE \
                N_("git for-each-ref" COMMON_USAGE_FOR_EACH_REF);
is very much desirable because we do not want the options of these
two commands drift apart, but it does not quite work with the
current usage_with_options() infrastructure, as it leaves the
indentation of second and subsequent lines up to the caller, and the
display widths of "refs list" and "for-each-ref" are different.
>>  int cmd_for_each_ref(int argc,
>>  		     const char **argv,
>>  		     const char *prefix,
>>  		     struct repository *repo)
>>  {
>> +	int cmd_is_refs_list = !strcmp(argv[0], "refs list");
>> +	const char *const *opt_usage = cmd_is_refs_list ? refs_list_usage : for_each_ref_usage;
>>  	struct ref_sorting *sorting;
>>  	struct string_list sorting_options = STRING_LIST_INIT_DUP;
>>  	int icase = 0, include_root_refs = 0, from_stdin = 0;
>
> This follows the same pattern we have in "builtin/blame.c". It's not
> exactly pretty that git-for-each-ref(1) is aware of git-refs(1) now, but
> I think it's the pragmatic thing to do.
Although it is yucky.  argv[0] can never be "refs list" unless you
are calling this as if it were a subroutine by hand, which is in
general a no-no.
cf. https://lore.kernel.org/git/20171010040604.26029-1-gitster@pobox.com/
In this particular case, it is not like calling cmd_merge() from
within a loop in cmd_rebase(), so many reasons against such program
structure would not apply, but it would be prudent to leave an
in-code comment to warn others not to mimic this pattern in general
case.
>> diff --git a/builtin/refs.c b/builtin/refs.c
>> index 998d2a2c1c..41e29d1b5f 100644
>> --- a/builtin/refs.c
>> +++ b/builtin/refs.c
>> @@ -13,6 +14,15 @@
>>  #define REFS_VERIFY_USAGE \
>>  	N_("git refs verify [--strict] [--verbose]")
>>  
>> +#define REFS_LIST_USAGE \
>> +	N_("git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n" \
>> +	   "              [(--sort=<key>)...] [--format=<format>]\n" \
>> +	   "              [--include-root-refs] [ --stdin | <pattern>... ]\n" \
>> +	   "              [--points-at=<object>]\n" \
>> +	   "              [--merged[=<object>]] [--no-merged[=<object>]]\n" \
>> +	   "              [--contains[=<object>]] [--no-contains[=<object>]]\n" \
>> +	   "              [--exclude=<pattern> ...]")
>> +
>>  static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
>>  			    struct repository *repo UNUSED)
>>  {
>
> Hm, this one is a bit unfortunate though, as it feels like it's just a
> matter of time before the two `REFS_LIST_USAGE` defines drift apart.
> Might be worth it to move them to a shared place.
Yes, but see above.
> Alternatively, we could pull out the logic of `cmd_for_each_ref()` into
> a separate function that also receives the usage array. Not sure whether
> that is worth the hassle though.
I was also wondering about such restructuring of the existing
program.  Before adding a single line to support "refs list", can we
move much of what cmd_for_each_ref() does that is common between it
and upcoming "refs list" into a helper function, and make
cmd_for_each_ref() to call it, as a preparatory step?  Then a new
"refs list" implementation does not have to touch the implementation
of cmd_for_each_ref(), or call cmd_for_each_ref().  Which would give
us much cleaner implementation, and we do not have to leave an ugly
comment "we are doing this because we want to introduce 'refs list'
synonym that behaves identically and calling cmd_for_each_ref() is
the easiest way, but do not mimick it! It is an unacceptable pattern
you should not follow in more general cases".
Thanks.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v3 1/3] builtin/refs: add list subcommand
  2025-07-24  5:58       ` Patrick Steinhardt
  2025-07-24 16:01         ` Junio C Hamano
@ 2025-07-25 11:10         ` Meet Soni
  1 sibling, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-07-25 11:10 UTC (permalink / raw)
  To: Patrick Steinhardt
  Cc: git, shejialuo, karthik.188, gitster, sunshine, Taylor Blau,
	Christian Couder, Victoria Dye
On Thu, 24 Jul 2025 at 11:28, Patrick Steinhardt <ps@pks.im> wrote:
>
> On Wed, Jul 23, 2025 at 12:13:11PM +0530, Meet Soni wrote:
> > diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc
> > index 5ef89fc0fe..f7bbc1902a 100644
> > --- a/Documentation/git-for-each-ref.adoc
> > +++ b/Documentation/git-for-each-ref.adoc
>
> Tiny nit, not worth a reroll by itself: it would have been nice to move
> the extraction of the common options from our docs into a separate,
> preparatory commit.
>
I'll do it in the next version.
> > diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
> > index 3d2207ec77..d7d8279049 100644
> > --- a/builtin/for-each-ref.c
> > +++ b/builtin/for-each-ref.c
> > @@ -16,11 +16,27 @@ static char const * const for_each_ref_usage[] = {
> >       NULL
> >  };
> >
> > +#define REFS_LIST_USAGE \
> > +     N_("git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n" \
> > +        "              [(--sort=<key>)...] [--format=<format>]\n" \
> > +        "              [--include-root-refs] [ --stdin | <pattern>... ]\n" \
> > +        "              [--points-at=<object>]\n" \
> > +        "              [--merged[=<object>]] [--no-merged[=<object>]]\n" \
> > +        "              [--contains[=<object>]] [--no-contains[=<object>]]\n" \
> > +        "              [--exclude=<pattern> ...]")
> > +
> > +static char const * const refs_list_usage[] = {
> > +     REFS_LIST_USAGE,
> > +     NULL
> > +};
>
> Shouldn't the usage strings for git-for-each-ref(1) and git-refs-list(1)
> be the same, except for the command name?
>
I thought so too, but the man page of git-for-each-ref(1) and the code
implementation
differ. And the test didn't capture it as it's marked as a "known breakage",
will update this in the next version.
> >  int cmd_for_each_ref(int argc,
> >                    const char **argv,
> >                    const char *prefix,
> >                    struct repository *repo)
> >  {
> > +     int cmd_is_refs_list = !strcmp(argv[0], "refs list");
> > +     const char *const *opt_usage = cmd_is_refs_list ? refs_list_usage : for_each_ref_usage;
> >       struct ref_sorting *sorting;
> >       struct string_list sorting_options = STRING_LIST_INIT_DUP;
> >       int icase = 0, include_root_refs = 0, from_stdin = 0;
>
> This follows the same pattern we have in "builtin/blame.c". It's not
> exactly pretty that git-for-each-ref(1) is aware of git-refs(1) now, but
> I think it's the pragmatic thing to do.
>
> > diff --git a/builtin/refs.c b/builtin/refs.c
> > index 998d2a2c1c..41e29d1b5f 100644
> > --- a/builtin/refs.c
> > +++ b/builtin/refs.c
> > @@ -13,6 +14,15 @@
> >  #define REFS_VERIFY_USAGE \
> >       N_("git refs verify [--strict] [--verbose]")
> >
> > +#define REFS_LIST_USAGE \
> > +     N_("git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n" \
> > +        "              [(--sort=<key>)...] [--format=<format>]\n" \
> > +        "              [--include-root-refs] [ --stdin | <pattern>... ]\n" \
> > +        "              [--points-at=<object>]\n" \
> > +        "              [--merged[=<object>]] [--no-merged[=<object>]]\n" \
> > +        "              [--contains[=<object>]] [--no-contains[=<object>]]\n" \
> > +        "              [--exclude=<pattern> ...]")
> > +
> >  static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
> >                           struct repository *repo UNUSED)
> >  {
>
> Hm, this one is a bit unfortunate though, as it feels like it's just a
> matter of time before the two `REFS_LIST_USAGE` defines drift apart.
> Might be worth it to move them to a shared place.
>
> Alternatively, we could pull out the logic of `cmd_for_each_ref()` into
> a separate function that also receives the usage array. Not sure whether
> that is worth the hassle though.
>
> Another alternative would be to just say `git refs list [<options>]`.
> This here
True.
The first two suggestions seem the right way forward. The third one,
just using a generic
git refs list [<options>] seems counter-productive, as a key goal here
is to improve
discoverability, and hiding the full list of options would work against that.
The solution of pulling the core logic into a helper function seems
like the most robust
approach, and since Junio's feedback also points in that direction,
I'll proceed with that
refactoring.
Thanks.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v4 0/5] Add refs list subcommand
  2025-07-23  6:43   ` [GSoC][RFC PATCH v3 0/3] Add " Meet Soni
                       ` (2 preceding siblings ...)
  2025-07-23  6:43     ` [GSoC][RFC PATCH v3 3/3] t: add test for git refs list subcommand Meet Soni
@ 2025-07-31  9:00     ` Meet Soni
  2025-07-31  9:00       ` [GSoC][RFC PATCH v4 1/5] doc: factor out common option Meet Soni
                         ` (6 more replies)
  3 siblings, 7 replies; 65+ messages in thread
From: Meet Soni @ 2025-07-31  9:00 UTC (permalink / raw)
  To: git; +Cc: ps, shejialuo, karthik.188, gitster, sunshine, Meet Soni
Hello everyone,
This is the fourth version of the patch series that introduces the git
refs list subcommand.
Changes in v4:
  - Implemented architectural refactoring. The core logic of
    for-each-ref now resides in a shared helper function, and both
    for-each-ref and refs list have been simplified to thin wrappers
    around it.
  - The usage strings have also been refactored. The common options
    are now defined in a shared macro in a new for-each-ref.h header.
  - The patch series has been further split to cleanly separate the
    preparatory refactoring commits (for both the C code and the
    AsciiDoc documentation) from the commit that introduces the new
    feature.
  - As a beneficial side-effect of unifying the usage strings, this
    series now fixes a pre-existing inconsistency between the --help
    output and the man page for for-each-ref. This allows a known
    breakage for it in the t0450 documentation test to be removed.
---
(v1 cover-letter text)
This patch series introduces `git refs list` as a modern replacement for
`git for-each-ref`, as part of an effort to consolidate ref-related
functionality under a unified `git refs` command.
Git's ref-related operations are currently handled by several distinct
commands, such as `git show-ref`, `git for-each-ref`, `git update-ref`,
`git pack-refs`, etc. This distribution has a few practical drawbacks:
- Users need to rely on multiple commands for related tasks involving
  refs.
- The commands may differ slightly in behavior and option syntax,
  leading to inconsistency.
We propose a long-term consolidation effort to bring ref-related
subcommands under the umbrella of a single command: `git refs`.
The implementation of `git refs list` is functionally identical to `git
for-each-ref`. It reuses the same internal logic (cmd_for_each_ref) to
ensure complete backward compatibility. The purpose of this patch is not
to introduce new behavior but to provide an alternate entry point under
the consolidated `git refs` namespace.
The motivation behind this change is twofold:
- Consolidation: Centralizing ref-related operations makes them easier
  to discover, use, and maintain.
- Evolution: While the initial goal is parity with existing commands,
  this consolidation allows for careful reconsideration of which
  features are essential. Over time, we can:
  - Remove legacy or obscure options that are no longer needed.
  - Add improvements that wouldn't make sense to bolt onto legacy
    commands.
  - Offering a more consistent and user-friendly surface.
To verify backward compatibility, this patch also includes a test
`t/t1461-refs-list.sh`, which runs the full `t6300-for-each-ref.sh` test
using `git refs list`. The test uses ${GIT_REFS_LIST_CMD:-for-each-ref}
to allow substitution without duplicating tests.
This patch is deliberately conservative: it introduces no behavioral
changes and leaves `for-each-ref` untouched. The goal is to lay
groundwork and demonstrate viability of ref consolidation within `git
refs`.
Going forward, I'd like to initiate a discussion on what the ideal
surface of `git refs list` should look like. Which options and features
from `for-each-ref` should be carried over? Are there any that are
obsolete or overly niche? What improvements might be worth considering
now that we have a new, consolidated interface?
Feedback on this, especially from those who rely on `for-each-ref` in
scripts or tooling would be very helpful.
Meet Soni (5):
  doc: factor out common option
  builtin/for-each-ref: factor out core logic into a helper
  builtin/refs: add list subcommand
  t6300: refactor tests to be shareable
  t: add test for git refs list subcommand
 Documentation/for-each-ref-options.adoc |   79 +
 Documentation/git-for-each-ref.adoc     |   80 +-
 Documentation/git-refs.adoc             |   16 +
 builtin/for-each-ref.c                  |   35 +-
 builtin/refs.c                          |   14 +
 for-each-ref.h                          |   24 +
 t/for-each-ref-tests.sh                 | 2141 +++++++++++++++++++++++
 t/meson.build                           |    1 +
 t/t0450/adoc-help-mismatches            |    1 -
 t/t1461-refs-list.sh                    |    8 +
 t/t6300-for-each-ref.sh                 | 2140 +---------------------
 11 files changed, 2305 insertions(+), 2234 deletions(-)
 create mode 100644 Documentation/for-each-ref-options.adoc
 create mode 100644 for-each-ref.h
 create mode 100644 t/for-each-ref-tests.sh
 create mode 100755 t/t1461-refs-list.sh
Range-diff against v3:
1:  547b0bbf8f < -:  ---------- builtin/refs: add list subcommand
-:  ---------- > 1:  d2fa47a2b9 doc: factor out common option
-:  ---------- > 2:  5084db4d14 builtin/for-each-ref: factor out core logic into a helper
-:  ---------- > 3:  1a15cd454b builtin/refs: add list subcommand
2:  5d5057ff98 = 4:  29c41d682b t6300: refactor tests to be shareable
3:  b9cb9cdf48 = 5:  0fd1f714c9 t: add test for git refs list subcommand
-- 
2.34.1
^ permalink raw reply	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v4 1/5] doc: factor out common option
  2025-07-31  9:00     ` [GSoC][RFC PATCH v4 0/5] Add " Meet Soni
@ 2025-07-31  9:00       ` Meet Soni
  2025-07-31  9:00       ` [GSoC][RFC PATCH v4 2/5] builtin/for-each-ref: factor out core logic into a helper Meet Soni
                         ` (5 subsequent siblings)
  6 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-07-31  9:00 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, Meet Soni,
	Aaron Lipman, Øystein Walle, Jeff King, Victoria Dye,
	Derrick Stolee, Taylor Blau
In preparation for adding documentation for `git refs list`, factor out
the common options from the `git-for-each-ref` man page into a
shareable file `for-each-ref-options.adoc` and update
`git-for-each-ref.adoc` to use an `include::` macro.
This change is a pure refactoring and results in no change to the
final rendered documentation for `for-each-ref`.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 Documentation/for-each-ref-options.adoc | 79 ++++++++++++++++++++++++
 Documentation/git-for-each-ref.adoc     | 80 +------------------------
 2 files changed, 80 insertions(+), 79 deletions(-)
 create mode 100644 Documentation/for-each-ref-options.adoc
diff --git a/Documentation/for-each-ref-options.adoc b/Documentation/for-each-ref-options.adoc
new file mode 100644
index 0000000000..5f3a85bf64
--- /dev/null
+++ b/Documentation/for-each-ref-options.adoc
@@ -0,0 +1,79 @@
+<pattern>...::
+	If one or more patterns are given, only refs are shown that
+	match against at least one pattern, either using fnmatch(3) or
+	literally, in the latter case matching completely or from the
+	beginning up to a slash.
+
+--stdin::
+	If `--stdin` is supplied, then the list of patterns is read from
+	standard input instead of from the argument list.
+
+--count=<count>::
+	By default the command shows all refs that match
+	`<pattern>`.  This option makes it stop after showing
+	that many refs.
+
+--sort=<key>::
+	A field name to sort on.  Prefix `-` to sort in
+	descending order of the value.  When unspecified,
+	`refname` is used.  You may use the --sort=<key> option
+	multiple times, in which case the last key becomes the primary
+	key.
+
+--format=<format>::
+	A string that interpolates `%(fieldname)` from a ref being shown and
+	the object it points at. In addition, the string literal `%%`
+	renders as `%` and `%xx` - where `xx` are hex digits - renders as
+	the character with hex code `xx`. For example, `%00` interpolates to
+	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
++
+When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
+TAB %(refname)`.
+
+--color[=<when>]::
+	Respect any colors specified in the `--format` option. The
+	`<when>` field must be one of `always`, `never`, or `auto` (if
+	`<when>` is absent, behave as if `always` was given).
+
+--shell::
+--perl::
+--python::
+--tcl::
+	If given, strings that substitute `%(fieldname)`
+	placeholders are quoted as string literals suitable for
+	the specified host language.  This is meant to produce
+	a scriptlet that can directly be `eval`ed.
+
+--points-at=<object>::
+	Only list refs which points at the given object.
+
+--merged[=<object>]::
+	Only list refs whose tips are reachable from the
+	specified commit (HEAD if not specified).
+
+--no-merged[=<object>]::
+	Only list refs whose tips are not reachable from the
+	specified commit (HEAD if not specified).
+
+--contains[=<object>]::
+	Only list refs which contain the specified commit (HEAD if not
+	specified).
+
+--no-contains[=<object>]::
+	Only list refs which don't contain the specified commit (HEAD
+	if not specified).
+
+--ignore-case::
+	Sorting and filtering refs are case insensitive.
+
+--omit-empty::
+	Do not print a newline after formatted refs where the format expands
+	to the empty string.
+
+--exclude=<pattern>::
+	If one or more patterns are given, only refs which do not match
+	any excluded pattern(s) are shown. Matching is done using the
+	same rules as `<pattern>` above.
+
+--include-root-refs::
+	List root refs (HEAD and pseudorefs) apart from regular refs.
diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc
index 5ef89fc0fe..c3cf1752e3 100644
--- a/Documentation/git-for-each-ref.adoc
+++ b/Documentation/git-for-each-ref.adoc
@@ -28,85 +28,7 @@ host language allowing their direct evaluation in that language.
 
 OPTIONS
 -------
-<pattern>...::
-	If one or more patterns are given, only refs are shown that
-	match against at least one pattern, either using fnmatch(3) or
-	literally, in the latter case matching completely or from the
-	beginning up to a slash.
-
---stdin::
-	If `--stdin` is supplied, then the list of patterns is read from
-	standard input instead of from the argument list.
-
---count=<count>::
-	By default the command shows all refs that match
-	`<pattern>`.  This option makes it stop after showing
-	that many refs.
-
---sort=<key>::
-	A field name to sort on.  Prefix `-` to sort in
-	descending order of the value.  When unspecified,
-	`refname` is used.  You may use the --sort=<key> option
-	multiple times, in which case the last key becomes the primary
-	key.
-
---format=<format>::
-	A string that interpolates `%(fieldname)` from a ref being shown and
-	the object it points at. In addition, the string literal `%%`
-	renders as `%` and `%xx` - where `xx` are hex digits - renders as
-	the character with hex code `xx`. For example, `%00` interpolates to
-	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
-+
-When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
-TAB %(refname)`.
-
---color[=<when>]::
-	Respect any colors specified in the `--format` option. The
-	`<when>` field must be one of `always`, `never`, or `auto` (if
-	`<when>` is absent, behave as if `always` was given).
-
---shell::
---perl::
---python::
---tcl::
-	If given, strings that substitute `%(fieldname)`
-	placeholders are quoted as string literals suitable for
-	the specified host language.  This is meant to produce
-	a scriptlet that can directly be `eval`ed.
-
---points-at=<object>::
-	Only list refs which points at the given object.
-
---merged[=<object>]::
-	Only list refs whose tips are reachable from the
-	specified commit (HEAD if not specified).
-
---no-merged[=<object>]::
-	Only list refs whose tips are not reachable from the
-	specified commit (HEAD if not specified).
-
---contains[=<object>]::
-	Only list refs which contain the specified commit (HEAD if not
-	specified).
-
---no-contains[=<object>]::
-	Only list refs which don't contain the specified commit (HEAD
-	if not specified).
-
---ignore-case::
-	Sorting and filtering refs are case insensitive.
-
---omit-empty::
-	Do not print a newline after formatted refs where the format expands
-	to the empty string.
-
---exclude=<pattern>::
-	If one or more patterns are given, only refs which do not match
-	any excluded pattern(s) are shown. Matching is done using the
-	same rules as `<pattern>` above.
-
---include-root-refs::
-	List root refs (HEAD and pseudorefs) apart from regular refs.
+include::for-each-ref-options.adoc[]
 
 FIELD NAMES
 -----------
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v4 2/5] builtin/for-each-ref: factor out core logic into a helper
  2025-07-31  9:00     ` [GSoC][RFC PATCH v4 0/5] Add " Meet Soni
  2025-07-31  9:00       ` [GSoC][RFC PATCH v4 1/5] doc: factor out common option Meet Soni
@ 2025-07-31  9:00       ` Meet Soni
  2025-08-01  5:54         ` Patrick Steinhardt
  2025-07-31  9:00       ` [GSoC][RFC PATCH v4 3/5] builtin/refs: add list subcommand Meet Soni
                         ` (4 subsequent siblings)
  6 siblings, 1 reply; 65+ messages in thread
From: Meet Soni @ 2025-07-31  9:00 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, Meet Soni,
	Ævar Arnfjörð Bjarmason, Victoria Dye
The implementation of `git for-each-ref` is monolithic within
`cmd_for_each_ref()`, making it impossible to share its logic with other
commands. To enable code reuse for the upcoming `git refs list`
subcommand, refactor the core logic into a shared helper function.
Introduce a new `for-each-ref.h` header to define the public interface
for this shared logic. It contains the declaration for a new helper
function, `for_each_ref_core()`, and a macro for the common usage
options.
Move the option parsing, filtering, and formatting logic from
`cmd_for_each_ref()` into a new helper function named
`for_each_ref_core()`. This helper is made generic by accepting the
command's usage string as a parameter.
The original `cmd_for_each_ref()` is simplified to a thin wrapper that
is only responsible for defining its specific usage array and calling
the shared helper.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 builtin/for-each-ref.c       | 35 +++++++++++++++++++----------------
 for-each-ref.h               | 24 ++++++++++++++++++++++++
 t/t0450/adoc-help-mismatches |  1 -
 3 files changed, 43 insertions(+), 17 deletions(-)
 create mode 100644 for-each-ref.h
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 3d2207ec77..bbc0e5ad1c 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -7,19 +7,9 @@
 #include "ref-filter.h"
 #include "strbuf.h"
 #include "strvec.h"
+#include "for-each-ref.h"
 
-static char const * const for_each_ref_usage[] = {
-	N_("git for-each-ref [<options>] [<pattern>]"),
-	N_("git for-each-ref [--points-at <object>]"),
-	N_("git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"),
-	N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"),
-	NULL
-};
-
-int cmd_for_each_ref(int argc,
-		     const char **argv,
-		     const char *prefix,
-		     struct repository *repo)
+int for_each_ref_core(int argc, const char **argv, const char *prefix, struct repository *repo, const char *const *usage)
 {
 	struct ref_sorting *sorting;
 	struct string_list sorting_options = STRING_LIST_INIT_DUP;
@@ -67,17 +57,17 @@ int cmd_for_each_ref(int argc,
 	/* Set default (refname) sorting */
 	string_list_append(&sorting_options, "refname");
 
-	parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
+	parse_options(argc, argv, prefix, opts, usage, 0);
 	if (format.array_opts.max_count < 0) {
 		error("invalid --count argument: `%d'", format.array_opts.max_count);
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(usage, opts);
 	}
 	if (HAS_MULTI_BITS(format.quote_style)) {
 		error("more than one quoting style?");
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(usage, opts);
 	}
 	if (verify_ref_format(&format))
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(usage, opts);
 
 	sorting = ref_sorting_options(&sorting_options);
 	ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
@@ -111,3 +101,16 @@ int cmd_for_each_ref(int argc,
 	strvec_clear(&vec);
 	return 0;
 }
+
+int cmd_for_each_ref(int argc,
+		     const char **argv,
+		     const char *prefix,
+		     struct repository *repo)
+{
+	static char const * const for_each_ref_usage[] = {
+		N_("git for-each-ref " COMMON_USAGE_FOR_EACH_REF),
+		NULL
+	};
+
+	return for_each_ref_core(argc, argv, prefix, repo, for_each_ref_usage);
+}
diff --git a/for-each-ref.h b/for-each-ref.h
new file mode 100644
index 0000000000..06d5b123b3
--- /dev/null
+++ b/for-each-ref.h
@@ -0,0 +1,24 @@
+#ifndef FOR_EACH_REF_H
+#define FOR_EACH_REF_H
+
+/*
+ * Shared usage string for options common to git-for-each-ref(1)
+ * and git-refs-list(1). The command-specific part (e.g., "git refs list ")
+ * must be prepended by the caller.
+ */
+#define COMMON_USAGE_FOR_EACH_REF \
+	"[--count=<count>] [--shell|--perl|--python|--tcl]\n" \
+	"                         [(--sort=<key>)...] [--format=<format>]\n" \
+	"                         [--include-root-refs] [ --stdin | <pattern>... ]\n" \
+	"                         [--points-at=<object>]\n" \
+	"                         [--merged[=<object>]] [--no-merged[=<object>]]\n" \
+	"                         [--contains[=<object>]] [--no-contains[=<object>]]\n" \
+	"                         [--exclude=<pattern> ...]"
+
+/*
+ * The core logic for for-each-ref and its clones.
+ */
+int for_each_ref_core(int argc, const char **argv, const char *prefix,
+		      struct repository *repo, const char *const *usage);
+
+#endif /* FOR_EACH_REF_H */
diff --git a/t/t0450/adoc-help-mismatches b/t/t0450/adoc-help-mismatches
index 06b469bdee..2c6ecd5fc8 100644
--- a/t/t0450/adoc-help-mismatches
+++ b/t/t0450/adoc-help-mismatches
@@ -17,7 +17,6 @@ fast-export
 fast-import
 fetch-pack
 fmt-merge-msg
-for-each-ref
 format-patch
 fsck-objects
 fsmonitor--daemon
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v4 3/5] builtin/refs: add list subcommand
  2025-07-31  9:00     ` [GSoC][RFC PATCH v4 0/5] Add " Meet Soni
  2025-07-31  9:00       ` [GSoC][RFC PATCH v4 1/5] doc: factor out common option Meet Soni
  2025-07-31  9:00       ` [GSoC][RFC PATCH v4 2/5] builtin/for-each-ref: factor out core logic into a helper Meet Soni
@ 2025-07-31  9:00       ` Meet Soni
  2025-08-01 13:27         ` Phillip Wood
  2025-07-31  9:00       ` [GSoC][RFC PATCH v4 4/5] t6300: refactor tests to be shareable Meet Soni
                         ` (3 subsequent siblings)
  6 siblings, 1 reply; 65+ messages in thread
From: Meet Soni @ 2025-07-31  9:00 UTC (permalink / raw)
  To: git; +Cc: ps, shejialuo, karthik.188, gitster, sunshine, Meet Soni,
	John Cai
Git's reference management is distributed across multiple commands. As
part of an ongoing effort to consolidate and modernize reference
handling, introduce a `list` subcommand under the `git refs` umbrella as
a replacement for `git for-each-ref`.
Implement `cmd_refs_list` by having it call the `for_each_ref_core()`
helper function. This helper was factored out of the original
`cmd_for_each_ref` in a preceding commit, allowing both commands to
share the same core logic as independent peers.
Add documentation for the new command. The man page leverages the shared
options file, created in a previous commit, by using the AsciiDoc
`include::` macro to ensure consistency with git-for-each-ref(1).
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 Documentation/git-refs.adoc | 16 ++++++++++++++++
 builtin/refs.c              | 14 ++++++++++++++
 2 files changed, 30 insertions(+)
diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
index 4d6dc994f9..ee563aa7e0 100644
--- a/Documentation/git-refs.adoc
+++ b/Documentation/git-refs.adoc
@@ -11,6 +11,13 @@ SYNOPSIS
 [synopsis]
 git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
 git refs verify [--strict] [--verbose]
+git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
+	      [(--sort=<key>)...] [--format=<format>]
+	      [--include-root-refs] [ --stdin | <pattern>... ]
+	      [--points-at=<object>]
+	      [--merged[=<object>]] [--no-merged[=<object>]]
+	      [--contains[=<object>]] [--no-contains[=<object>]]
+	      [--exclude=<pattern> ...]
 
 DESCRIPTION
 -----------
@@ -26,6 +33,11 @@ migrate::
 verify::
 	Verify reference database consistency.
 
+list::
+	List references in the repository with support for filtering,
+	formatting, and sorting. This subcommand is an alias for
+	linkgit:git-for-each-ref[1] and offers identical functionality.
+
 OPTIONS
 -------
 
@@ -57,6 +69,10 @@ The following options are specific to 'git refs verify':
 --verbose::
 	When verifying the reference database consistency, be chatty.
 
+The following options are specific to 'git refs list':
+
+include::for-each-ref-options.adoc[]
+
 KNOWN LIMITATIONS
 -----------------
 
diff --git a/builtin/refs.c b/builtin/refs.c
index 998d2a2c1c..848a7c9072 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -6,6 +6,7 @@
 #include "refs.h"
 #include "strbuf.h"
 #include "worktree.h"
+#include "for-each-ref.h"
 
 #define REFS_MIGRATE_USAGE \
 	N_("git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]")
@@ -101,6 +102,17 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
 	return ret;
 }
 
+static int cmd_refs_list(int argc, const char **argv, const char *prefix,
+			   struct repository *repo)
+{
+	static char const * const refs_list_usage[] = {
+		N_("git refs list " COMMON_USAGE_FOR_EACH_REF),
+		NULL
+	};
+
+	return for_each_ref_core(argc, argv, prefix, repo, refs_list_usage);
+}
+
 int cmd_refs(int argc,
 	     const char **argv,
 	     const char *prefix,
@@ -109,12 +121,14 @@ int cmd_refs(int argc,
 	const char * const refs_usage[] = {
 		REFS_MIGRATE_USAGE,
 		REFS_VERIFY_USAGE,
+		"git refs list " COMMON_USAGE_FOR_EACH_REF,
 		NULL,
 	};
 	parse_opt_subcommand_fn *fn = NULL;
 	struct option opts[] = {
 		OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate),
 		OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify),
+		OPT_SUBCOMMAND("list", &fn, cmd_refs_list),
 		OPT_END(),
 	};
 
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v4 4/5] t6300: refactor tests to be shareable
  2025-07-31  9:00     ` [GSoC][RFC PATCH v4 0/5] Add " Meet Soni
                         ` (2 preceding siblings ...)
  2025-07-31  9:00       ` [GSoC][RFC PATCH v4 3/5] builtin/refs: add list subcommand Meet Soni
@ 2025-07-31  9:00       ` Meet Soni
  2025-07-31  9:00       ` [GSoC][RFC PATCH v4 5/5] t: add test for git refs list subcommand Meet Soni
                         ` (2 subsequent siblings)
  6 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-07-31  9:00 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, Meet Soni,
	Kousik Sanagavarapu, Jeff King, Taylor Blau, Hariom Verma
In preparation for adding tests for the new `git refs list` command,
refactor the existing t6300 test suite to make its logic shareable.
Move the core test logic from `t6300-for-each-ref.sh` into a new
`for-each-ref-tests.sh` file. Inside this new script, replace hardcoded
calls to "git for-each-ref" with the `$git_for_each_ref` variable.
The original `t6300-for-each-ref.sh` script now becomes a simple
"driver". It is responsible for setting the default value of the
variable and then sourcing the test library.
This new structure follows the established pattern used for sharing
tests between `git-blame` and `git-annotate` and prepares the test suite
for the `refs list` tests to be added in a subsequent commit.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 t/for-each-ref-tests.sh | 2141 +++++++++++++++++++++++++++++++++++++++
 t/t6300-for-each-ref.sh | 2140 +-------------------------------------
 2 files changed, 2143 insertions(+), 2138 deletions(-)
 create mode 100644 t/for-each-ref-tests.sh
diff --git a/t/for-each-ref-tests.sh b/t/for-each-ref-tests.sh
new file mode 100644
index 0000000000..e3ad19298a
--- /dev/null
+++ b/t/for-each-ref-tests.sh
@@ -0,0 +1,2141 @@
+git_for_each_ref=${git_for_each_ref:-git for-each-ref}
+GNUPGHOME_NOT_USED=$GNUPGHOME
+. "$TEST_DIRECTORY"/lib-gpg.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
+
+# Mon Jul 3 23:18:43 2006 +0000
+datestamp=1151968723
+setdate_and_increment () {
+    GIT_COMMITTER_DATE="$datestamp +0200"
+    datestamp=$(expr "$datestamp" + 1)
+    GIT_AUTHOR_DATE="$datestamp +0200"
+    datestamp=$(expr "$datestamp" + 1)
+    export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+}
+
+test_object_file_size () {
+	oid=$(git rev-parse "$1")
+	path=".git/objects/$(test_oid_to_path $oid)"
+	test_file_size "$path"
+}
+
+test_expect_success setup '
+	# setup .mailmap
+	cat >.mailmap <<-EOF &&
+	A Thor <athor@example.com> A U Thor <author@example.com>
+	C Mitter <cmitter@example.com> C O Mitter <committer@example.com>
+	EOF
+
+	setdate_and_increment &&
+	echo "Using $datestamp" > one &&
+	git add one &&
+	git commit -m "Initial" &&
+	git branch -M main &&
+	setdate_and_increment &&
+	git tag -a -m "Tagging at $datestamp" testtag &&
+	git update-ref refs/remotes/origin/main main &&
+	git remote add origin nowhere &&
+	git config branch.main.remote origin &&
+	git config branch.main.merge refs/heads/main &&
+	git remote add myfork elsewhere &&
+	git config remote.pushdefault myfork &&
+	git config push.default current
+'
+
+test_atom () {
+	case "$1" in
+		head) ref=refs/heads/main ;;
+		 tag) ref=refs/tags/testtag ;;
+		 sym) ref=refs/heads/sym ;;
+		   *) ref=$1 ;;
+	esac
+	format=$2
+	test_do=test_expect_${4:-success}
+
+	printf '%s\n' "$3" >expected
+	$test_do $PREREQ "basic atom: $ref $format" '
+		${git_for_each_ref} --format="%($format)" "$ref" >actual &&
+		sanitize_pgp <actual >actual.clean &&
+		test_cmp expected actual.clean
+	'
+
+	# Automatically test "contents:size" atom after testing "contents"
+	if test "$format" = "contents"
+	then
+		# for commit leg, $3 is changed there
+		expect=$(printf '%s' "$3" | wc -c)
+		$test_do $PREREQ "basic atom: $ref contents:size" '
+			type=$(git cat-file -t "$ref") &&
+			case $type in
+			tag)
+				# We cannot use $3 as it expects sanitize_pgp to run
+				git cat-file tag $ref >out &&
+				expect=$(tail -n +6 out | wc -c) &&
+				rm -f out ;;
+			tree | blob)
+				expect="" ;;
+			commit)
+				: "use the calculated expect" ;;
+			*)
+				BUG "unknown object type" ;;
+			esac &&
+			# Leave $expect unquoted to lose possible leading whitespaces
+			echo $expect >expected &&
+			${git_for_each_ref} --format="%(contents:size)" "$ref" >actual &&
+			test_cmp expected actual
+		'
+	fi
+}
+
+hexlen=$(test_oid hexsz)
+
+test_atom head refname refs/heads/main
+test_atom head refname: refs/heads/main
+test_atom head refname:short main
+test_atom head refname:lstrip=1 heads/main
+test_atom head refname:lstrip=2 main
+test_atom head refname:lstrip=-1 main
+test_atom head refname:lstrip=-2 heads/main
+test_atom head refname:rstrip=1 refs/heads
+test_atom head refname:rstrip=2 refs
+test_atom head refname:rstrip=-1 refs
+test_atom head refname:rstrip=-2 refs/heads
+test_atom head refname:strip=1 heads/main
+test_atom head refname:strip=2 main
+test_atom head refname:strip=-1 main
+test_atom head refname:strip=-2 heads/main
+test_atom head upstream refs/remotes/origin/main
+test_atom head upstream:short origin/main
+test_atom head upstream:lstrip=2 origin/main
+test_atom head upstream:lstrip=-2 origin/main
+test_atom head upstream:rstrip=2 refs/remotes
+test_atom head upstream:rstrip=-2 refs/remotes
+test_atom head upstream:strip=2 origin/main
+test_atom head upstream:strip=-2 origin/main
+test_atom head push refs/remotes/myfork/main
+test_atom head push:short myfork/main
+test_atom head push:lstrip=1 remotes/myfork/main
+test_atom head push:lstrip=-1 main
+test_atom head push:rstrip=1 refs/remotes/myfork
+test_atom head push:rstrip=-1 refs
+test_atom head push:strip=1 remotes/myfork/main
+test_atom head push:strip=-1 main
+test_atom head objecttype commit
+test_atom head objectsize $((131 + hexlen))
+test_atom head objectsize:disk $(test_object_file_size refs/heads/main)
+test_atom head deltabase $ZERO_OID
+test_atom head objectname $(git rev-parse refs/heads/main)
+test_atom head objectname:short $(git rev-parse --short refs/heads/main)
+test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
+test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
+test_atom head tree $(git rev-parse refs/heads/main^{tree})
+test_atom head tree:short $(git rev-parse --short refs/heads/main^{tree})
+test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/main^{tree})
+test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/main^{tree})
+test_atom head parent ''
+test_atom head parent:short ''
+test_atom head parent:short=1 ''
+test_atom head parent:short=10 ''
+test_atom head numparent 0
+test_atom head object ''
+test_atom head type ''
+test_atom head raw "$(git cat-file commit refs/heads/main)
+"
+test_atom head '*objectname' ''
+test_atom head '*objecttype' ''
+test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
+test_atom head authorname 'A U Thor'
+test_atom head authorname:mailmap 'A Thor'
+test_atom head authoremail '<author@example.com>'
+test_atom head authoremail:trim 'author@example.com'
+test_atom head authoremail:localpart 'author'
+test_atom head authoremail:trim,localpart 'author'
+test_atom head authoremail:mailmap '<athor@example.com>'
+test_atom head authoremail:mailmap,trim 'athor@example.com'
+test_atom head authoremail:trim,mailmap 'athor@example.com'
+test_atom head authoremail:mailmap,localpart 'athor'
+test_atom head authoremail:localpart,mailmap 'athor'
+test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor'
+test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
+test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
+test_atom head committername 'C O Mitter'
+test_atom head committername:mailmap 'C Mitter'
+test_atom head committeremail '<committer@example.com>'
+test_atom head committeremail:trim 'committer@example.com'
+test_atom head committeremail:localpart 'committer'
+test_atom head committeremail:localpart,trim 'committer'
+test_atom head committeremail:mailmap '<cmitter@example.com>'
+test_atom head committeremail:mailmap,trim 'cmitter@example.com'
+test_atom head committeremail:trim,mailmap 'cmitter@example.com'
+test_atom head committeremail:mailmap,localpart 'cmitter'
+test_atom head committeremail:localpart,mailmap 'cmitter'
+test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter'
+test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
+test_atom head tag ''
+test_atom head tagger ''
+test_atom head taggername ''
+test_atom head taggeremail ''
+test_atom head taggeremail:trim ''
+test_atom head taggeremail:localpart ''
+test_atom head taggerdate ''
+test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
+test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
+test_atom head subject 'Initial'
+test_atom head subject:sanitize 'Initial'
+test_atom head contents:subject 'Initial'
+test_atom head body ''
+test_atom head contents:body ''
+test_atom head contents:signature ''
+test_atom head contents 'Initial
+'
+test_atom head HEAD '*'
+
+test_atom tag refname refs/tags/testtag
+test_atom tag refname:short testtag
+test_atom tag upstream ''
+test_atom tag push ''
+test_atom tag objecttype tag
+test_atom tag objectsize $((114 + hexlen))
+test_atom tag objectsize:disk $(test_object_file_size refs/tags/testtag)
+test_atom tag '*objectsize:disk' $(test_object_file_size refs/heads/main)
+test_atom tag deltabase $ZERO_OID
+test_atom tag '*deltabase' $ZERO_OID
+test_atom tag objectname $(git rev-parse refs/tags/testtag)
+test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
+test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
+test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
+test_atom tag tree ''
+test_atom tag tree:short ''
+test_atom tag tree:short=1 ''
+test_atom tag tree:short=10 ''
+test_atom tag parent ''
+test_atom tag parent:short ''
+test_atom tag parent:short=1 ''
+test_atom tag parent:short=10 ''
+test_atom tag numparent ''
+test_atom tag object $(git rev-parse refs/tags/testtag^0)
+test_atom tag type 'commit'
+test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{})
+test_atom tag '*objecttype' 'commit'
+test_atom tag author ''
+test_atom tag authorname ''
+test_atom tag authorname:mailmap ''
+test_atom tag authoremail ''
+test_atom tag authoremail:trim ''
+test_atom tag authoremail:localpart ''
+test_atom tag authoremail:trim,localpart ''
+test_atom tag authoremail:mailmap ''
+test_atom tag authoremail:mailmap,trim ''
+test_atom tag authoremail:trim,mailmap ''
+test_atom tag authoremail:mailmap,localpart ''
+test_atom tag authoremail:localpart,mailmap ''
+test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim ''
+test_atom tag authordate ''
+test_atom tag committer ''
+test_atom tag committername ''
+test_atom tag committername:mailmap ''
+test_atom tag committeremail ''
+test_atom tag committeremail:trim ''
+test_atom tag committeremail:localpart ''
+test_atom tag committeremail:localpart,trim ''
+test_atom tag committeremail:mailmap ''
+test_atom tag committeremail:mailmap,trim ''
+test_atom tag committeremail:trim,mailmap ''
+test_atom tag committeremail:mailmap,localpart ''
+test_atom tag committeremail:localpart,mailmap ''
+test_atom tag committeremail:trim,mailmap,trim,trim,localpart ''
+test_atom tag committerdate ''
+test_atom tag tag 'testtag'
+test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
+test_atom tag taggername 'C O Mitter'
+test_atom tag taggername:mailmap 'C Mitter'
+test_atom tag taggeremail '<committer@example.com>'
+test_atom tag taggeremail:trim 'committer@example.com'
+test_atom tag taggeremail:localpart 'committer'
+test_atom tag taggeremail:trim,localpart 'committer'
+test_atom tag taggeremail:mailmap '<cmitter@example.com>'
+test_atom tag taggeremail:mailmap,trim 'cmitter@example.com'
+test_atom tag taggeremail:trim,mailmap 'cmitter@example.com'
+test_atom tag taggeremail:mailmap,localpart 'cmitter'
+test_atom tag taggeremail:localpart,mailmap 'cmitter'
+test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter'
+test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
+test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag subject 'Tagging at 1151968727'
+test_atom tag subject:sanitize 'Tagging-at-1151968727'
+test_atom tag contents:subject 'Tagging at 1151968727'
+test_atom tag body ''
+test_atom tag contents:body ''
+test_atom tag contents:signature ''
+test_atom tag contents 'Tagging at 1151968727
+'
+test_atom tag HEAD ' '
+
+test_expect_success 'basic atom: refs/tags/testtag *raw' '
+	git cat-file commit refs/tags/testtag^{} >expected &&
+	${git_for_each_ref} --format="%(*raw)" refs/tags/testtag >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_expect_success 'Check invalid atoms names are errors' '
+	test_must_fail ${git_for_each_ref} --format="%(INVALID)" refs/heads
+'
+
+test_expect_success 'Check format specifiers are ignored in naming date atoms' '
+	${git_for_each_ref} --format="%(authordate)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:default) %(authordate)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate) %(authordate:default)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:default) %(authordate:default)" refs/heads
+'
+
+test_expect_success 'Check valid format specifiers for date fields' '
+	${git_for_each_ref} --format="%(authordate:default)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:relative)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:short)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:local)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:iso8601)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:rfc2822)" refs/heads
+'
+
+test_expect_success 'Check invalid format specifiers are errors' '
+	test_must_fail ${git_for_each_ref} --format="%(authordate:INVALID)" refs/heads
+'
+
+test_expect_success 'arguments to %(objectname:short=) must be positive integers' '
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=0)" &&
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=-1)" &&
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=foo)"
+'
+
+test_bad_atom () {
+	case "$1" in
+	head) ref=refs/heads/main ;;
+	 tag) ref=refs/tags/testtag ;;
+	 sym) ref=refs/heads/sym ;;
+	   *) ref=$1 ;;
+	esac
+	format=$2
+	test_do=test_expect_${4:-success}
+
+	printf '%s\n' "$3" >expect
+	$test_do $PREREQ "err basic atom: $ref $format" '
+		test_must_fail ${git_for_each_ref} \
+			--format="%($format)" "$ref" 2>error &&
+		test_cmp expect error
+	'
+}
+
+test_bad_atom head 'authoremail:foo' \
+	'fatal: unrecognized %(authoremail) argument: foo'
+
+test_bad_atom head 'authoremail:mailmap,trim,bar' \
+	'fatal: unrecognized %(authoremail) argument: bar'
+
+test_bad_atom head 'authoremail:trim,' \
+	'fatal: unrecognized %(authoremail) argument: '
+
+test_bad_atom head 'authoremail:mailmaptrim' \
+	'fatal: unrecognized %(authoremail) argument: trim'
+
+test_bad_atom head 'committeremail: ' \
+	'fatal: unrecognized %(committeremail) argument:  '
+
+test_bad_atom head 'committeremail: trim,foo' \
+	'fatal: unrecognized %(committeremail) argument:  trim,foo'
+
+test_bad_atom head 'committeremail:mailmap,localpart ' \
+	'fatal: unrecognized %(committeremail) argument:  '
+
+test_bad_atom head 'committeremail:trim_localpart' \
+	'fatal: unrecognized %(committeremail) argument: _localpart'
+
+test_bad_atom head 'committeremail:localpart,,,trim' \
+	'fatal: unrecognized %(committeremail) argument: ,,trim'
+
+test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \
+	'fatal: unrecognized %(taggeremail) argument:  foo '
+
+test_bad_atom tag 'taggeremail:trim,localpart,' \
+	'fatal: unrecognized %(taggeremail) argument: '
+
+test_bad_atom tag 'taggeremail:mailmap;localpart trim' \
+	'fatal: unrecognized %(taggeremail) argument: ;localpart trim'
+
+test_bad_atom tag 'taggeremail:localpart trim' \
+	'fatal: unrecognized %(taggeremail) argument:  trim'
+
+test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \
+	'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim'
+
+test_date () {
+	f=$1 &&
+	committer_date=$2 &&
+	author_date=$3 &&
+	tagger_date=$4 &&
+	cat >expected <<-EOF &&
+	'refs/heads/main' '$committer_date' '$author_date'
+	'refs/tags/testtag' '$tagger_date'
+	EOF
+	(
+		${git_for_each_ref} --shell \
+			--format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
+			refs/heads &&
+		${git_for_each_ref} --shell \
+			--format="%(refname) %(taggerdate${f:+:$f})" \
+			refs/tags
+	) >actual &&
+	test_cmp expected actual
+}
+
+test_expect_success 'Check unformatted date fields output' '
+	test_date "" \
+		"Tue Jul 4 01:18:43 2006 +0200" \
+		"Tue Jul 4 01:18:44 2006 +0200" \
+		"Tue Jul 4 01:18:45 2006 +0200"
+'
+
+test_expect_success 'Check format "default" formatted date fields output' '
+	test_date default \
+		"Tue Jul 4 01:18:43 2006 +0200" \
+		"Tue Jul 4 01:18:44 2006 +0200" \
+		"Tue Jul 4 01:18:45 2006 +0200"
+'
+
+test_expect_success 'Check format "default-local" date fields output' '
+	test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006"
+'
+
+# Don't know how to do relative check because I can't know when this script
+# is going to be run and can't fake the current time to git, and hence can't
+# provide expected output.  Instead, I'll just make sure that "relative"
+# doesn't exit in error
+test_expect_success 'Check format "relative" date fields output' '
+	f=relative &&
+	(${git_for_each_ref} --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
+	${git_for_each_ref} --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
+'
+
+# We just check that this is the same as "relative" for now.
+test_expect_success 'Check format "relative-local" date fields output' '
+	test_date relative-local \
+		"$(${git_for_each_ref} --format="%(committerdate:relative)" refs/heads)" \
+		"$(${git_for_each_ref} --format="%(authordate:relative)" refs/heads)" \
+		"$(${git_for_each_ref} --format="%(taggerdate:relative)" refs/tags)"
+'
+
+test_expect_success 'Check format "short" date fields output' '
+	test_date short 2006-07-04 2006-07-04 2006-07-04
+'
+
+test_expect_success 'Check format "short-local" date fields output' '
+	test_date short-local 2006-07-03 2006-07-03 2006-07-03
+'
+
+test_expect_success 'Check format "local" date fields output' '
+	test_date local \
+		"Mon Jul 3 23:18:43 2006" \
+		"Mon Jul 3 23:18:44 2006" \
+		"Mon Jul 3 23:18:45 2006"
+'
+
+test_expect_success 'Check format "iso8601" date fields output' '
+	test_date iso8601 \
+		"2006-07-04 01:18:43 +0200" \
+		"2006-07-04 01:18:44 +0200" \
+		"2006-07-04 01:18:45 +0200"
+'
+
+test_expect_success 'Check format "iso8601-local" date fields output' '
+	test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000"
+'
+
+test_expect_success 'Check format "rfc2822" date fields output' '
+	test_date rfc2822 \
+		"Tue, 4 Jul 2006 01:18:43 +0200" \
+		"Tue, 4 Jul 2006 01:18:44 +0200" \
+		"Tue, 4 Jul 2006 01:18:45 +0200"
+'
+
+test_expect_success 'Check format "rfc2822-local" date fields output' '
+	test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000"
+'
+
+test_expect_success 'Check format "raw" date fields output' '
+	test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200"
+'
+
+test_expect_success 'Check format "raw-local" date fields output' '
+	test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000"
+'
+
+test_expect_success 'Check format of strftime date fields' '
+	echo "my date is 2006-07-04" >expected &&
+	${git_for_each_ref} \
+	  --format="%(authordate:format:my date is %Y-%m-%d)" \
+	  refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Check format of strftime-local date fields' '
+	echo "my date is 2006-07-03" >expected &&
+	${git_for_each_ref} \
+	  --format="%(authordate:format-local:my date is %Y-%m-%d)" \
+	  refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'exercise strftime with odd fields' '
+	echo >expected &&
+	${git_for_each_ref} --format="%(authordate:format:)" refs/heads >actual &&
+	test_cmp expected actual &&
+	long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" &&
+	echo $long >expected &&
+	${git_for_each_ref} --format="%(authordate:format:$long)" refs/heads >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/heads/main
+refs/remotes/origin/main
+refs/tags/testtag
+EOF
+
+test_expect_success 'Verify ascending sort' '
+	${git_for_each_ref} --format="%(refname)" --sort=refname >actual &&
+	test_cmp expected actual
+'
+
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/remotes/origin/main
+refs/heads/main
+EOF
+
+test_expect_success 'Verify descending sort' '
+	${git_for_each_ref} --format="%(refname)" --sort=-refname >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Give help even with invalid sort atoms' '
+	test_expect_code 129 ${git_for_each_ref} --sort=bogus -h >actual 2>&1 &&
+	grep "^usage: ${git_for_each_ref}" actual
+'
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/tags/testtag-2
+EOF
+
+test_expect_success 'exercise patterns with prefixes' '
+	git tag testtag-2 &&
+	test_when_finished "git tag -d testtag-2" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/testtag refs/tags/testtag-2 >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/tags/testtag-2
+EOF
+
+test_expect_success 'exercise glob patterns with prefixes' '
+	git tag testtag-2 &&
+	test_when_finished "git tag -d testtag-2" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/testtag "refs/tags/testtag-*" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/bar
+refs/tags/baz
+refs/tags/testtag
+EOF
+
+test_expect_success 'exercise patterns with prefix exclusions' '
+	for tag in foo/one foo/two foo/three bar baz
+	do
+		git tag "$tag" || return 1
+	done &&
+	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/ --exclude=refs/tags/foo >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/bar
+refs/tags/baz
+refs/tags/foo/one
+refs/tags/testtag
+EOF
+
+test_expect_success 'exercise patterns with pattern exclusions' '
+	for tag in foo/one foo/two foo/three bar baz
+	do
+		git tag "$tag" || return 1
+	done &&
+	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/ --exclude="refs/tags/foo/t*" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+'refs/heads/main'
+'refs/remotes/origin/main'
+'refs/tags/testtag'
+EOF
+
+test_expect_success 'Quoting style: shell' '
+	${git_for_each_ref} --shell --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Quoting style: perl' '
+	${git_for_each_ref} --perl --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Quoting style: python' '
+	${git_for_each_ref} --python --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+"refs/heads/main"
+"refs/remotes/origin/main"
+"refs/tags/testtag"
+EOF
+
+test_expect_success 'Quoting style: tcl' '
+	${git_for_each_ref} --tcl --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
+	test_expect_success "more than one quoting style: $i" "
+		test_must_fail ${git_for_each_ref} $i 2>err &&
+		grep '^error: more than one quoting style' err
+	"
+done
+
+test_expect_success 'setup for upstream:track[short]' '
+	test_commit two
+'
+
+test_atom head upstream:track '[ahead 1]'
+test_atom head upstream:trackshort '>'
+test_atom head upstream:track,nobracket 'ahead 1'
+test_atom head upstream:nobracket,track 'ahead 1'
+
+test_expect_success 'setup for push:track[short]' '
+	test_commit third &&
+	git update-ref refs/remotes/myfork/main main &&
+	git reset main~1
+'
+
+test_atom head push:track '[behind 1]'
+test_atom head push:trackshort '<'
+
+test_expect_success 'Check that :track[short] cannot be used with other atoms' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:track)" 2>/dev/null &&
+	test_must_fail ${git_for_each_ref} --format="%(refname:trackshort)" 2>/dev/null
+'
+
+test_expect_success 'Check that :track[short] works when upstream is invalid' '
+	cat >expected <<-\EOF &&
+	[gone]
+
+	EOF
+	test_when_finished "git config branch.main.merge refs/heads/main" &&
+	git config branch.main.merge refs/heads/does-not-exist &&
+	${git_for_each_ref} \
+		--format="%(upstream:track)$LF%(upstream:trackshort)" \
+		refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Check for invalid refname format' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:INVALID)"
+'
+
+test_expect_success 'set up color tests' '
+	cat >expected.color <<-EOF &&
+	$(git rev-parse --short refs/heads/main) <GREEN>main<RESET>
+	$(git rev-parse --short refs/remotes/myfork/main) <GREEN>myfork/main<RESET>
+	$(git rev-parse --short refs/remotes/origin/main) <GREEN>origin/main<RESET>
+	$(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET>
+	$(git rev-parse --short refs/tags/third) <GREEN>third<RESET>
+	$(git rev-parse --short refs/tags/two) <GREEN>two<RESET>
+	EOF
+	sed "s/<[^>]*>//g" <expected.color >expected.bare &&
+	color_format="%(objectname:short) %(color:green)%(refname:short)"
+'
+
+test_expect_success TTY '%(color) shows color with a tty' '
+	test_terminal ${git_for_each_ref} --format="$color_format" >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	test_cmp expected.color actual
+'
+
+test_expect_success '%(color) does not show color without tty' '
+	TERM=vt100 ${git_for_each_ref} --format="$color_format" >actual &&
+	test_cmp expected.bare actual
+'
+
+test_expect_success '--color can override tty check' '
+	${git_for_each_ref} --color --format="$color_format" >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	test_cmp expected.color actual
+'
+
+test_expect_success 'color.ui=always does not override tty check' '
+	git -c color.ui=always ${git_for_each_ref#git} --format="$color_format" >actual &&
+	test_cmp expected.bare actual
+'
+
+test_expect_success 'setup for describe atom tests' '
+	git init -b master describe-repo &&
+	(
+		cd describe-repo &&
+
+		test_commit --no-tag one &&
+		git tag tagone &&
+
+		test_commit --no-tag two &&
+		git tag -a -m "tag two" tagtwo
+	)
+'
+
+test_expect_success 'describe atom vs git describe' '
+	(
+		cd describe-repo &&
+
+		${git_for_each_ref} --format="%(objectname)" \
+			refs/tags/ >obj &&
+		while read hash
+		do
+			if desc=$(git describe $hash)
+			then
+				: >expect-contains-good
+			else
+				: >expect-contains-bad
+			fi &&
+			echo "$hash $desc" || return 1
+		done <obj >expect &&
+		test_path_exists expect-contains-good &&
+		test_path_exists expect-contains-bad &&
+
+		${git_for_each_ref} --format="%(objectname) %(describe)" \
+			refs/tags/ >actual 2>err &&
+		test_cmp expect actual &&
+		test_must_be_empty err
+	)
+'
+
+test_expect_success 'describe:tags vs describe --tags' '
+	(
+		cd describe-repo &&
+		git describe --tags >expect &&
+		${git_for_each_ref} --format="%(describe:tags)" \
+				refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
+	(
+		cd describe-repo &&
+
+		# Case 1: We have commits between HEAD and the most
+		#	  recent tag reachable from it
+		test_commit --no-tag file &&
+		git describe --abbrev=14 >expect &&
+		${git_for_each_ref} --format="%(describe:abbrev=14)" \
+			refs/heads/master >actual &&
+		test_cmp expect actual &&
+
+		# Make sure the hash used is at least 14 digits long
+		sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart &&
+		test 15 -le $(wc -c <hexpart) &&
+
+		# Case 2: We have a tag at HEAD, describe directly gives
+		#	  the name of the tag
+		git tag -a -m tagged tagname &&
+		git describe --abbrev=14 >expect &&
+		${git_for_each_ref} --format="%(describe:abbrev=14)" \
+			refs/heads/master >actual &&
+		test_cmp expect actual &&
+		test tagname = $(cat actual)
+	)
+'
+
+test_expect_success 'describe:match=... vs describe --match ...' '
+	(
+		cd describe-repo &&
+		git tag -a -m "tag foo" tag-foo &&
+		git describe --match "*-foo" >expect &&
+		${git_for_each_ref} --format="%(describe:match="*-foo")" \
+			refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'describe:exclude:... vs describe --exclude ...' '
+	(
+		cd describe-repo &&
+		git tag -a -m "tag bar" tag-bar &&
+		git describe --exclude "*-bar" >expect &&
+		${git_for_each_ref} --format="%(describe:exclude="*-bar")" \
+			refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'deref with describe atom' '
+	(
+		cd describe-repo &&
+		cat >expect <<-\EOF &&
+
+		tagname
+		tagname
+		tagname
+
+		tagtwo
+		EOF
+		${git_for_each_ref} --format="%(*describe)" >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'err on bad describe atom arg' '
+	(
+		cd describe-repo &&
+
+		# The bad arg is the only arg passed to describe atom
+		cat >expect <<-\EOF &&
+		fatal: unrecognized %(describe) argument: baz
+		EOF
+		test_must_fail ${git_for_each_ref} --format="%(describe:baz)" \
+			refs/heads/master 2>actual &&
+		test_cmp expect actual &&
+
+		# The bad arg is in the middle of the option string
+		# passed to the describe atom
+		cat >expect <<-\EOF &&
+		fatal: unrecognized %(describe) argument: qux=1,abbrev=14
+		EOF
+		test_must_fail ${git_for_each_ref} \
+			--format="%(describe:tags,qux=1,abbrev=14)" \
+			ref/heads/master 2>actual &&
+		test_cmp expect actual
+	)
+'
+
+cat >expected <<\EOF
+heads/main
+tags/main
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs (strict)' '
+	git config --bool core.warnambiguousrefs true &&
+	git checkout -b newtag &&
+	echo "Using $datestamp" > one &&
+	git add one &&
+	git commit -m "Branch" &&
+	setdate_and_increment &&
+	git tag -m "Tagging at $datestamp" main &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+heads/main
+main
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs (loose)' '
+	git config --bool core.warnambiguousrefs false &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+heads/ambiguous
+ambiguous
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs II (loose)' '
+	git checkout main &&
+	git tag ambiguous testtag^0 &&
+	git branch ambiguous testtag^0 &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'create tag without tagger' '
+	git tag -a -m "Broken tag" taggerless &&
+	git tag -f taggerless $(git cat-file tag taggerless |
+		sed -e "/^tagger /d" |
+		git hash-object --literally --stdin -w -t tag)
+'
+
+test_atom refs/tags/taggerless type 'commit'
+test_atom refs/tags/taggerless tag 'taggerless'
+test_atom refs/tags/taggerless tagger ''
+test_atom refs/tags/taggerless taggername ''
+test_atom refs/tags/taggerless taggeremail ''
+test_atom refs/tags/taggerless taggeremail:trim ''
+test_atom refs/tags/taggerless taggeremail:localpart ''
+test_atom refs/tags/taggerless taggerdate ''
+test_atom refs/tags/taggerless committer ''
+test_atom refs/tags/taggerless committername ''
+test_atom refs/tags/taggerless committeremail ''
+test_atom refs/tags/taggerless committeremail:trim ''
+test_atom refs/tags/taggerless committeremail:localpart ''
+test_atom refs/tags/taggerless committerdate ''
+test_atom refs/tags/taggerless subject 'Broken tag'
+
+test_expect_success 'an unusual tag with an incomplete line' '
+
+	git tag -m "bogo" bogo &&
+	bogo=$(git cat-file tag bogo) &&
+	bogo=$(printf "%s" "$bogo" | git mktag) &&
+	git tag -f bogo "$bogo" &&
+	${git_for_each_ref} --format "%(body)" refs/tags/bogo
+
+'
+
+test_expect_success 'create tag with subject and body content' '
+	cat >>msg <<-\EOF &&
+		the subject line
+
+		first body line
+		second body line
+	EOF
+	git tag -F msg subject-body
+'
+test_atom refs/tags/subject-body subject 'the subject line'
+test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
+test_atom refs/tags/subject-body body 'first body line
+second body line
+'
+test_atom refs/tags/subject-body contents 'the subject line
+
+first body line
+second body line
+'
+
+test_expect_success 'create tag with multiline subject' '
+	cat >msg <<-\EOF &&
+		first subject line
+		second subject line
+
+		first body line
+		second body line
+	EOF
+	git tag -F msg multiline
+'
+test_atom refs/tags/multiline subject 'first subject line second subject line'
+test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
+test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
+test_atom refs/tags/multiline body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:signature ''
+test_atom refs/tags/multiline contents 'first subject line
+second subject line
+
+first body line
+second body line
+'
+
+test_expect_success GPG 'create signed tags' '
+	git tag -s -m "" signed-empty &&
+	git tag -s -m "subject line" signed-short &&
+	cat >msg <<-\EOF &&
+	subject line
+
+	body contents
+	EOF
+	git tag -s -F msg signed-long
+'
+
+sig='-----BEGIN PGP SIGNATURE-----
+-----END PGP SIGNATURE-----
+'
+
+PREREQ=GPG
+test_atom refs/tags/signed-empty subject ''
+test_atom refs/tags/signed-empty subject:sanitize ''
+test_atom refs/tags/signed-empty contents:subject ''
+test_atom refs/tags/signed-empty body "$sig"
+test_atom refs/tags/signed-empty contents:body ''
+test_atom refs/tags/signed-empty contents:signature "$sig"
+test_atom refs/tags/signed-empty contents "$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
+	git cat-file tag refs/tags/signed-empty >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-empty >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_atom refs/tags/signed-short subject 'subject line'
+test_atom refs/tags/signed-short subject:sanitize 'subject-line'
+test_atom refs/tags/signed-short contents:subject 'subject line'
+test_atom refs/tags/signed-short body "$sig"
+test_atom refs/tags/signed-short contents:body ''
+test_atom refs/tags/signed-short contents:signature "$sig"
+test_atom refs/tags/signed-short contents "subject line
+$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
+	git cat-file tag refs/tags/signed-short >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-short >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_atom refs/tags/signed-long subject 'subject line'
+test_atom refs/tags/signed-long subject:sanitize 'subject-line'
+test_atom refs/tags/signed-long contents:subject 'subject line'
+test_atom refs/tags/signed-long body "body contents
+$sig"
+test_atom refs/tags/signed-long contents:body 'body contents
+'
+test_atom refs/tags/signed-long contents:signature "$sig"
+test_atom refs/tags/signed-long contents "subject line
+
+body contents
+$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
+	git cat-file tag refs/tags/signed-long >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-long >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_expect_success 'set up refs pointing to tree and blob' '
+	git update-ref refs/mytrees/first refs/heads/main^{tree} &&
+	git update-ref refs/myblobs/first refs/heads/main:one
+'
+
+test_atom refs/mytrees/first subject ""
+test_atom refs/mytrees/first contents:subject ""
+test_atom refs/mytrees/first body ""
+test_atom refs/mytrees/first contents:body ""
+test_atom refs/mytrees/first contents:signature ""
+test_atom refs/mytrees/first contents ""
+
+test_expect_success 'basic atom: refs/mytrees/first raw' '
+	git cat-file tree refs/mytrees/first >expected &&
+	echo >>expected &&
+	${git_for_each_ref} --format="%(raw)" refs/mytrees/first >actual &&
+	test_cmp expected actual &&
+	git cat-file -s refs/mytrees/first >expected &&
+	${git_for_each_ref} --format="%(raw:size)" refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_atom refs/myblobs/first subject ""
+test_atom refs/myblobs/first contents:subject ""
+test_atom refs/myblobs/first body ""
+test_atom refs/myblobs/first contents:body ""
+test_atom refs/myblobs/first contents:signature ""
+test_atom refs/myblobs/first contents ""
+
+test_expect_success 'basic atom: refs/myblobs/first raw' '
+	git cat-file blob refs/myblobs/first >expected &&
+	echo >>expected &&
+	${git_for_each_ref} --format="%(raw)" refs/myblobs/first >actual &&
+	test_cmp expected actual &&
+	git cat-file -s refs/myblobs/first >expected &&
+	${git_for_each_ref} --format="%(raw:size)" refs/myblobs/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'set up refs pointing to binary blob' '
+	printf "a\0b\0c" >blob1 &&
+	printf "a\0c\0b" >blob2 &&
+	printf "\0a\0b\0c" >blob3 &&
+	printf "abc" >blob4 &&
+	printf "\0 \0 \0 " >blob5 &&
+	printf "\0 \0a\0 " >blob6 &&
+	printf "  " >blob7 &&
+	>blob8 &&
+	obj=$(git hash-object -w blob1) &&
+	git update-ref refs/myblobs/blob1 "$obj" &&
+	obj=$(git hash-object -w blob2) &&
+	git update-ref refs/myblobs/blob2 "$obj" &&
+	obj=$(git hash-object -w blob3) &&
+	git update-ref refs/myblobs/blob3 "$obj" &&
+	obj=$(git hash-object -w blob4) &&
+	git update-ref refs/myblobs/blob4 "$obj" &&
+	obj=$(git hash-object -w blob5) &&
+	git update-ref refs/myblobs/blob5 "$obj" &&
+	obj=$(git hash-object -w blob6) &&
+	git update-ref refs/myblobs/blob6 "$obj" &&
+	obj=$(git hash-object -w blob7) &&
+	git update-ref refs/myblobs/blob7 "$obj" &&
+	obj=$(git hash-object -w blob8) &&
+	git update-ref refs/myblobs/blob8 "$obj"
+'
+
+test_expect_success 'Verify sorts with raw' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob8
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/blob3
+	refs/myblobs/blob7
+	refs/mytrees/first
+	refs/myblobs/first
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob4
+	refs/heads/main
+	EOF
+	${git_for_each_ref} --format="%(refname)" --sort=raw \
+		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Verify sorts with raw:size' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob8
+	refs/myblobs/blob7
+	refs/myblobs/blob4
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob3
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/first
+	refs/mytrees/first
+	refs/heads/main
+	EOF
+	${git_for_each_ref} --format="%(refname)" --sort=raw:size \
+		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:equals)' '
+	cat >expected <<-EOF &&
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	refs/myblobs/blob4
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	EOF
+	${git_for_each_ref} --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
+		refs/myblobs/ refs/heads/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:notequals)' '
+	cat >expected <<-EOF &&
+	refs/heads/ambiguous
+	refs/heads/main
+	refs/heads/newtag
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob3
+	equals
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/blob7
+	refs/myblobs/blob8
+	refs/myblobs/first
+	EOF
+	${git_for_each_ref} --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
+		refs/myblobs/ refs/heads/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'empty raw refs with %(if)' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob1 not empty
+	refs/myblobs/blob2 not empty
+	refs/myblobs/blob3 not empty
+	refs/myblobs/blob4 not empty
+	refs/myblobs/blob5 not empty
+	refs/myblobs/blob6 not empty
+	refs/myblobs/blob7 empty
+	refs/myblobs/blob8 empty
+	refs/myblobs/first not empty
+	EOF
+	${git_for_each_ref} --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
+		refs/myblobs/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '%(raw) with --python must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --python
+'
+
+test_expect_success '%(raw) with --tcl must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --tcl
+'
+
+test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' '
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
+	cmp blob1 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
+	cmp blob3 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
+	cmp blob8 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
+	cmp one actual &&
+	git cat-file tree refs/mytrees/first > expected &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
+	cmp expected actual
+'
+
+test_expect_success '%(raw) with --shell must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --shell
+'
+
+test_expect_success '%(raw) with --shell and --sort=raw must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --sort=raw --shell
+'
+
+test_expect_success '%(raw:size) with --shell' '
+	${git_for_each_ref} --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
+	${git_for_each_ref} --format="%(raw:size)" --shell >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --format compare with cat-file --batch" '
+	git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
+	${git_for_each_ref} --format="%(objectname) %(objecttype) %(objectsize)
+%(raw)" refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'verify sorts with contents:size' '
+	cat >expect <<-\EOF &&
+	refs/heads/main
+	refs/heads/newtag
+	refs/heads/ambiguous
+	EOF
+	${git_for_each_ref} --format="%(refname)" \
+		--sort=contents:size refs/heads/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'set up multiple-sort tags' '
+	for when in 100000 200000
+	do
+		for email in user1 user2
+		do
+			for ref in ref1 ref2
+			do
+				GIT_COMMITTER_DATE="@$when +0000" \
+				GIT_COMMITTER_EMAIL="$email@example.com" \
+				git tag -m "tag $ref-$when-$email" \
+				multi-$ref-$when-$email || return 1
+			done
+		done
+	done
+'
+
+test_expect_success 'Verify sort with multiple keys' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=-refname \
+		--sort=taggeremail \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'equivalent sorts fall back on refname' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '--no-sort cancels the previous sort keys' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=-refname \
+		--sort=taggeremail \
+		--no-sort \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '--no-sort without subsequent --sort prints expected refs' '
+	cat >expected <<-\EOF &&
+	refs/tags/multi-ref1-100000-user1
+	refs/tags/multi-ref1-100000-user2
+	refs/tags/multi-ref1-200000-user1
+	refs/tags/multi-ref1-200000-user2
+	refs/tags/multi-ref2-100000-user1
+	refs/tags/multi-ref2-100000-user2
+	refs/tags/multi-ref2-200000-user1
+	refs/tags/multi-ref2-200000-user2
+	EOF
+
+	# Sort the results with `sort` for a consistent comparison against
+	# expected
+	${git_for_each_ref} \
+		--format="%(refname)" \
+		--no-sort \
+		"refs/tags/multi-*" | sort >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'set up custom date sorting' '
+	# Dates:
+	# - Wed Feb 07 2024 21:34:20 +0000
+	# - Tue Dec 14 1999 00:05:22 +0000
+	# - Fri Jun 04 2021 11:26:51 +0000
+	# - Mon Jan 22 2007 16:44:01 GMT+0000
+	i=1 &&
+	for when in 1707341660 945129922 1622806011 1169484241
+	do
+		GIT_COMMITTER_DATE="@$when +0000" \
+		GIT_COMMITTER_EMAIL="user@example.com" \
+		git tag -m "tag $when" custom-dates-$i &&
+		i=$(($i+1)) || return 1
+	done
+'
+
+test_expect_success 'sort by date defaults to full timestamp' '
+	cat >expected <<-\EOF &&
+	945129922 refs/tags/custom-dates-2
+	1169484241 refs/tags/custom-dates-4
+	1622806011 refs/tags/custom-dates-3
+	1707341660 refs/tags/custom-dates-1
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(creatordate:unix) %(refname)" \
+		--sort=creatordate \
+		"refs/tags/custom-dates-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'sort by custom date format' '
+	cat >expected <<-\EOF &&
+	00:05:22 refs/tags/custom-dates-2
+	11:26:51 refs/tags/custom-dates-3
+	16:44:01 refs/tags/custom-dates-4
+	21:34:20 refs/tags/custom-dates-1
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(creatordate:format:%H:%M:%S) %(refname)" \
+		--sort="creatordate:format:%H:%M:%S" \
+		"refs/tags/custom-dates-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
+	test_when_finished "git checkout main" &&
+	${git_for_each_ref} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	sed -e "s/^\* /  /" actual >expect &&
+	git checkout --orphan orphaned-branch &&
+	${git_for_each_ref} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	test_cmp expect actual
+'
+
+cat >trailers <<EOF
+Reviewed-by: A U Thor <author@example.com>
+Signed-off-by: A U Thor <author@example.com>
+[ v2 updated patch description ]
+Acked-by: A U Thor
+  <author@example.com>
+EOF
+
+unfold () {
+	perl -0pe 's/\n\s+/ /g'
+}
+
+test_expect_success 'set up trailers for next test' '
+	echo "Some contents" > two &&
+	git add two &&
+	git commit -F - <<-EOF
+	trailers: this commit message has trailers
+
+	Some message contents
+
+	$(cat trailers)
+	EOF
+'
+
+test_trailer_option () {
+	if test "$#" -eq 3
+	then
+		prereq="$1"
+		shift
+	fi &&
+	title=$1 option=$2
+	cat >expect
+	test_expect_success $prereq "$title" '
+		${git_for_each_ref} --format="%($option)" refs/heads/main >actual &&
+		test_cmp expect actual &&
+		${git_for_each_ref} --format="%(contents:$option)" refs/heads/main >actual &&
+		test_cmp expect actual
+	'
+}
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' \
+	'trailers:unfold' <<-EOF
+	$(unfold <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only) shows only "key: value" trailers' \
+	'trailers:only' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=no,only=true) shows only "key: value" trailers' \
+	'trailers:only=no,only=true' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=yes) shows only "key: value" trailers' \
+	'trailers:only=yes' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=no) shows all trailers' \
+	'trailers:only=no' <<-EOF
+	$(cat trailers)
+
+	EOF
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:only) and %(trailers:unfold) work together' \
+	'trailers:only,unfold' <<-EOF
+	$(grep -v patch.description <trailers | unfold)
+
+	EOF
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) and %(trailers:only) work together' \
+	'trailers:unfold,only' <<-EOF
+	$(grep -v patch.description <trailers | unfold)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) shows that trailer' \
+	'trailers:key=Signed-off-by' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) is case insensitive' \
+	'trailers:key=SiGned-oFf-bY' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo:) trailing colon also works' \
+	'trailers:key=Signed-off-by:' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) multiple keys' \
+	'trailers:key=Reviewed-by:,key=Signed-off-by' <<-EOF
+	Reviewed-by: A U Thor <author@example.com>
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=nonexistent) becomes empty' \
+	'trailers:key=Shined-off-by:' <<-EOF
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) handles multiple lines even if folded' \
+	'trailers:key=Acked-by' <<-EOF
+	$(grep -v patch.description <trailers | grep -v Signed-off-by | grep -v Reviewed-by)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,unfold) properly unfolds' \
+	'trailers:key=Signed-Off-by,unfold' <<-EOF
+	$(unfold <trailers | grep Signed-off-by)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,only=no) also includes nontrailer lines' \
+	'trailers:key=Signed-off-by,only=no' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+	$(grep patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,valueonly) shows only value' \
+	'trailers:key=Signed-off-by,valueonly' <<-EOF
+	A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:separator) changes separator' \
+	'trailers:separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by: A U Thor <author@example.com>,Signed-off-by: A U Thor <author@example.com>
+	EOF
+
+test_trailer_option '%(trailers:key_value_separator) changes key-value separator' \
+	'trailers:key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by,A U Thor <author@example.com>
+	Signed-off-by,A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:separator,key_value_separator) changes both separators' \
+	'trailers:separator=%x2C,key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by,A U Thor <author@example.com>,Signed-off-by,A U Thor <author@example.com>
+	EOF
+
+test_expect_success 'multiple %(trailers) use their own options' '
+	git tag -F - tag-with-trailers <<-\EOF &&
+	body
+
+	one: foo
+	one: bar
+	two: baz
+	two: qux
+	EOF
+	t1="%(trailers:key=one,key_value_separator=W,separator=X)" &&
+	t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" &&
+	${git_for_each_ref} --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
+	cat >expect <<-\EOF &&
+	oneWfooXoneWbar
+	twoYbazZtwoYqux
+	EOF
+	test_cmp expect actual
+'
+
+test_failing_trailer_option () {
+	title=$1 option=$2
+	cat >expect
+	test_expect_success "$title" '
+		# error message cannot be checked under i18n
+		test_must_fail ${git_for_each_ref} --format="%($option)" refs/heads/main 2>actual &&
+		test_cmp expect actual &&
+		test_must_fail ${git_for_each_ref} --format="%(contents:$option)" refs/heads/main 2>actual &&
+		test_cmp expect actual
+	'
+}
+
+test_failing_trailer_option '%(trailers) rejects unknown trailers arguments' \
+	'trailers:unsupported' <<-\EOF
+	fatal: unknown %(trailers) argument: unsupported
+	EOF
+
+test_failing_trailer_option '%(trailers:key) without value is error' \
+	'trailers:key' <<-\EOF
+	fatal: expected %(trailers:key=<value>)
+	EOF
+
+test_expect_success 'if arguments, %(contents:trailers) shows error if colon is missing' '
+	cat >expect <<-EOF &&
+	fatal: unrecognized %(contents) argument: trailersonly
+	EOF
+	test_must_fail ${git_for_each_ref} --format="%(contents:trailersonly)" 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'basic atom: head contents:trailers' '
+	${git_for_each_ref} --format="%(contents:trailers)" refs/heads/main >actual &&
+	sanitize_pgp <actual >actual.clean &&
+	# ${git_for_each_ref} ends with a blank line
+	cat >expect <<-EOF &&
+	$(cat trailers)
+
+	EOF
+	test_cmp expect actual.clean
+'
+
+test_expect_success 'basic atom: rest must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(rest)" refs/heads/main
+'
+
+test_expect_success 'HEAD atom does not take arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(HEAD:foo)" 2>err &&
+	echo "fatal: %(HEAD) does not take arguments" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'subject atom rejects unknown arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(subject:foo)" 2>err &&
+	echo "fatal: unrecognized %(subject) argument: foo" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'refname atom rejects unknown arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:foo)" 2>err &&
+	echo "fatal: unrecognized %(refname) argument: foo" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'trailer parsing not fooled by --- line' '
+	git commit --allow-empty -F - <<-\EOF &&
+	this is the subject
+
+	This is the body. The message has a "---" line which would confuse a
+	message+patch parser. But here we know we have only a commit message,
+	so we get it right.
+
+	trailer: wrong
+	---
+	This is more body.
+
+	trailer: right
+	EOF
+
+	{
+		echo "trailer: right" &&
+		echo
+	} >expect &&
+	${git_for_each_ref} --format="%(trailers)" refs/heads/main >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'Add symbolic ref for the following tests' '
+	git symbolic-ref refs/heads/sym refs/heads/main
+'
+
+cat >expected <<EOF
+refs/heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref) atom' '
+	${git_for_each_ref} --format="%(symref)" refs/heads/sym >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref:short) atom' '
+	${git_for_each_ref} --format="%(symref:short)" refs/heads/sym >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+main
+heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref:lstrip) atom' '
+	${git_for_each_ref} --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual &&
+
+	${git_for_each_ref} --format="%(symref:strip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+refs
+refs/heads
+EOF
+
+test_expect_success 'Verify usage of %(symref:rstrip) atom' '
+	${git_for_each_ref} --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual
+'
+
+test_expect_success ':remotename and :remoteref' '
+	git init remote-tests &&
+	(
+		cd remote-tests &&
+		test_commit initial &&
+		git branch -M main &&
+		git remote add from fifth.coffee:blub &&
+		git config branch.main.remote from &&
+		git config branch.main.merge refs/heads/stable &&
+		git remote add to southridge.audio:repo &&
+		git config remote.to.push "refs/heads/*:refs/heads/pushed/*" &&
+		git config branch.main.pushRemote to &&
+		for pair in "%(upstream)=refs/remotes/from/stable" \
+			"%(upstream:remotename)=from" \
+			"%(upstream:remoteref)=refs/heads/stable" \
+			"%(push)=refs/remotes/to/pushed/main" \
+			"%(push:remotename)=to" \
+			"%(push:remoteref)=refs/heads/pushed/main"
+		do
+			echo "${pair#*=}" >expect &&
+			${git_for_each_ref} --format="${pair%=*}" \
+				refs/heads/main >actual &&
+			test_cmp expect actual || exit 1
+		done &&
+		git branch push-simple &&
+		git config branch.push-simple.pushRemote from &&
+		actual="$(${git_for_each_ref} \
+			--format="%(push:remotename),%(push:remoteref)" \
+			refs/heads/push-simple)" &&
+		test from, = "$actual"
+	)
+'
+
+test_expect_success "${git_for_each_ref} --ignore-case ignores case" '
+	${git_for_each_ref} --format="%(refname)" refs/heads/MAIN >actual &&
+	test_must_be_empty actual &&
+
+	echo refs/heads/main >expect &&
+	${git_for_each_ref} --format="%(refname)" --ignore-case \
+		refs/heads/MAIN >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --omit-empty works" '
+	${git_for_each_ref} --format="%(refname)" >actual &&
+	test_line_count -gt 1 actual &&
+	${git_for_each_ref} --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
+	echo refs/heads/main >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --ignore-case works on multiple sort keys" '
+	# name refs numerically to avoid case-insensitive filesystem conflicts
+	nr=0 &&
+	for email in a A b B
+	do
+		for subject in a A b B
+		do
+			GIT_COMMITTER_EMAIL="$email@example.com" \
+			git tag -m "tag $subject" icase-$(printf %02d $nr) &&
+			nr=$((nr+1))||
+			return 1
+		done
+	done &&
+	${git_for_each_ref} --ignore-case \
+		--format="%(taggeremail) %(subject) %(refname)" \
+		--sort=refname \
+		--sort=subject \
+		--sort=taggeremail \
+		refs/tags/icase-* >actual &&
+	cat >expect <<-\EOF &&
+	<a@example.com> tag a refs/tags/icase-00
+	<a@example.com> tag A refs/tags/icase-01
+	<A@example.com> tag a refs/tags/icase-04
+	<A@example.com> tag A refs/tags/icase-05
+	<a@example.com> tag b refs/tags/icase-02
+	<a@example.com> tag B refs/tags/icase-03
+	<A@example.com> tag b refs/tags/icase-06
+	<A@example.com> tag B refs/tags/icase-07
+	<b@example.com> tag a refs/tags/icase-08
+	<b@example.com> tag A refs/tags/icase-09
+	<B@example.com> tag a refs/tags/icase-12
+	<B@example.com> tag A refs/tags/icase-13
+	<b@example.com> tag b refs/tags/icase-10
+	<b@example.com> tag B refs/tags/icase-11
+	<B@example.com> tag b refs/tags/icase-14
+	<B@example.com> tag B refs/tags/icase-15
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} reports broken tags" '
+	git tag -m "good tag" broken-tag-good HEAD &&
+	git cat-file tag broken-tag-good >good &&
+	sed s/commit/blob/ <good >bad &&
+	bad=$(git hash-object -w -t tag bad) &&
+	git update-ref refs/tags/broken-tag-bad $bad &&
+	test_must_fail ${git_for_each_ref} --format="%(*objectname)" \
+		refs/tags/broken-tag-*
+'
+
+test_expect_success 'set up tag with signature and no blank lines' '
+	git tag -F - fake-sig-no-blanks <<-\EOF
+	this is the subject
+	-----BEGIN PGP SIGNATURE-----
+	not a real signature, but we just care about the
+	subject/body parsing. It is important here that
+	there are no blank lines in the signature.
+	-----END PGP SIGNATURE-----
+	EOF
+'
+
+test_atom refs/tags/fake-sig-no-blanks contents:subject 'this is the subject'
+test_atom refs/tags/fake-sig-no-blanks contents:body ''
+test_atom refs/tags/fake-sig-no-blanks contents:signature "$sig"
+
+test_expect_success 'set up tag with CRLF signature' '
+	append_cr <<-\EOF |
+	this is the subject
+	-----BEGIN PGP SIGNATURE-----
+
+	not a real signature, but we just care about
+	the subject/body parsing. It is important here
+	that there is a blank line separating this
+	from the signature header.
+	-----END PGP SIGNATURE-----
+	EOF
+	git tag -F - --cleanup=verbatim fake-sig-crlf
+'
+
+test_atom refs/tags/fake-sig-crlf contents:subject 'this is the subject'
+test_atom refs/tags/fake-sig-crlf contents:body ''
+
+# CRLF is retained in the signature, so we have to pass our expected value
+# through append_cr. But test_atom requires a shell string, which means command
+# substitution, and the shell will strip trailing newlines from the output of
+# the substitution. Hack around it by adding and then removing a dummy line.
+sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
+sig_crlf=${sig_crlf%dummy}
+test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
+
+test_expect_success 'set up tag with signature and trailers' '
+	git tag -F - fake-sig-trailer <<-\EOF
+	this is the subject
+
+	this is the body
+
+	My-Trailer: foo
+	-----BEGIN PGP SIGNATURE-----
+
+	not a real signature, but we just care about the
+	subject/body/trailer parsing.
+	-----END PGP SIGNATURE-----
+	EOF
+'
+
+# use "separator=" here to suppress the terminating newline
+test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo'
+
+test_expect_success "${git_for_each_ref} --stdin: empty" '
+	>in &&
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	${git_for_each_ref} --format="%(refname)" >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --stdin: fails if extra args" '
+	>in &&
+	test_must_fail ${git_for_each_ref} --format="%(refname)" \
+		--stdin refs/heads/extra <in 2>err &&
+	grep "unknown arguments supplied with --stdin" err
+'
+
+test_expect_success "${git_for_each_ref} --stdin: matches" '
+	cat >in <<-EOF &&
+	refs/tags/multi*
+	refs/heads/amb*
+	EOF
+
+	cat >expect <<-EOF &&
+	refs/heads/ambiguous
+	refs/tags/multi-ref1-100000-user1
+	refs/tags/multi-ref1-100000-user2
+	refs/tags/multi-ref1-200000-user1
+	refs/tags/multi-ref1-200000-user2
+	refs/tags/multi-ref2-100000-user1
+	refs/tags/multi-ref2-100000-user2
+	refs/tags/multi-ref2-200000-user1
+	refs/tags/multi-ref2-200000-user2
+	refs/tags/multiline
+	EOF
+
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} with non-existing refs" '
+	cat >in <<-EOF &&
+	refs/heads/this-ref-does-not-exist
+	refs/tags/bogus
+	EOF
+
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	test_must_be_empty actual &&
+
+	xargs ${git_for_each_ref} --format="%(refname)" <in >actual &&
+	test_must_be_empty actual
+'
+
+test_expect_success "${git_for_each_ref} with nested tags" '
+	git tag -am "Normal tag" nested/base HEAD &&
+	git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
+	git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
+
+	head_oid="$(git rev-parse HEAD)" &&
+	base_tag_oid="$(git rev-parse refs/tags/nested/base)" &&
+	nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" &&
+	nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" &&
+
+	cat >expect <<-EOF &&
+	refs/tags/nested/base $base_tag_oid tag $head_oid commit
+	refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit
+	refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
+		refs/tags/nested/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'is-base atom with non-commits' '
+	${git_for_each_ref} --format="%(is-base:HEAD) %(refname)" >out 2>err &&
+	grep "(HEAD) refs/heads/main" out &&
+
+	test_line_count = 2 err &&
+	grep "error: object .* is a commit, not a blob" err &&
+	grep "error: bad tag pointer to" err
+'
+
+GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
+TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
+
+test_expect_success GPG 'setup for signature atom using gpg' '
+	git checkout -b signed &&
+
+	test_when_finished "test_unconfig commit.gpgSign" &&
+
+	echo "1" >file &&
+	git add file &&
+	test_tick &&
+	git commit -S -m "file: 1" &&
+	git tag first-signed &&
+
+	echo "2" >file &&
+	test_tick &&
+	git commit -a -m "file: 2" &&
+	git tag second-unsigned &&
+
+	git config commit.gpgSign 1 &&
+	echo "3" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 3" &&
+	git tag third-unsigned &&
+
+	test_tick &&
+	git rebase -f HEAD^^ && git tag second-signed HEAD^ &&
+	git tag third-signed &&
+
+	echo "4" >file &&
+	test_tick &&
+	git commit -a -SB7227189 -m "file: 4" &&
+	git tag fourth-signed &&
+
+	echo "5" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 5" &&
+	git tag fifth-unsigned &&
+
+	echo "6" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 6" &&
+
+	test_tick &&
+	git rebase -f HEAD^^ &&
+	git tag fifth-signed HEAD^ &&
+	git tag sixth-signed &&
+
+	echo "7" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 7" &&
+	git tag seventh-unsigned
+'
+
+test_expect_success GPGSSH 'setup for signature atom using ssh' '
+	test_when_finished "test_unconfig gpg.format user.signingkey" &&
+
+	test_config gpg.format ssh &&
+	test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+	echo "8" >file &&
+	test_tick &&
+	git add file &&
+	git commit -S -m "file: 8" &&
+	git tag eighth-signed-ssh
+'
+
+test_expect_success GPG2 'bare signature atom' '
+	git verify-commit first-signed 2>expect &&
+	echo  >>expect &&
+	${git_for_each_ref} refs/tags/first-signed \
+		--format="%(signature)" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show good signature with custom format' '
+	git verify-commit first-signed &&
+	cat >expect <<-\EOF &&
+	G
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	EOF
+	${git_for_each_ref} refs/tags/first-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+test_expect_success GPGSSH 'show good signature with custom format with ssh' '
+	test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+	FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+	cat >expect.tmpl <<-\EOF &&
+	G
+	FINGERPRINT
+	principal with number 1
+	FINGERPRINT
+
+	EOF
+	sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
+	${git_for_each_ref} refs/tags/eighth-signed-ssh \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'signature atom with grade option and bad signature' '
+	git cat-file commit third-signed >raw &&
+	sed -e "s/^file: 3/file: 3 forged/" raw >forged1 &&
+	FORGED1=$(git hash-object -w -t commit forged1) &&
+	git update-ref refs/tags/third-signed "$FORGED1" &&
+	test_must_fail git verify-commit "$FORGED1" &&
+
+	cat >expect <<-\EOF &&
+	B
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+
+
+	EOF
+	${git_for_each_ref} refs/tags/third-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with custom format' '
+	cat >expect <<-\EOF &&
+	U
+	65A0EEA02E30CAD7
+	Eris Discordia <discord@example.net>
+	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+	D4BE22311AD3131E5EDA29A461092E85B7227189
+	EOF
+	${git_for_each_ref} refs/tags/fourth-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with undefined trust level' '
+	cat >expect <<-\EOF &&
+	undefined
+	65A0EEA02E30CAD7
+	Eris Discordia <discord@example.net>
+	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+	D4BE22311AD3131E5EDA29A461092E85B7227189
+	EOF
+	${git_for_each_ref} refs/tags/fourth-signed \
+		--format="$TRUSTLEVEL_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with ultimate trust level' '
+	cat >expect <<-\EOF &&
+	ultimate
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	EOF
+	${git_for_each_ref} refs/tags/sixth-signed \
+		--format="$TRUSTLEVEL_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show unknown signature with custom format' '
+	cat >expect <<-\EOF &&
+	E
+	13B6F51ECDDE430D
+
+
+
+	EOF
+	GNUPGHOME="$GNUPGHOME_NOT_USED" ${git_for_each_ref} \
+		refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show lack of signature with custom format' '
+	cat >expect <<-\EOF &&
+	N
+
+
+
+
+	EOF
+	${git_for_each_ref} refs/tags/seventh-unsigned \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index ce9af79ab1..1d9809114d 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -6,2150 +6,14 @@
 test_description='for-each-ref test'
 
 . ./test-lib.sh
-GNUPGHOME_NOT_USED=$GNUPGHOME
-. "$TEST_DIRECTORY"/lib-gpg.sh
-. "$TEST_DIRECTORY"/lib-terminal.sh
 
-# Mon Jul 3 23:18:43 2006 +0000
-datestamp=1151968723
-setdate_and_increment () {
-    GIT_COMMITTER_DATE="$datestamp +0200"
-    datestamp=$(expr "$datestamp" + 1)
-    GIT_AUTHOR_DATE="$datestamp +0200"
-    datestamp=$(expr "$datestamp" + 1)
-    export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
-}
-
-test_object_file_size () {
-	oid=$(git rev-parse "$1")
-	path=".git/objects/$(test_oid_to_path $oid)"
-	test_file_size "$path"
-}
-
-test_expect_success setup '
-	# setup .mailmap
-	cat >.mailmap <<-EOF &&
-	A Thor <athor@example.com> A U Thor <author@example.com>
-	C Mitter <cmitter@example.com> C O Mitter <committer@example.com>
-	EOF
-
-	setdate_and_increment &&
-	echo "Using $datestamp" > one &&
-	git add one &&
-	git commit -m "Initial" &&
-	git branch -M main &&
-	setdate_and_increment &&
-	git tag -a -m "Tagging at $datestamp" testtag &&
-	git update-ref refs/remotes/origin/main main &&
-	git remote add origin nowhere &&
-	git config branch.main.remote origin &&
-	git config branch.main.merge refs/heads/main &&
-	git remote add myfork elsewhere &&
-	git config remote.pushdefault myfork &&
-	git config push.default current
-'
-
-test_atom () {
-	case "$1" in
-		head) ref=refs/heads/main ;;
-		 tag) ref=refs/tags/testtag ;;
-		 sym) ref=refs/heads/sym ;;
-		   *) ref=$1 ;;
-	esac
-	format=$2
-	test_do=test_expect_${4:-success}
-
-	printf '%s\n' "$3" >expected
-	$test_do $PREREQ "basic atom: $ref $format" '
-		git for-each-ref --format="%($format)" "$ref" >actual &&
-		sanitize_pgp <actual >actual.clean &&
-		test_cmp expected actual.clean
-	'
-
-	# Automatically test "contents:size" atom after testing "contents"
-	if test "$format" = "contents"
-	then
-		# for commit leg, $3 is changed there
-		expect=$(printf '%s' "$3" | wc -c)
-		$test_do $PREREQ "basic atom: $ref contents:size" '
-			type=$(git cat-file -t "$ref") &&
-			case $type in
-			tag)
-				# We cannot use $3 as it expects sanitize_pgp to run
-				git cat-file tag $ref >out &&
-				expect=$(tail -n +6 out | wc -c) &&
-				rm -f out ;;
-			tree | blob)
-				expect="" ;;
-			commit)
-				: "use the calculated expect" ;;
-			*)
-				BUG "unknown object type" ;;
-			esac &&
-			# Leave $expect unquoted to lose possible leading whitespaces
-			echo $expect >expected &&
-			git for-each-ref --format="%(contents:size)" "$ref" >actual &&
-			test_cmp expected actual
-		'
-	fi
-}
-
-hexlen=$(test_oid hexsz)
-
-test_atom head refname refs/heads/main
-test_atom head refname: refs/heads/main
-test_atom head refname:short main
-test_atom head refname:lstrip=1 heads/main
-test_atom head refname:lstrip=2 main
-test_atom head refname:lstrip=-1 main
-test_atom head refname:lstrip=-2 heads/main
-test_atom head refname:rstrip=1 refs/heads
-test_atom head refname:rstrip=2 refs
-test_atom head refname:rstrip=-1 refs
-test_atom head refname:rstrip=-2 refs/heads
-test_atom head refname:strip=1 heads/main
-test_atom head refname:strip=2 main
-test_atom head refname:strip=-1 main
-test_atom head refname:strip=-2 heads/main
-test_atom head upstream refs/remotes/origin/main
-test_atom head upstream:short origin/main
-test_atom head upstream:lstrip=2 origin/main
-test_atom head upstream:lstrip=-2 origin/main
-test_atom head upstream:rstrip=2 refs/remotes
-test_atom head upstream:rstrip=-2 refs/remotes
-test_atom head upstream:strip=2 origin/main
-test_atom head upstream:strip=-2 origin/main
-test_atom head push refs/remotes/myfork/main
-test_atom head push:short myfork/main
-test_atom head push:lstrip=1 remotes/myfork/main
-test_atom head push:lstrip=-1 main
-test_atom head push:rstrip=1 refs/remotes/myfork
-test_atom head push:rstrip=-1 refs
-test_atom head push:strip=1 remotes/myfork/main
-test_atom head push:strip=-1 main
-test_atom head objecttype commit
-test_atom head objectsize $((131 + hexlen))
-test_atom head objectsize:disk $(test_object_file_size refs/heads/main)
-test_atom head deltabase $ZERO_OID
-test_atom head objectname $(git rev-parse refs/heads/main)
-test_atom head objectname:short $(git rev-parse --short refs/heads/main)
-test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
-test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
-test_atom head tree $(git rev-parse refs/heads/main^{tree})
-test_atom head tree:short $(git rev-parse --short refs/heads/main^{tree})
-test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/main^{tree})
-test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/main^{tree})
-test_atom head parent ''
-test_atom head parent:short ''
-test_atom head parent:short=1 ''
-test_atom head parent:short=10 ''
-test_atom head numparent 0
-test_atom head object ''
-test_atom head type ''
-test_atom head raw "$(git cat-file commit refs/heads/main)
-"
-test_atom head '*objectname' ''
-test_atom head '*objecttype' ''
-test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
-test_atom head authorname 'A U Thor'
-test_atom head authorname:mailmap 'A Thor'
-test_atom head authoremail '<author@example.com>'
-test_atom head authoremail:trim 'author@example.com'
-test_atom head authoremail:localpart 'author'
-test_atom head authoremail:trim,localpart 'author'
-test_atom head authoremail:mailmap '<athor@example.com>'
-test_atom head authoremail:mailmap,trim 'athor@example.com'
-test_atom head authoremail:trim,mailmap 'athor@example.com'
-test_atom head authoremail:mailmap,localpart 'athor'
-test_atom head authoremail:localpart,mailmap 'athor'
-test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor'
-test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
-test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
-test_atom head committername 'C O Mitter'
-test_atom head committername:mailmap 'C Mitter'
-test_atom head committeremail '<committer@example.com>'
-test_atom head committeremail:trim 'committer@example.com'
-test_atom head committeremail:localpart 'committer'
-test_atom head committeremail:localpart,trim 'committer'
-test_atom head committeremail:mailmap '<cmitter@example.com>'
-test_atom head committeremail:mailmap,trim 'cmitter@example.com'
-test_atom head committeremail:trim,mailmap 'cmitter@example.com'
-test_atom head committeremail:mailmap,localpart 'cmitter'
-test_atom head committeremail:localpart,mailmap 'cmitter'
-test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter'
-test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
-test_atom head tag ''
-test_atom head tagger ''
-test_atom head taggername ''
-test_atom head taggeremail ''
-test_atom head taggeremail:trim ''
-test_atom head taggeremail:localpart ''
-test_atom head taggerdate ''
-test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
-test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
-test_atom head subject 'Initial'
-test_atom head subject:sanitize 'Initial'
-test_atom head contents:subject 'Initial'
-test_atom head body ''
-test_atom head contents:body ''
-test_atom head contents:signature ''
-test_atom head contents 'Initial
-'
-test_atom head HEAD '*'
-
-test_atom tag refname refs/tags/testtag
-test_atom tag refname:short testtag
-test_atom tag upstream ''
-test_atom tag push ''
-test_atom tag objecttype tag
-test_atom tag objectsize $((114 + hexlen))
-test_atom tag objectsize:disk $(test_object_file_size refs/tags/testtag)
-test_atom tag '*objectsize:disk' $(test_object_file_size refs/heads/main)
-test_atom tag deltabase $ZERO_OID
-test_atom tag '*deltabase' $ZERO_OID
-test_atom tag objectname $(git rev-parse refs/tags/testtag)
-test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
-test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
-test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
-test_atom tag tree ''
-test_atom tag tree:short ''
-test_atom tag tree:short=1 ''
-test_atom tag tree:short=10 ''
-test_atom tag parent ''
-test_atom tag parent:short ''
-test_atom tag parent:short=1 ''
-test_atom tag parent:short=10 ''
-test_atom tag numparent ''
-test_atom tag object $(git rev-parse refs/tags/testtag^0)
-test_atom tag type 'commit'
-test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{})
-test_atom tag '*objecttype' 'commit'
-test_atom tag author ''
-test_atom tag authorname ''
-test_atom tag authorname:mailmap ''
-test_atom tag authoremail ''
-test_atom tag authoremail:trim ''
-test_atom tag authoremail:localpart ''
-test_atom tag authoremail:trim,localpart ''
-test_atom tag authoremail:mailmap ''
-test_atom tag authoremail:mailmap,trim ''
-test_atom tag authoremail:trim,mailmap ''
-test_atom tag authoremail:mailmap,localpart ''
-test_atom tag authoremail:localpart,mailmap ''
-test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim ''
-test_atom tag authordate ''
-test_atom tag committer ''
-test_atom tag committername ''
-test_atom tag committername:mailmap ''
-test_atom tag committeremail ''
-test_atom tag committeremail:trim ''
-test_atom tag committeremail:localpart ''
-test_atom tag committeremail:localpart,trim ''
-test_atom tag committeremail:mailmap ''
-test_atom tag committeremail:mailmap,trim ''
-test_atom tag committeremail:trim,mailmap ''
-test_atom tag committeremail:mailmap,localpart ''
-test_atom tag committeremail:localpart,mailmap ''
-test_atom tag committeremail:trim,mailmap,trim,trim,localpart ''
-test_atom tag committerdate ''
-test_atom tag tag 'testtag'
-test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
-test_atom tag taggername 'C O Mitter'
-test_atom tag taggername:mailmap 'C Mitter'
-test_atom tag taggeremail '<committer@example.com>'
-test_atom tag taggeremail:trim 'committer@example.com'
-test_atom tag taggeremail:localpart 'committer'
-test_atom tag taggeremail:trim,localpart 'committer'
-test_atom tag taggeremail:mailmap '<cmitter@example.com>'
-test_atom tag taggeremail:mailmap,trim 'cmitter@example.com'
-test_atom tag taggeremail:trim,mailmap 'cmitter@example.com'
-test_atom tag taggeremail:mailmap,localpart 'cmitter'
-test_atom tag taggeremail:localpart,mailmap 'cmitter'
-test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter'
-test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
-test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
-test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
-test_atom tag subject 'Tagging at 1151968727'
-test_atom tag subject:sanitize 'Tagging-at-1151968727'
-test_atom tag contents:subject 'Tagging at 1151968727'
-test_atom tag body ''
-test_atom tag contents:body ''
-test_atom tag contents:signature ''
-test_atom tag contents 'Tagging at 1151968727
-'
-test_atom tag HEAD ' '
-
-test_expect_success 'basic atom: refs/tags/testtag *raw' '
-	git cat-file commit refs/tags/testtag^{} >expected &&
-	git for-each-ref --format="%(*raw)" refs/tags/testtag >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_expect_success 'Check invalid atoms names are errors' '
-	test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
-'
-
-test_expect_success 'for-each-ref does not crash with -h' '
+test_expect_success "for-each-ref does not crash with -h" '
 	test_expect_code 129 git for-each-ref -h >usage &&
 	test_grep "[Uu]sage: git for-each-ref " usage &&
 	test_expect_code 129 nongit git for-each-ref -h >usage &&
 	test_grep "[Uu]sage: git for-each-ref " usage
 '
 
-test_expect_success 'Check format specifiers are ignored in naming date atoms' '
-	git for-each-ref --format="%(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate) %(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate:default)" refs/heads
-'
-
-test_expect_success 'Check valid format specifiers for date fields' '
-	git for-each-ref --format="%(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:relative)" refs/heads &&
-	git for-each-ref --format="%(authordate:short)" refs/heads &&
-	git for-each-ref --format="%(authordate:local)" refs/heads &&
-	git for-each-ref --format="%(authordate:iso8601)" refs/heads &&
-	git for-each-ref --format="%(authordate:rfc2822)" refs/heads
-'
-
-test_expect_success 'Check invalid format specifiers are errors' '
-	test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads
-'
-
-test_expect_success 'arguments to %(objectname:short=) must be positive integers' '
-	test_must_fail git for-each-ref --format="%(objectname:short=0)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=-1)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=foo)"
-'
-
-test_bad_atom () {
-	case "$1" in
-	head) ref=refs/heads/main ;;
-	 tag) ref=refs/tags/testtag ;;
-	 sym) ref=refs/heads/sym ;;
-	   *) ref=$1 ;;
-	esac
-	format=$2
-	test_do=test_expect_${4:-success}
-
-	printf '%s\n' "$3" >expect
-	$test_do $PREREQ "err basic atom: $ref $format" '
-		test_must_fail git for-each-ref \
-			--format="%($format)" "$ref" 2>error &&
-		test_cmp expect error
-	'
-}
-
-test_bad_atom head 'authoremail:foo' \
-	'fatal: unrecognized %(authoremail) argument: foo'
-
-test_bad_atom head 'authoremail:mailmap,trim,bar' \
-	'fatal: unrecognized %(authoremail) argument: bar'
-
-test_bad_atom head 'authoremail:trim,' \
-	'fatal: unrecognized %(authoremail) argument: '
-
-test_bad_atom head 'authoremail:mailmaptrim' \
-	'fatal: unrecognized %(authoremail) argument: trim'
-
-test_bad_atom head 'committeremail: ' \
-	'fatal: unrecognized %(committeremail) argument:  '
-
-test_bad_atom head 'committeremail: trim,foo' \
-	'fatal: unrecognized %(committeremail) argument:  trim,foo'
-
-test_bad_atom head 'committeremail:mailmap,localpart ' \
-	'fatal: unrecognized %(committeremail) argument:  '
-
-test_bad_atom head 'committeremail:trim_localpart' \
-	'fatal: unrecognized %(committeremail) argument: _localpart'
-
-test_bad_atom head 'committeremail:localpart,,,trim' \
-	'fatal: unrecognized %(committeremail) argument: ,,trim'
-
-test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \
-	'fatal: unrecognized %(taggeremail) argument:  foo '
-
-test_bad_atom tag 'taggeremail:trim,localpart,' \
-	'fatal: unrecognized %(taggeremail) argument: '
-
-test_bad_atom tag 'taggeremail:mailmap;localpart trim' \
-	'fatal: unrecognized %(taggeremail) argument: ;localpart trim'
-
-test_bad_atom tag 'taggeremail:localpart trim' \
-	'fatal: unrecognized %(taggeremail) argument:  trim'
-
-test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \
-	'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim'
-
-test_date () {
-	f=$1 &&
-	committer_date=$2 &&
-	author_date=$3 &&
-	tagger_date=$4 &&
-	cat >expected <<-EOF &&
-	'refs/heads/main' '$committer_date' '$author_date'
-	'refs/tags/testtag' '$tagger_date'
-	EOF
-	(
-		git for-each-ref --shell \
-			--format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
-			refs/heads &&
-		git for-each-ref --shell \
-			--format="%(refname) %(taggerdate${f:+:$f})" \
-			refs/tags
-	) >actual &&
-	test_cmp expected actual
-}
-
-test_expect_success 'Check unformatted date fields output' '
-	test_date "" \
-		"Tue Jul 4 01:18:43 2006 +0200" \
-		"Tue Jul 4 01:18:44 2006 +0200" \
-		"Tue Jul 4 01:18:45 2006 +0200"
-'
-
-test_expect_success 'Check format "default" formatted date fields output' '
-	test_date default \
-		"Tue Jul 4 01:18:43 2006 +0200" \
-		"Tue Jul 4 01:18:44 2006 +0200" \
-		"Tue Jul 4 01:18:45 2006 +0200"
-'
-
-test_expect_success 'Check format "default-local" date fields output' '
-	test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006"
-'
-
-# Don't know how to do relative check because I can't know when this script
-# is going to be run and can't fake the current time to git, and hence can't
-# provide expected output.  Instead, I'll just make sure that "relative"
-# doesn't exit in error
-test_expect_success 'Check format "relative" date fields output' '
-	f=relative &&
-	(git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-	git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
-'
-
-# We just check that this is the same as "relative" for now.
-test_expect_success 'Check format "relative-local" date fields output' '
-	test_date relative-local \
-		"$(git for-each-ref --format="%(committerdate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(authordate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(taggerdate:relative)" refs/tags)"
-'
-
-test_expect_success 'Check format "short" date fields output' '
-	test_date short 2006-07-04 2006-07-04 2006-07-04
-'
-
-test_expect_success 'Check format "short-local" date fields output' '
-	test_date short-local 2006-07-03 2006-07-03 2006-07-03
-'
-
-test_expect_success 'Check format "local" date fields output' '
-	test_date local \
-		"Mon Jul 3 23:18:43 2006" \
-		"Mon Jul 3 23:18:44 2006" \
-		"Mon Jul 3 23:18:45 2006"
-'
-
-test_expect_success 'Check format "iso8601" date fields output' '
-	test_date iso8601 \
-		"2006-07-04 01:18:43 +0200" \
-		"2006-07-04 01:18:44 +0200" \
-		"2006-07-04 01:18:45 +0200"
-'
-
-test_expect_success 'Check format "iso8601-local" date fields output' '
-	test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000"
-'
-
-test_expect_success 'Check format "rfc2822" date fields output' '
-	test_date rfc2822 \
-		"Tue, 4 Jul 2006 01:18:43 +0200" \
-		"Tue, 4 Jul 2006 01:18:44 +0200" \
-		"Tue, 4 Jul 2006 01:18:45 +0200"
-'
-
-test_expect_success 'Check format "rfc2822-local" date fields output' '
-	test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000"
-'
-
-test_expect_success 'Check format "raw" date fields output' '
-	test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200"
-'
-
-test_expect_success 'Check format "raw-local" date fields output' '
-	test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000"
-'
-
-test_expect_success 'Check format of strftime date fields' '
-	echo "my date is 2006-07-04" >expected &&
-	git for-each-ref \
-	  --format="%(authordate:format:my date is %Y-%m-%d)" \
-	  refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Check format of strftime-local date fields' '
-	echo "my date is 2006-07-03" >expected &&
-	git for-each-ref \
-	  --format="%(authordate:format-local:my date is %Y-%m-%d)" \
-	  refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'exercise strftime with odd fields' '
-	echo >expected &&
-	git for-each-ref --format="%(authordate:format:)" refs/heads >actual &&
-	test_cmp expected actual &&
-	long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" &&
-	echo $long >expected &&
-	git for-each-ref --format="%(authordate:format:$long)" refs/heads >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/heads/main
-refs/remotes/origin/main
-refs/tags/testtag
-EOF
-
-test_expect_success 'Verify ascending sort' '
-	git for-each-ref --format="%(refname)" --sort=refname >actual &&
-	test_cmp expected actual
-'
-
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/remotes/origin/main
-refs/heads/main
-EOF
-
-test_expect_success 'Verify descending sort' '
-	git for-each-ref --format="%(refname)" --sort=-refname >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Give help even with invalid sort atoms' '
-	test_expect_code 129 git for-each-ref --sort=bogus -h >actual 2>&1 &&
-	grep "^usage: git for-each-ref" actual
-'
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/tags/testtag-2
-EOF
-
-test_expect_success 'exercise patterns with prefixes' '
-	git tag testtag-2 &&
-	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/testtag refs/tags/testtag-2 >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/tags/testtag-2
-EOF
-
-test_expect_success 'exercise glob patterns with prefixes' '
-	git tag testtag-2 &&
-	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/testtag "refs/tags/testtag-*" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/bar
-refs/tags/baz
-refs/tags/testtag
-EOF
-
-test_expect_success 'exercise patterns with prefix exclusions' '
-	for tag in foo/one foo/two foo/three bar baz
-	do
-		git tag "$tag" || return 1
-	done &&
-	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/ --exclude=refs/tags/foo >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/bar
-refs/tags/baz
-refs/tags/foo/one
-refs/tags/testtag
-EOF
-
-test_expect_success 'exercise patterns with pattern exclusions' '
-	for tag in foo/one foo/two foo/three bar baz
-	do
-		git tag "$tag" || return 1
-	done &&
-	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/ --exclude="refs/tags/foo/t*" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-'refs/heads/main'
-'refs/remotes/origin/main'
-'refs/tags/testtag'
-EOF
-
-test_expect_success 'Quoting style: shell' '
-	git for-each-ref --shell --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Quoting style: perl' '
-	git for-each-ref --perl --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Quoting style: python' '
-	git for-each-ref --python --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-"refs/heads/main"
-"refs/remotes/origin/main"
-"refs/tags/testtag"
-EOF
-
-test_expect_success 'Quoting style: tcl' '
-	git for-each-ref --tcl --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
-	test_expect_success "more than one quoting style: $i" "
-		test_must_fail git for-each-ref $i 2>err &&
-		grep '^error: more than one quoting style' err
-	"
-done
-
-test_expect_success 'setup for upstream:track[short]' '
-	test_commit two
-'
-
-test_atom head upstream:track '[ahead 1]'
-test_atom head upstream:trackshort '>'
-test_atom head upstream:track,nobracket 'ahead 1'
-test_atom head upstream:nobracket,track 'ahead 1'
-
-test_expect_success 'setup for push:track[short]' '
-	test_commit third &&
-	git update-ref refs/remotes/myfork/main main &&
-	git reset main~1
-'
-
-test_atom head push:track '[behind 1]'
-test_atom head push:trackshort '<'
-
-test_expect_success 'Check that :track[short] cannot be used with other atoms' '
-	test_must_fail git for-each-ref --format="%(refname:track)" 2>/dev/null &&
-	test_must_fail git for-each-ref --format="%(refname:trackshort)" 2>/dev/null
-'
-
-test_expect_success 'Check that :track[short] works when upstream is invalid' '
-	cat >expected <<-\EOF &&
-	[gone]
-
-	EOF
-	test_when_finished "git config branch.main.merge refs/heads/main" &&
-	git config branch.main.merge refs/heads/does-not-exist &&
-	git for-each-ref \
-		--format="%(upstream:track)$LF%(upstream:trackshort)" \
-		refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Check for invalid refname format' '
-	test_must_fail git for-each-ref --format="%(refname:INVALID)"
-'
-
-test_expect_success 'set up color tests' '
-	cat >expected.color <<-EOF &&
-	$(git rev-parse --short refs/heads/main) <GREEN>main<RESET>
-	$(git rev-parse --short refs/remotes/myfork/main) <GREEN>myfork/main<RESET>
-	$(git rev-parse --short refs/remotes/origin/main) <GREEN>origin/main<RESET>
-	$(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET>
-	$(git rev-parse --short refs/tags/third) <GREEN>third<RESET>
-	$(git rev-parse --short refs/tags/two) <GREEN>two<RESET>
-	EOF
-	sed "s/<[^>]*>//g" <expected.color >expected.bare &&
-	color_format="%(objectname:short) %(color:green)%(refname:short)"
-'
-
-test_expect_success TTY '%(color) shows color with a tty' '
-	test_terminal git for-each-ref --format="$color_format" >actual.raw &&
-	test_decode_color <actual.raw >actual &&
-	test_cmp expected.color actual
-'
-
-test_expect_success '%(color) does not show color without tty' '
-	TERM=vt100 git for-each-ref --format="$color_format" >actual &&
-	test_cmp expected.bare actual
-'
-
-test_expect_success '--color can override tty check' '
-	git for-each-ref --color --format="$color_format" >actual.raw &&
-	test_decode_color <actual.raw >actual &&
-	test_cmp expected.color actual
-'
-
-test_expect_success 'color.ui=always does not override tty check' '
-	git -c color.ui=always for-each-ref --format="$color_format" >actual &&
-	test_cmp expected.bare actual
-'
-
-test_expect_success 'setup for describe atom tests' '
-	git init -b master describe-repo &&
-	(
-		cd describe-repo &&
-
-		test_commit --no-tag one &&
-		git tag tagone &&
-
-		test_commit --no-tag two &&
-		git tag -a -m "tag two" tagtwo
-	)
-'
-
-test_expect_success 'describe atom vs git describe' '
-	(
-		cd describe-repo &&
-
-		git for-each-ref --format="%(objectname)" \
-			refs/tags/ >obj &&
-		while read hash
-		do
-			if desc=$(git describe $hash)
-			then
-				: >expect-contains-good
-			else
-				: >expect-contains-bad
-			fi &&
-			echo "$hash $desc" || return 1
-		done <obj >expect &&
-		test_path_exists expect-contains-good &&
-		test_path_exists expect-contains-bad &&
-
-		git for-each-ref --format="%(objectname) %(describe)" \
-			refs/tags/ >actual 2>err &&
-		test_cmp expect actual &&
-		test_must_be_empty err
-	)
-'
-
-test_expect_success 'describe:tags vs describe --tags' '
-	(
-		cd describe-repo &&
-		git describe --tags >expect &&
-		git for-each-ref --format="%(describe:tags)" \
-				refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
-	(
-		cd describe-repo &&
-
-		# Case 1: We have commits between HEAD and the most
-		#	  recent tag reachable from it
-		test_commit --no-tag file &&
-		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
-			refs/heads/master >actual &&
-		test_cmp expect actual &&
-
-		# Make sure the hash used is at least 14 digits long
-		sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart &&
-		test 15 -le $(wc -c <hexpart) &&
-
-		# Case 2: We have a tag at HEAD, describe directly gives
-		#	  the name of the tag
-		git tag -a -m tagged tagname &&
-		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
-			refs/heads/master >actual &&
-		test_cmp expect actual &&
-		test tagname = $(cat actual)
-	)
-'
-
-test_expect_success 'describe:match=... vs describe --match ...' '
-	(
-		cd describe-repo &&
-		git tag -a -m "tag foo" tag-foo &&
-		git describe --match "*-foo" >expect &&
-		git for-each-ref --format="%(describe:match="*-foo")" \
-			refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'describe:exclude:... vs describe --exclude ...' '
-	(
-		cd describe-repo &&
-		git tag -a -m "tag bar" tag-bar &&
-		git describe --exclude "*-bar" >expect &&
-		git for-each-ref --format="%(describe:exclude="*-bar")" \
-			refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'deref with describe atom' '
-	(
-		cd describe-repo &&
-		cat >expect <<-\EOF &&
-
-		tagname
-		tagname
-		tagname
-
-		tagtwo
-		EOF
-		git for-each-ref --format="%(*describe)" >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'err on bad describe atom arg' '
-	(
-		cd describe-repo &&
-
-		# The bad arg is the only arg passed to describe atom
-		cat >expect <<-\EOF &&
-		fatal: unrecognized %(describe) argument: baz
-		EOF
-		test_must_fail git for-each-ref --format="%(describe:baz)" \
-			refs/heads/master 2>actual &&
-		test_cmp expect actual &&
-
-		# The bad arg is in the middle of the option string
-		# passed to the describe atom
-		cat >expect <<-\EOF &&
-		fatal: unrecognized %(describe) argument: qux=1,abbrev=14
-		EOF
-		test_must_fail git for-each-ref \
-			--format="%(describe:tags,qux=1,abbrev=14)" \
-			ref/heads/master 2>actual &&
-		test_cmp expect actual
-	)
-'
-
-cat >expected <<\EOF
-heads/main
-tags/main
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs (strict)' '
-	git config --bool core.warnambiguousrefs true &&
-	git checkout -b newtag &&
-	echo "Using $datestamp" > one &&
-	git add one &&
-	git commit -m "Branch" &&
-	setdate_and_increment &&
-	git tag -m "Tagging at $datestamp" main &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-heads/main
-main
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs (loose)' '
-	git config --bool core.warnambiguousrefs false &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-heads/ambiguous
-ambiguous
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs II (loose)' '
-	git checkout main &&
-	git tag ambiguous testtag^0 &&
-	git branch ambiguous testtag^0 &&
-	git for-each-ref --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'create tag without tagger' '
-	git tag -a -m "Broken tag" taggerless &&
-	git tag -f taggerless $(git cat-file tag taggerless |
-		sed -e "/^tagger /d" |
-		git hash-object --literally --stdin -w -t tag)
-'
-
-test_atom refs/tags/taggerless type 'commit'
-test_atom refs/tags/taggerless tag 'taggerless'
-test_atom refs/tags/taggerless tagger ''
-test_atom refs/tags/taggerless taggername ''
-test_atom refs/tags/taggerless taggeremail ''
-test_atom refs/tags/taggerless taggeremail:trim ''
-test_atom refs/tags/taggerless taggeremail:localpart ''
-test_atom refs/tags/taggerless taggerdate ''
-test_atom refs/tags/taggerless committer ''
-test_atom refs/tags/taggerless committername ''
-test_atom refs/tags/taggerless committeremail ''
-test_atom refs/tags/taggerless committeremail:trim ''
-test_atom refs/tags/taggerless committeremail:localpart ''
-test_atom refs/tags/taggerless committerdate ''
-test_atom refs/tags/taggerless subject 'Broken tag'
-
-test_expect_success 'an unusual tag with an incomplete line' '
-
-	git tag -m "bogo" bogo &&
-	bogo=$(git cat-file tag bogo) &&
-	bogo=$(printf "%s" "$bogo" | git mktag) &&
-	git tag -f bogo "$bogo" &&
-	git for-each-ref --format "%(body)" refs/tags/bogo
-
-'
-
-test_expect_success 'create tag with subject and body content' '
-	cat >>msg <<-\EOF &&
-		the subject line
-
-		first body line
-		second body line
-	EOF
-	git tag -F msg subject-body
-'
-test_atom refs/tags/subject-body subject 'the subject line'
-test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
-test_atom refs/tags/subject-body body 'first body line
-second body line
-'
-test_atom refs/tags/subject-body contents 'the subject line
-
-first body line
-second body line
-'
-
-test_expect_success 'create tag with multiline subject' '
-	cat >msg <<-\EOF &&
-		first subject line
-		second subject line
-
-		first body line
-		second body line
-	EOF
-	git tag -F msg multiline
-'
-test_atom refs/tags/multiline subject 'first subject line second subject line'
-test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
-test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
-test_atom refs/tags/multiline body 'first body line
-second body line
-'
-test_atom refs/tags/multiline contents:body 'first body line
-second body line
-'
-test_atom refs/tags/multiline contents:signature ''
-test_atom refs/tags/multiline contents 'first subject line
-second subject line
-
-first body line
-second body line
-'
-
-test_expect_success GPG 'create signed tags' '
-	git tag -s -m "" signed-empty &&
-	git tag -s -m "subject line" signed-short &&
-	cat >msg <<-\EOF &&
-	subject line
-
-	body contents
-	EOF
-	git tag -s -F msg signed-long
-'
-
-sig='-----BEGIN PGP SIGNATURE-----
------END PGP SIGNATURE-----
-'
-
-PREREQ=GPG
-test_atom refs/tags/signed-empty subject ''
-test_atom refs/tags/signed-empty subject:sanitize ''
-test_atom refs/tags/signed-empty contents:subject ''
-test_atom refs/tags/signed-empty body "$sig"
-test_atom refs/tags/signed-empty contents:body ''
-test_atom refs/tags/signed-empty contents:signature "$sig"
-test_atom refs/tags/signed-empty contents "$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
-	git cat-file tag refs/tags/signed-empty >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-empty >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_atom refs/tags/signed-short subject 'subject line'
-test_atom refs/tags/signed-short subject:sanitize 'subject-line'
-test_atom refs/tags/signed-short contents:subject 'subject line'
-test_atom refs/tags/signed-short body "$sig"
-test_atom refs/tags/signed-short contents:body ''
-test_atom refs/tags/signed-short contents:signature "$sig"
-test_atom refs/tags/signed-short contents "subject line
-$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
-	git cat-file tag refs/tags/signed-short >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-short >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_atom refs/tags/signed-long subject 'subject line'
-test_atom refs/tags/signed-long subject:sanitize 'subject-line'
-test_atom refs/tags/signed-long contents:subject 'subject line'
-test_atom refs/tags/signed-long body "body contents
-$sig"
-test_atom refs/tags/signed-long contents:body 'body contents
-'
-test_atom refs/tags/signed-long contents:signature "$sig"
-test_atom refs/tags/signed-long contents "subject line
-
-body contents
-$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
-	git cat-file tag refs/tags/signed-long >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-long >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_expect_success 'set up refs pointing to tree and blob' '
-	git update-ref refs/mytrees/first refs/heads/main^{tree} &&
-	git update-ref refs/myblobs/first refs/heads/main:one
-'
-
-test_atom refs/mytrees/first subject ""
-test_atom refs/mytrees/first contents:subject ""
-test_atom refs/mytrees/first body ""
-test_atom refs/mytrees/first contents:body ""
-test_atom refs/mytrees/first contents:signature ""
-test_atom refs/mytrees/first contents ""
-
-test_expect_success 'basic atom: refs/mytrees/first raw' '
-	git cat-file tree refs/mytrees/first >expected &&
-	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/mytrees/first >actual &&
-	test_cmp expected actual &&
-	git cat-file -s refs/mytrees/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_atom refs/myblobs/first subject ""
-test_atom refs/myblobs/first contents:subject ""
-test_atom refs/myblobs/first body ""
-test_atom refs/myblobs/first contents:body ""
-test_atom refs/myblobs/first contents:signature ""
-test_atom refs/myblobs/first contents ""
-
-test_expect_success 'basic atom: refs/myblobs/first raw' '
-	git cat-file blob refs/myblobs/first >expected &&
-	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/myblobs/first >actual &&
-	test_cmp expected actual &&
-	git cat-file -s refs/myblobs/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/myblobs/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'set up refs pointing to binary blob' '
-	printf "a\0b\0c" >blob1 &&
-	printf "a\0c\0b" >blob2 &&
-	printf "\0a\0b\0c" >blob3 &&
-	printf "abc" >blob4 &&
-	printf "\0 \0 \0 " >blob5 &&
-	printf "\0 \0a\0 " >blob6 &&
-	printf "  " >blob7 &&
-	>blob8 &&
-	obj=$(git hash-object -w blob1) &&
-	git update-ref refs/myblobs/blob1 "$obj" &&
-	obj=$(git hash-object -w blob2) &&
-	git update-ref refs/myblobs/blob2 "$obj" &&
-	obj=$(git hash-object -w blob3) &&
-	git update-ref refs/myblobs/blob3 "$obj" &&
-	obj=$(git hash-object -w blob4) &&
-	git update-ref refs/myblobs/blob4 "$obj" &&
-	obj=$(git hash-object -w blob5) &&
-	git update-ref refs/myblobs/blob5 "$obj" &&
-	obj=$(git hash-object -w blob6) &&
-	git update-ref refs/myblobs/blob6 "$obj" &&
-	obj=$(git hash-object -w blob7) &&
-	git update-ref refs/myblobs/blob7 "$obj" &&
-	obj=$(git hash-object -w blob8) &&
-	git update-ref refs/myblobs/blob8 "$obj"
-'
-
-test_expect_success 'Verify sorts with raw' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob8
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/blob3
-	refs/myblobs/blob7
-	refs/mytrees/first
-	refs/myblobs/first
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob4
-	refs/heads/main
-	EOF
-	git for-each-ref --format="%(refname)" --sort=raw \
-		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Verify sorts with raw:size' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob8
-	refs/myblobs/blob7
-	refs/myblobs/blob4
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob3
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/first
-	refs/mytrees/first
-	refs/heads/main
-	EOF
-	git for-each-ref --format="%(refname)" --sort=raw:size \
-		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'validate raw atom with %(if:equals)' '
-	cat >expected <<-EOF &&
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	refs/myblobs/blob4
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	EOF
-	git for-each-ref --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
-		refs/myblobs/ refs/heads/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'validate raw atom with %(if:notequals)' '
-	cat >expected <<-EOF &&
-	refs/heads/ambiguous
-	refs/heads/main
-	refs/heads/newtag
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob3
-	equals
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/blob7
-	refs/myblobs/blob8
-	refs/myblobs/first
-	EOF
-	git for-each-ref --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
-		refs/myblobs/ refs/heads/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'empty raw refs with %(if)' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob1 not empty
-	refs/myblobs/blob2 not empty
-	refs/myblobs/blob3 not empty
-	refs/myblobs/blob4 not empty
-	refs/myblobs/blob5 not empty
-	refs/myblobs/blob6 not empty
-	refs/myblobs/blob7 empty
-	refs/myblobs/blob8 empty
-	refs/myblobs/first not empty
-	EOF
-	git for-each-ref --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
-		refs/myblobs/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '%(raw) with --python must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --python
-'
-
-test_expect_success '%(raw) with --tcl must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --tcl
-'
-
-test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' '
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
-	cmp blob1 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
-	cmp blob3 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
-	cmp blob8 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
-	cmp one actual &&
-	git cat-file tree refs/mytrees/first > expected &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
-	cmp expected actual
-'
-
-test_expect_success '%(raw) with --shell must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --shell
-'
-
-test_expect_success '%(raw) with --shell and --sort=raw must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --sort=raw --shell
-'
-
-test_expect_success '%(raw:size) with --shell' '
-	git for-each-ref --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
-	git for-each-ref --format="%(raw:size)" --shell >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --format compare with cat-file --batch' '
-	git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
-	git for-each-ref --format="%(objectname) %(objecttype) %(objectsize)
-%(raw)" refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'verify sorts with contents:size' '
-	cat >expect <<-\EOF &&
-	refs/heads/main
-	refs/heads/newtag
-	refs/heads/ambiguous
-	EOF
-	git for-each-ref --format="%(refname)" \
-		--sort=contents:size refs/heads/ >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'set up multiple-sort tags' '
-	for when in 100000 200000
-	do
-		for email in user1 user2
-		do
-			for ref in ref1 ref2
-			do
-				GIT_COMMITTER_DATE="@$when +0000" \
-				GIT_COMMITTER_EMAIL="$email@example.com" \
-				git tag -m "tag $ref-$when-$email" \
-				multi-$ref-$when-$email || return 1
-			done
-		done
-	done
-'
-
-test_expect_success 'Verify sort with multiple keys' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=-refname \
-		--sort=taggeremail \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'equivalent sorts fall back on refname' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '--no-sort cancels the previous sort keys' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=-refname \
-		--sort=taggeremail \
-		--no-sort \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '--no-sort without subsequent --sort prints expected refs' '
-	cat >expected <<-\EOF &&
-	refs/tags/multi-ref1-100000-user1
-	refs/tags/multi-ref1-100000-user2
-	refs/tags/multi-ref1-200000-user1
-	refs/tags/multi-ref1-200000-user2
-	refs/tags/multi-ref2-100000-user1
-	refs/tags/multi-ref2-100000-user2
-	refs/tags/multi-ref2-200000-user1
-	refs/tags/multi-ref2-200000-user2
-	EOF
-
-	# Sort the results with `sort` for a consistent comparison against
-	# expected
-	git for-each-ref \
-		--format="%(refname)" \
-		--no-sort \
-		"refs/tags/multi-*" | sort >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'set up custom date sorting' '
-	# Dates:
-	# - Wed Feb 07 2024 21:34:20 +0000
-	# - Tue Dec 14 1999 00:05:22 +0000
-	# - Fri Jun 04 2021 11:26:51 +0000
-	# - Mon Jan 22 2007 16:44:01 GMT+0000
-	i=1 &&
-	for when in 1707341660 945129922 1622806011 1169484241
-	do
-		GIT_COMMITTER_DATE="@$when +0000" \
-		GIT_COMMITTER_EMAIL="user@example.com" \
-		git tag -m "tag $when" custom-dates-$i &&
-		i=$(($i+1)) || return 1
-	done
-'
-
-test_expect_success 'sort by date defaults to full timestamp' '
-	cat >expected <<-\EOF &&
-	945129922 refs/tags/custom-dates-2
-	1169484241 refs/tags/custom-dates-4
-	1622806011 refs/tags/custom-dates-3
-	1707341660 refs/tags/custom-dates-1
-	EOF
-
-	git for-each-ref \
-		--format="%(creatordate:unix) %(refname)" \
-		--sort=creatordate \
-		"refs/tags/custom-dates-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'sort by custom date format' '
-	cat >expected <<-\EOF &&
-	00:05:22 refs/tags/custom-dates-2
-	11:26:51 refs/tags/custom-dates-3
-	16:44:01 refs/tags/custom-dates-4
-	21:34:20 refs/tags/custom-dates-1
-	EOF
-
-	git for-each-ref \
-		--format="%(creatordate:format:%H:%M:%S) %(refname)" \
-		--sort="creatordate:format:%H:%M:%S" \
-		"refs/tags/custom-dates-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
-	test_when_finished "git checkout main" &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
-	sed -e "s/^\* /  /" actual >expect &&
-	git checkout --orphan orphaned-branch &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
-	test_cmp expect actual
-'
-
-cat >trailers <<EOF
-Reviewed-by: A U Thor <author@example.com>
-Signed-off-by: A U Thor <author@example.com>
-[ v2 updated patch description ]
-Acked-by: A U Thor
-  <author@example.com>
-EOF
-
-unfold () {
-	perl -0pe 's/\n\s+/ /g'
-}
-
-test_expect_success 'set up trailers for next test' '
-	echo "Some contents" > two &&
-	git add two &&
-	git commit -F - <<-EOF
-	trailers: this commit message has trailers
-
-	Some message contents
-
-	$(cat trailers)
-	EOF
-'
-
-test_trailer_option () {
-	if test "$#" -eq 3
-	then
-		prereq="$1"
-		shift
-	fi &&
-	title=$1 option=$2
-	cat >expect
-	test_expect_success $prereq "$title" '
-		git for-each-ref --format="%($option)" refs/heads/main >actual &&
-		test_cmp expect actual &&
-		git for-each-ref --format="%(contents:$option)" refs/heads/main >actual &&
-		test_cmp expect actual
-	'
-}
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' \
-	'trailers:unfold' <<-EOF
-	$(unfold <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only) shows only "key: value" trailers' \
-	'trailers:only' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=no,only=true) shows only "key: value" trailers' \
-	'trailers:only=no,only=true' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=yes) shows only "key: value" trailers' \
-	'trailers:only=yes' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=no) shows all trailers' \
-	'trailers:only=no' <<-EOF
-	$(cat trailers)
-
-	EOF
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:only) and %(trailers:unfold) work together' \
-	'trailers:only,unfold' <<-EOF
-	$(grep -v patch.description <trailers | unfold)
-
-	EOF
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) and %(trailers:only) work together' \
-	'trailers:unfold,only' <<-EOF
-	$(grep -v patch.description <trailers | unfold)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) shows that trailer' \
-	'trailers:key=Signed-off-by' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) is case insensitive' \
-	'trailers:key=SiGned-oFf-bY' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo:) trailing colon also works' \
-	'trailers:key=Signed-off-by:' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) multiple keys' \
-	'trailers:key=Reviewed-by:,key=Signed-off-by' <<-EOF
-	Reviewed-by: A U Thor <author@example.com>
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=nonexistent) becomes empty' \
-	'trailers:key=Shined-off-by:' <<-EOF
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) handles multiple lines even if folded' \
-	'trailers:key=Acked-by' <<-EOF
-	$(grep -v patch.description <trailers | grep -v Signed-off-by | grep -v Reviewed-by)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,unfold) properly unfolds' \
-	'trailers:key=Signed-Off-by,unfold' <<-EOF
-	$(unfold <trailers | grep Signed-off-by)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,only=no) also includes nontrailer lines' \
-	'trailers:key=Signed-off-by,only=no' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-	$(grep patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,valueonly) shows only value' \
-	'trailers:key=Signed-off-by,valueonly' <<-EOF
-	A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:separator) changes separator' \
-	'trailers:separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by: A U Thor <author@example.com>,Signed-off-by: A U Thor <author@example.com>
-	EOF
-
-test_trailer_option '%(trailers:key_value_separator) changes key-value separator' \
-	'trailers:key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by,A U Thor <author@example.com>
-	Signed-off-by,A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:separator,key_value_separator) changes both separators' \
-	'trailers:separator=%x2C,key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by,A U Thor <author@example.com>,Signed-off-by,A U Thor <author@example.com>
-	EOF
-
-test_expect_success 'multiple %(trailers) use their own options' '
-	git tag -F - tag-with-trailers <<-\EOF &&
-	body
-
-	one: foo
-	one: bar
-	two: baz
-	two: qux
-	EOF
-	t1="%(trailers:key=one,key_value_separator=W,separator=X)" &&
-	t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" &&
-	git for-each-ref --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
-	cat >expect <<-\EOF &&
-	oneWfooXoneWbar
-	twoYbazZtwoYqux
-	EOF
-	test_cmp expect actual
-'
-
-test_failing_trailer_option () {
-	title=$1 option=$2
-	cat >expect
-	test_expect_success "$title" '
-		# error message cannot be checked under i18n
-		test_must_fail git for-each-ref --format="%($option)" refs/heads/main 2>actual &&
-		test_cmp expect actual &&
-		test_must_fail git for-each-ref --format="%(contents:$option)" refs/heads/main 2>actual &&
-		test_cmp expect actual
-	'
-}
-
-test_failing_trailer_option '%(trailers) rejects unknown trailers arguments' \
-	'trailers:unsupported' <<-\EOF
-	fatal: unknown %(trailers) argument: unsupported
-	EOF
-
-test_failing_trailer_option '%(trailers:key) without value is error' \
-	'trailers:key' <<-\EOF
-	fatal: expected %(trailers:key=<value>)
-	EOF
-
-test_expect_success 'if arguments, %(contents:trailers) shows error if colon is missing' '
-	cat >expect <<-EOF &&
-	fatal: unrecognized %(contents) argument: trailersonly
-	EOF
-	test_must_fail git for-each-ref --format="%(contents:trailersonly)" 2>actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'basic atom: head contents:trailers' '
-	git for-each-ref --format="%(contents:trailers)" refs/heads/main >actual &&
-	sanitize_pgp <actual >actual.clean &&
-	# git for-each-ref ends with a blank line
-	cat >expect <<-EOF &&
-	$(cat trailers)
-
-	EOF
-	test_cmp expect actual.clean
-'
-
-test_expect_success 'basic atom: rest must fail' '
-	test_must_fail git for-each-ref --format="%(rest)" refs/heads/main
-'
-
-test_expect_success 'HEAD atom does not take arguments' '
-	test_must_fail git for-each-ref --format="%(HEAD:foo)" 2>err &&
-	echo "fatal: %(HEAD) does not take arguments" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'subject atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(subject:foo)" 2>err &&
-	echo "fatal: unrecognized %(subject) argument: foo" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'refname atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(refname:foo)" 2>err &&
-	echo "fatal: unrecognized %(refname) argument: foo" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'trailer parsing not fooled by --- line' '
-	git commit --allow-empty -F - <<-\EOF &&
-	this is the subject
-
-	This is the body. The message has a "---" line which would confuse a
-	message+patch parser. But here we know we have only a commit message,
-	so we get it right.
-
-	trailer: wrong
-	---
-	This is more body.
-
-	trailer: right
-	EOF
-
-	{
-		echo "trailer: right" &&
-		echo
-	} >expect &&
-	git for-each-ref --format="%(trailers)" refs/heads/main >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'Add symbolic ref for the following tests' '
-	git symbolic-ref refs/heads/sym refs/heads/main
-'
-
-cat >expected <<EOF
-refs/heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref) atom' '
-	git for-each-ref --format="%(symref)" refs/heads/sym >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref:short) atom' '
-	git for-each-ref --format="%(symref:short)" refs/heads/sym >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-main
-heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref:lstrip) atom' '
-	git for-each-ref --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual &&
-
-	git for-each-ref --format="%(symref:strip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-refs
-refs/heads
-EOF
-
-test_expect_success 'Verify usage of %(symref:rstrip) atom' '
-	git for-each-ref --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual
-'
-
-test_expect_success ':remotename and :remoteref' '
-	git init remote-tests &&
-	(
-		cd remote-tests &&
-		test_commit initial &&
-		git branch -M main &&
-		git remote add from fifth.coffee:blub &&
-		git config branch.main.remote from &&
-		git config branch.main.merge refs/heads/stable &&
-		git remote add to southridge.audio:repo &&
-		git config remote.to.push "refs/heads/*:refs/heads/pushed/*" &&
-		git config branch.main.pushRemote to &&
-		for pair in "%(upstream)=refs/remotes/from/stable" \
-			"%(upstream:remotename)=from" \
-			"%(upstream:remoteref)=refs/heads/stable" \
-			"%(push)=refs/remotes/to/pushed/main" \
-			"%(push:remotename)=to" \
-			"%(push:remoteref)=refs/heads/pushed/main"
-		do
-			echo "${pair#*=}" >expect &&
-			git for-each-ref --format="${pair%=*}" \
-				refs/heads/main >actual &&
-			test_cmp expect actual || exit 1
-		done &&
-		git branch push-simple &&
-		git config branch.push-simple.pushRemote from &&
-		actual="$(git for-each-ref \
-			--format="%(push:remotename),%(push:remoteref)" \
-			refs/heads/push-simple)" &&
-		test from, = "$actual"
-	)
-'
-
-test_expect_success 'for-each-ref --ignore-case ignores case' '
-	git for-each-ref --format="%(refname)" refs/heads/MAIN >actual &&
-	test_must_be_empty actual &&
-
-	echo refs/heads/main >expect &&
-	git for-each-ref --format="%(refname)" --ignore-case \
-		refs/heads/MAIN >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --omit-empty works' '
-	git for-each-ref --format="%(refname)" >actual &&
-	test_line_count -gt 1 actual &&
-	git for-each-ref --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
-	echo refs/heads/main >expect &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
-	# name refs numerically to avoid case-insensitive filesystem conflicts
-	nr=0 &&
-	for email in a A b B
-	do
-		for subject in a A b B
-		do
-			GIT_COMMITTER_EMAIL="$email@example.com" \
-			git tag -m "tag $subject" icase-$(printf %02d $nr) &&
-			nr=$((nr+1))||
-			return 1
-		done
-	done &&
-	git for-each-ref --ignore-case \
-		--format="%(taggeremail) %(subject) %(refname)" \
-		--sort=refname \
-		--sort=subject \
-		--sort=taggeremail \
-		refs/tags/icase-* >actual &&
-	cat >expect <<-\EOF &&
-	<a@example.com> tag a refs/tags/icase-00
-	<a@example.com> tag A refs/tags/icase-01
-	<A@example.com> tag a refs/tags/icase-04
-	<A@example.com> tag A refs/tags/icase-05
-	<a@example.com> tag b refs/tags/icase-02
-	<a@example.com> tag B refs/tags/icase-03
-	<A@example.com> tag b refs/tags/icase-06
-	<A@example.com> tag B refs/tags/icase-07
-	<b@example.com> tag a refs/tags/icase-08
-	<b@example.com> tag A refs/tags/icase-09
-	<B@example.com> tag a refs/tags/icase-12
-	<B@example.com> tag A refs/tags/icase-13
-	<b@example.com> tag b refs/tags/icase-10
-	<b@example.com> tag B refs/tags/icase-11
-	<B@example.com> tag b refs/tags/icase-14
-	<B@example.com> tag B refs/tags/icase-15
-	EOF
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref reports broken tags' '
-	git tag -m "good tag" broken-tag-good HEAD &&
-	git cat-file tag broken-tag-good >good &&
-	sed s/commit/blob/ <good >bad &&
-	bad=$(git hash-object -w -t tag bad) &&
-	git update-ref refs/tags/broken-tag-bad $bad &&
-	test_must_fail git for-each-ref --format="%(*objectname)" \
-		refs/tags/broken-tag-*
-'
-
-test_expect_success 'set up tag with signature and no blank lines' '
-	git tag -F - fake-sig-no-blanks <<-\EOF
-	this is the subject
-	-----BEGIN PGP SIGNATURE-----
-	not a real signature, but we just care about the
-	subject/body parsing. It is important here that
-	there are no blank lines in the signature.
-	-----END PGP SIGNATURE-----
-	EOF
-'
-
-test_atom refs/tags/fake-sig-no-blanks contents:subject 'this is the subject'
-test_atom refs/tags/fake-sig-no-blanks contents:body ''
-test_atom refs/tags/fake-sig-no-blanks contents:signature "$sig"
-
-test_expect_success 'set up tag with CRLF signature' '
-	append_cr <<-\EOF |
-	this is the subject
-	-----BEGIN PGP SIGNATURE-----
-
-	not a real signature, but we just care about
-	the subject/body parsing. It is important here
-	that there is a blank line separating this
-	from the signature header.
-	-----END PGP SIGNATURE-----
-	EOF
-	git tag -F - --cleanup=verbatim fake-sig-crlf
-'
-
-test_atom refs/tags/fake-sig-crlf contents:subject 'this is the subject'
-test_atom refs/tags/fake-sig-crlf contents:body ''
-
-# CRLF is retained in the signature, so we have to pass our expected value
-# through append_cr. But test_atom requires a shell string, which means command
-# substitution, and the shell will strip trailing newlines from the output of
-# the substitution. Hack around it by adding and then removing a dummy line.
-sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
-sig_crlf=${sig_crlf%dummy}
-test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
-
-test_expect_success 'set up tag with signature and trailers' '
-	git tag -F - fake-sig-trailer <<-\EOF
-	this is the subject
-
-	this is the body
-
-	My-Trailer: foo
-	-----BEGIN PGP SIGNATURE-----
-
-	not a real signature, but we just care about the
-	subject/body/trailer parsing.
-	-----END PGP SIGNATURE-----
-	EOF
-'
-
-# use "separator=" here to suppress the terminating newline
-test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo'
-
-test_expect_success 'git for-each-ref --stdin: empty' '
-	>in &&
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	git for-each-ref --format="%(refname)" >expect &&
-	test_cmp expect actual
-'
-
-test_expect_success 'git for-each-ref --stdin: fails if extra args' '
-	>in &&
-	test_must_fail git for-each-ref --format="%(refname)" \
-		--stdin refs/heads/extra <in 2>err &&
-	grep "unknown arguments supplied with --stdin" err
-'
-
-test_expect_success 'git for-each-ref --stdin: matches' '
-	cat >in <<-EOF &&
-	refs/tags/multi*
-	refs/heads/amb*
-	EOF
-
-	cat >expect <<-EOF &&
-	refs/heads/ambiguous
-	refs/tags/multi-ref1-100000-user1
-	refs/tags/multi-ref1-100000-user2
-	refs/tags/multi-ref1-200000-user1
-	refs/tags/multi-ref1-200000-user2
-	refs/tags/multi-ref2-100000-user1
-	refs/tags/multi-ref2-100000-user2
-	refs/tags/multi-ref2-200000-user1
-	refs/tags/multi-ref2-200000-user2
-	refs/tags/multiline
-	EOF
-
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'git for-each-ref with non-existing refs' '
-	cat >in <<-EOF &&
-	refs/heads/this-ref-does-not-exist
-	refs/tags/bogus
-	EOF
-
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	test_must_be_empty actual &&
-
-	xargs git for-each-ref --format="%(refname)" <in >actual &&
-	test_must_be_empty actual
-'
-
-test_expect_success 'git for-each-ref with nested tags' '
-	git tag -am "Normal tag" nested/base HEAD &&
-	git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
-	git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
-
-	head_oid="$(git rev-parse HEAD)" &&
-	base_tag_oid="$(git rev-parse refs/tags/nested/base)" &&
-	nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" &&
-	nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" &&
-
-	cat >expect <<-EOF &&
-	refs/tags/nested/base $base_tag_oid tag $head_oid commit
-	refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit
-	refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
-	EOF
-
-	git for-each-ref \
-		--format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
-		refs/tags/nested/ >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'is-base atom with non-commits' '
-	git for-each-ref --format="%(is-base:HEAD) %(refname)" >out 2>err &&
-	grep "(HEAD) refs/heads/main" out &&
-
-	test_line_count = 2 err &&
-	grep "error: object .* is a commit, not a blob" err &&
-	grep "error: bad tag pointer to" err
-'
-
-GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
-TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
-
-test_expect_success GPG 'setup for signature atom using gpg' '
-	git checkout -b signed &&
-
-	test_when_finished "test_unconfig commit.gpgSign" &&
-
-	echo "1" >file &&
-	git add file &&
-	test_tick &&
-	git commit -S -m "file: 1" &&
-	git tag first-signed &&
-
-	echo "2" >file &&
-	test_tick &&
-	git commit -a -m "file: 2" &&
-	git tag second-unsigned &&
-
-	git config commit.gpgSign 1 &&
-	echo "3" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 3" &&
-	git tag third-unsigned &&
-
-	test_tick &&
-	git rebase -f HEAD^^ && git tag second-signed HEAD^ &&
-	git tag third-signed &&
-
-	echo "4" >file &&
-	test_tick &&
-	git commit -a -SB7227189 -m "file: 4" &&
-	git tag fourth-signed &&
-
-	echo "5" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 5" &&
-	git tag fifth-unsigned &&
-
-	echo "6" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 6" &&
-
-	test_tick &&
-	git rebase -f HEAD^^ &&
-	git tag fifth-signed HEAD^ &&
-	git tag sixth-signed &&
-
-	echo "7" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 7" &&
-	git tag seventh-unsigned
-'
-
-test_expect_success GPGSSH 'setup for signature atom using ssh' '
-	test_when_finished "test_unconfig gpg.format user.signingkey" &&
-
-	test_config gpg.format ssh &&
-	test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
-	echo "8" >file &&
-	test_tick &&
-	git add file &&
-	git commit -S -m "file: 8" &&
-	git tag eighth-signed-ssh
-'
-
-test_expect_success GPG2 'bare signature atom' '
-	git verify-commit first-signed 2>expect &&
-	echo  >>expect &&
-	git for-each-ref refs/tags/first-signed \
-		--format="%(signature)" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show good signature with custom format' '
-	git verify-commit first-signed &&
-	cat >expect <<-\EOF &&
-	G
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	EOF
-	git for-each-ref refs/tags/first-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-test_expect_success GPGSSH 'show good signature with custom format with ssh' '
-	test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
-	FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
-	cat >expect.tmpl <<-\EOF &&
-	G
-	FINGERPRINT
-	principal with number 1
-	FINGERPRINT
-
-	EOF
-	sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
-	git for-each-ref refs/tags/eighth-signed-ssh \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'signature atom with grade option and bad signature' '
-	git cat-file commit third-signed >raw &&
-	sed -e "s/^file: 3/file: 3 forged/" raw >forged1 &&
-	FORGED1=$(git hash-object -w -t commit forged1) &&
-	git update-ref refs/tags/third-signed "$FORGED1" &&
-	test_must_fail git verify-commit "$FORGED1" &&
-
-	cat >expect <<-\EOF &&
-	B
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-
-
-	EOF
-	git for-each-ref refs/tags/third-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with custom format' '
-	cat >expect <<-\EOF &&
-	U
-	65A0EEA02E30CAD7
-	Eris Discordia <discord@example.net>
-	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
-	D4BE22311AD3131E5EDA29A461092E85B7227189
-	EOF
-	git for-each-ref refs/tags/fourth-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with undefined trust level' '
-	cat >expect <<-\EOF &&
-	undefined
-	65A0EEA02E30CAD7
-	Eris Discordia <discord@example.net>
-	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
-	D4BE22311AD3131E5EDA29A461092E85B7227189
-	EOF
-	git for-each-ref refs/tags/fourth-signed \
-		--format="$TRUSTLEVEL_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with ultimate trust level' '
-	cat >expect <<-\EOF &&
-	ultimate
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	EOF
-	git for-each-ref refs/tags/sixth-signed \
-		--format="$TRUSTLEVEL_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show unknown signature with custom format' '
-	cat >expect <<-\EOF &&
-	E
-	13B6F51ECDDE430D
-
-
-
-	EOF
-	GNUPGHOME="$GNUPGHOME_NOT_USED" git for-each-ref \
-		refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show lack of signature with custom format' '
-	cat >expect <<-\EOF &&
-	N
-
-
-
-
-	EOF
-	git for-each-ref refs/tags/seventh-unsigned \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
+. "$TEST_DIRECTORY"/for-each-ref-tests.sh
 
 test_done
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v4 5/5] t: add test for git refs list subcommand
  2025-07-31  9:00     ` [GSoC][RFC PATCH v4 0/5] Add " Meet Soni
                         ` (3 preceding siblings ...)
  2025-07-31  9:00       ` [GSoC][RFC PATCH v4 4/5] t6300: refactor tests to be shareable Meet Soni
@ 2025-07-31  9:00       ` Meet Soni
  2025-08-01  5:54       ` [GSoC][RFC PATCH v4 0/5] Add " Patrick Steinhardt
  2025-08-04  9:22       ` [GSoC][RFC PATCH v5 0/6] " Meet Soni
  6 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-07-31  9:00 UTC (permalink / raw)
  To: git; +Cc: ps, shejialuo, karthik.188, gitster, sunshine, Meet Soni
Add a test script, `t/t1461-refs-list.sh`, for the new `git refs list`
command.
This script acts as a simple driver, leveraging the shared test library
created in the preceding commit. It works by overriding the
`$git_for_each_ref` variable to "git refs list" and then sourcing the
shared library (`t/for-each-ref-tests.sh`).
This approach ensures that `git refs list` is tested against the
entire comprehensive test suite of `git for-each-ref`, verifying
that it acts as a compatible drop-in replacement.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 t/meson.build        | 1 +
 t/t1461-refs-list.sh | 8 ++++++++
 2 files changed, 9 insertions(+)
 create mode 100755 t/t1461-refs-list.sh
diff --git a/t/meson.build b/t/meson.build
index 50e89e764a..c959c039d0 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -224,6 +224,7 @@ integration_tests = [
   't1450-fsck.sh',
   't1451-fsck-buffer.sh',
   't1460-refs-migrate.sh',
+  't1461-refs-list.sh',
   't1500-rev-parse.sh',
   't1501-work-tree.sh',
   't1502-rev-parse-parseopt.sh',
diff --git a/t/t1461-refs-list.sh b/t/t1461-refs-list.sh
new file mode 100755
index 0000000000..36e3d81e59
--- /dev/null
+++ b/t/t1461-refs-list.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+test_description='git refs list tests'
+
+. ./test-lib.sh
+
+git_for_each_ref='git refs list'
+. "$TEST_DIRECTORY"/for-each-ref-tests.sh
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v4 2/5] builtin/for-each-ref: factor out core logic into a helper
  2025-07-31  9:00       ` [GSoC][RFC PATCH v4 2/5] builtin/for-each-ref: factor out core logic into a helper Meet Soni
@ 2025-08-01  5:54         ` Patrick Steinhardt
  2025-08-04  6:34           ` Meet Soni
  0 siblings, 1 reply; 65+ messages in thread
From: Patrick Steinhardt @ 2025-08-01  5:54 UTC (permalink / raw)
  To: Meet Soni
  Cc: git, shejialuo, karthik.188, gitster, sunshine,
	Ævar Arnfjörð Bjarmason, Victoria Dye
On Thu, Jul 31, 2025 at 02:30:37PM +0530, Meet Soni wrote:
> diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
> index 3d2207ec77..bbc0e5ad1c 100644
> --- a/builtin/for-each-ref.c
> +++ b/builtin/for-each-ref.c
> @@ -7,19 +7,9 @@
>  #include "ref-filter.h"
>  #include "strbuf.h"
>  #include "strvec.h"
> +#include "for-each-ref.h"
Let's keep the includes alphabetically sorted.
> diff --git a/t/t0450/adoc-help-mismatches b/t/t0450/adoc-help-mismatches
> index 06b469bdee..2c6ecd5fc8 100644
> --- a/t/t0450/adoc-help-mismatches
> +++ b/t/t0450/adoc-help-mismatches
> @@ -17,7 +17,6 @@ fast-export
>  fast-import
>  fetch-pack
>  fmt-merge-msg
> -for-each-ref
>  format-patch
>  fsck-objects
>  fsmonitor--daemon
Everything else looks sensible, but this change is surprising as it
wasn't mentioned in the commit message. I see that you changed the usage
though to match what we have in the man page. That's a good change
overall, but should probably be moved into a separate commit so that
this commit here is not changing any behaviour.
Patrick
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v4 0/5] Add refs list subcommand
  2025-07-31  9:00     ` [GSoC][RFC PATCH v4 0/5] Add " Meet Soni
                         ` (4 preceding siblings ...)
  2025-07-31  9:00       ` [GSoC][RFC PATCH v4 5/5] t: add test for git refs list subcommand Meet Soni
@ 2025-08-01  5:54       ` Patrick Steinhardt
  2025-08-04  9:22       ` [GSoC][RFC PATCH v5 0/6] " Meet Soni
  6 siblings, 0 replies; 65+ messages in thread
From: Patrick Steinhardt @ 2025-08-01  5:54 UTC (permalink / raw)
  To: Meet Soni; +Cc: git, shejialuo, karthik.188, gitster, sunshine
On Thu, Jul 31, 2025 at 02:30:35PM +0530, Meet Soni wrote:
> Hello everyone,
> 
> This is the fourth version of the patch series that introduces the git
> refs list subcommand.
> 
> Changes in v4:
> 
>   - Implemented architectural refactoring. The core logic of
>     for-each-ref now resides in a shared helper function, and both
>     for-each-ref and refs list have been simplified to thin wrappers
>     around it.
> 
>   - The usage strings have also been refactored. The common options
>     are now defined in a shared macro in a new for-each-ref.h header.
> 
>   - The patch series has been further split to cleanly separate the
>     preparatory refactoring commits (for both the C code and the
>     AsciiDoc documentation) from the commit that introduces the new
>     feature.
> 
>   - As a beneficial side-effect of unifying the usage strings, this
>     series now fixes a pre-existing inconsistency between the --help
>     output and the man page for for-each-ref. This allows a known
>     breakage for it in the t0450 documentation test to be removed.
I've only got some minor nits that should be quick to address,
everything else looks quite sensible to me. So I think this is almost
ready to be merged.
Thanks!
Patrick
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v4 3/5] builtin/refs: add list subcommand
  2025-07-31  9:00       ` [GSoC][RFC PATCH v4 3/5] builtin/refs: add list subcommand Meet Soni
@ 2025-08-01 13:27         ` Phillip Wood
  2025-08-01 14:43           ` Junio C Hamano
  0 siblings, 1 reply; 65+ messages in thread
From: Phillip Wood @ 2025-08-01 13:27 UTC (permalink / raw)
  To: Meet Soni, git; +Cc: ps, shejialuo, karthik.188, gitster, sunshine, John Cai
Hi Soni
On 31/07/2025 10:00, Meet Soni wrote:
> Git's reference management is distributed across multiple commands. As
> part of an ongoing effort to consolidate and modernize reference
> handling, introduce a `list` subcommand under the `git refs` umbrella as
> a replacement for `git for-each-ref`.
> 
> Implement `cmd_refs_list` by having it call the `for_each_ref_core()`
> helper function. This helper was factored out of the original
> `cmd_for_each_ref` in a preceding commit, allowing both commands to
> share the same core logic as independent peers.
> 
> Add documentation for the new command. The man page leverages the shared
> options file, created in a previous commit, by using the AsciiDoc
> `include::` macro to ensure consistency with git-for-each-ref(1).
I find the behavior of for-each-ref quite confusing so I wonder if we 
really want to copy it to the new command. For example
     git for-each-ref 'refs/h*'
does not print anything but
     git for-each-ref 'refs/heads/m*'
prints all the branches beginning with m. Another example is
     git for-each-ref 'refs/heads'
prints all the branches but
     git for-each-ref 'refs/heads*'
prints nothing. To me it would be much easier to understand if the 
pattern always required an explicit wildcard (or possible always did a 
prefix match in the absence of a wildcard).
Thanks
Phillip
> Mentored-by: Patrick Steinhardt <ps@pks.im>
> Mentored-by: shejialuo <shejialuo@gmail.com>
> Mentored-by: Karthik Nayak <karthik.188@gmail.com>
> Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
> ---
>   Documentation/git-refs.adoc | 16 ++++++++++++++++
>   builtin/refs.c              | 14 ++++++++++++++
>   2 files changed, 30 insertions(+)
> 
> diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
> index 4d6dc994f9..ee563aa7e0 100644
> --- a/Documentation/git-refs.adoc
> +++ b/Documentation/git-refs.adoc
> @@ -11,6 +11,13 @@ SYNOPSIS
>   [synopsis]
>   git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
>   git refs verify [--strict] [--verbose]
> +git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
> +	      [(--sort=<key>)...] [--format=<format>]
> +	      [--include-root-refs] [ --stdin | <pattern>... ]
> +	      [--points-at=<object>]
> +	      [--merged[=<object>]] [--no-merged[=<object>]]
> +	      [--contains[=<object>]] [--no-contains[=<object>]]
> +	      [--exclude=<pattern> ...]
>   
>   DESCRIPTION
>   -----------
> @@ -26,6 +33,11 @@ migrate::
>   verify::
>   	Verify reference database consistency.
>   
> +list::
> +	List references in the repository with support for filtering,
> +	formatting, and sorting. This subcommand is an alias for
> +	linkgit:git-for-each-ref[1] and offers identical functionality.
> +
>   OPTIONS
>   -------
>   
> @@ -57,6 +69,10 @@ The following options are specific to 'git refs verify':
>   --verbose::
>   	When verifying the reference database consistency, be chatty.
>   
> +The following options are specific to 'git refs list':
> +
> +include::for-each-ref-options.adoc[]
> +
>   KNOWN LIMITATIONS
>   -----------------
>   
> diff --git a/builtin/refs.c b/builtin/refs.c
> index 998d2a2c1c..848a7c9072 100644
> --- a/builtin/refs.c
> +++ b/builtin/refs.c
> @@ -6,6 +6,7 @@
>   #include "refs.h"
>   #include "strbuf.h"
>   #include "worktree.h"
> +#include "for-each-ref.h"
>   
>   #define REFS_MIGRATE_USAGE \
>   	N_("git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]")
> @@ -101,6 +102,17 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
>   	return ret;
>   }
>   
> +static int cmd_refs_list(int argc, const char **argv, const char *prefix,
> +			   struct repository *repo)
> +{
> +	static char const * const refs_list_usage[] = {
> +		N_("git refs list " COMMON_USAGE_FOR_EACH_REF),
> +		NULL
> +	};
> +
> +	return for_each_ref_core(argc, argv, prefix, repo, refs_list_usage);
> +}
> +
>   int cmd_refs(int argc,
>   	     const char **argv,
>   	     const char *prefix,
> @@ -109,12 +121,14 @@ int cmd_refs(int argc,
>   	const char * const refs_usage[] = {
>   		REFS_MIGRATE_USAGE,
>   		REFS_VERIFY_USAGE,
> +		"git refs list " COMMON_USAGE_FOR_EACH_REF,
>   		NULL,
>   	};
>   	parse_opt_subcommand_fn *fn = NULL;
>   	struct option opts[] = {
>   		OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate),
>   		OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify),
> +		OPT_SUBCOMMAND("list", &fn, cmd_refs_list),
>   		OPT_END(),
>   	};
>   
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v4 3/5] builtin/refs: add list subcommand
  2025-08-01 13:27         ` Phillip Wood
@ 2025-08-01 14:43           ` Junio C Hamano
  2025-08-01 15:49             ` Phillip Wood
  0 siblings, 1 reply; 65+ messages in thread
From: Junio C Hamano @ 2025-08-01 14:43 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Meet Soni, git, ps, shejialuo, karthik.188, sunshine, John Cai
Phillip Wood <phillip.wood123@gmail.com> writes:
> I find the behavior of for-each-ref quite confusing so I wonder if we
> really want to copy it to the new command. For example
>
>     git for-each-ref 'refs/h*'
>
> does not print anything but
>
>     git for-each-ref 'refs/heads/m*'
>
> prints all the branches beginning with m. Another example is
>
>     git for-each-ref 'refs/heads'
>
> prints all the branches but
>
>     git for-each-ref 'refs/heads*'
>
> prints nothing. To me it would be much easier to understand if the
> pattern always required an explicit wildcard (or possible always did a
> prefix match in the absence of a wildcard).
Thanks for raising this point.
One point you are wrong is 'refs/heads/m*'; it does not list all the
branches that begin with "m".  It will not show "refs/heads/mid/night"
even though it may show "refs/heads/morning'.
I do not think we want to require wildcard (in other words,
refs/heads/ and refs/heads that result in the prefix matching
behaviour that is anchored at hierarchy boundary is a good thing,
and we should not require refs/heads/* to get it).
As the "list the refs with various criteria" feature itself is
shared with "refs list", it would make the entire system even more
confusing if their criteria to choose which ones to show are
different.
I _think_ the current selection criteria is basically the prefix
match that is anchored at hierarchy boundary, and a single asterisk
expands only to a single hierarchy element without crossing
hierarchy boundary.  It is very handy that refs/heads/* expands to
the main integration branches (master, next, seen, maint-*) without
showing pw/3.0-commentchar-auto-deprecation branch and others.
What does a double-asterisk currently do in these patterns?  If it
is not doing anything useful, perhaps we should make it match any
letter, without getting constrained by hierarchy boundaries?  IOW,
a "fix" might be to make sure the following happens?
 - "refs/heads/m*" matches all local branches whose name starts with
   'm' like 'morning', but not the ones inside subhierarchies that
   start with 'm' like 'mid/night'.
 - "refs/heads/m**" matches all local branches whose name starts
   with 'm' and in the ones inside subhierarchies that start with
   'm'.
Thanks.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v4 3/5] builtin/refs: add list subcommand
  2025-08-01 14:43           ` Junio C Hamano
@ 2025-08-01 15:49             ` Phillip Wood
  2025-08-01 17:14               ` Junio C Hamano
                                 ` (2 more replies)
  0 siblings, 3 replies; 65+ messages in thread
From: Phillip Wood @ 2025-08-01 15:49 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Meet Soni, git, ps, shejialuo, karthik.188, sunshine, John Cai
On 01/08/2025 15:43, Junio C Hamano wrote:
> Phillip Wood <phillip.wood123@gmail.com> writes:
> 
>> I find the behavior of for-each-ref quite confusing so I wonder if we
>> really want to copy it to the new command. For example
>>
>>      git for-each-ref 'refs/h*'
>>
>> does not print anything but
>>
>>      git for-each-ref 'refs/heads/m*'
>>
>> prints all the branches beginning with m. Another example is
>>
>>      git for-each-ref 'refs/heads'
>>
>> prints all the branches but
>>
>>      git for-each-ref 'refs/heads*'
>>
>> prints nothing. To me it would be much easier to understand if the
>> pattern always required an explicit wildcard (or possible always did a
>> prefix match in the absence of a wildcard).
> 
> Thanks for raising this point.
> 
> One point you are wrong is 'refs/heads/m*'; it does not list all the
> branches that begin with "m".  It will not show "refs/heads/mid/night"
> even though it may show "refs/heads/morning'.
Thanks for correcting that
> I do not think we want to require wildcard (in other words,
> refs/heads/ and refs/heads that result in the prefix matching
> behaviour that is anchored at hierarchy boundary is a good thing,
> and we should not require refs/heads/* to get it).
It is confusing that refs/heads does a prefix match but refs/heads/m 
does not (unless there is a hierarchy boundary after the m). Maybe we 
should require a trailing slash to trigger a prefix match?
> As the "list the refs with various criteria" feature itself is
> shared with "refs list", it would make the entire system even more
> confusing if their criteria to choose which ones to show are
> different.
> 
> I _think_ the current selection criteria is basically the prefix
> match that is anchored at hierarchy boundary, and a single asterisk
> expands only to a single hierarchy element without crossing
> hierarchy boundary.
Having had a bit of a play with different patterns I think that's right.
> It is very handy that refs/heads/* expands to
> the main integration branches (master, next, seen, maint-*) without
> showing pw/3.0-commentchar-auto-deprecation branch and others.
> 
> What does a double-asterisk currently do in these patterns?
refs/heads/m** seems to behave like refs/heads/m*. I'm a bit surprised 
by that as for-each-ref seems to set WM_PATHNAME and I thought that our 
wildmatch code used '**' to match any character in that case.
> If it
> is not doing anything useful, perhaps we should make it match any
> letter, without getting constrained by hierarchy boundaries?  IOW,
> a "fix" might be to make sure the following happens?
> 
>   - "refs/heads/m*" matches all local branches whose name starts with
>     'm' like 'morning', but not the ones inside subhierarchies that
>     start with 'm' like 'mid/night'.
> 
>   - "refs/heads/m**" matches all local branches whose name starts
>     with 'm' and in the ones inside subhierarchies that start with
>     'm'.
That sounds like a good idea
Thanks
Phillip
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v4 3/5] builtin/refs: add list subcommand
  2025-08-01 15:49             ` Phillip Wood
@ 2025-08-01 17:14               ` Junio C Hamano
  2025-08-04  9:28                 ` Phillip Wood
  2025-08-04  6:32               ` Meet Soni
  2025-08-04  9:27               ` Phillip Wood
  2 siblings, 1 reply; 65+ messages in thread
From: Junio C Hamano @ 2025-08-01 17:14 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Meet Soni, git, ps, shejialuo, karthik.188, sunshine, John Cai
Phillip Wood <phillip.wood123@gmail.com> writes:
> It is confusing that refs/heads does a prefix match but refs/heads/m
> does not (unless there is a hierarchy boundary after the m).
And refs/hea would not show branches, for the same reason.  It is
what "anchored at hierarchy boundary" in the message you are
responding to means.  In other words, it is not a simple textual
prefix match.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v4 3/5] builtin/refs: add list subcommand
  2025-08-01 15:49             ` Phillip Wood
  2025-08-01 17:14               ` Junio C Hamano
@ 2025-08-04  6:32               ` Meet Soni
  2025-08-04  9:27               ` Phillip Wood
  2 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-04  6:32 UTC (permalink / raw)
  To: phillip.wood
  Cc: Junio C Hamano, git, ps, shejialuo, karthik.188, sunshine,
	John Cai
On Fri, 1 Aug 2025 at 21:19, Phillip Wood <phillip.wood123@gmail.com> wrote:
>
> On 01/08/2025 15:43, Junio C Hamano wrote:
> > Phillip Wood <phillip.wood123@gmail.com> writes:
> >
[snip]
> > If it
> > is not doing anything useful, perhaps we should make it match any
> > letter, without getting constrained by hierarchy boundaries?  IOW,
> > a "fix" might be to make sure the following happens?
> >
> >   - "refs/heads/m*" matches all local branches whose name starts with
> >     'm' like 'morning', but not the ones inside subhierarchies that
> >     start with 'm' like 'mid/night'.
> >
> >   - "refs/heads/m**" matches all local branches whose name starts
> >     with 'm' and in the ones inside subhierarchies that start with
> >     'm'.
>
> That sounds like a good idea
>
> Thanks
>
> Phillip
>
Hi Junio and Phillip,
Thanks for the valuable discussion.
I think it makes the most sense to implement the ** wildcard enhancement in a
separate patch series. The goals are logically different, and separating them
will be better for the commit history.
I'll start working on that series and will send it for review once this one is
wrapped up.
Thanks.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v4 2/5] builtin/for-each-ref: factor out core logic into a helper
  2025-08-01  5:54         ` Patrick Steinhardt
@ 2025-08-04  6:34           ` Meet Soni
  0 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-04  6:34 UTC (permalink / raw)
  To: Patrick Steinhardt
  Cc: git, shejialuo, karthik.188, gitster, sunshine,
	Ævar Arnfjörð Bjarmason, Victoria Dye
On Fri, 1 Aug 2025 at 11:24, Patrick Steinhardt <ps@pks.im> wrote:
>
> On Thu, Jul 31, 2025 at 02:30:37PM +0530, Meet Soni wrote:
> > diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
> > index 3d2207ec77..bbc0e5ad1c 100644
> > --- a/builtin/for-each-ref.c
> > +++ b/builtin/for-each-ref.c
> > @@ -7,19 +7,9 @@
> >  #include "ref-filter.h"
> >  #include "strbuf.h"
> >  #include "strvec.h"
> > +#include "for-each-ref.h"
>
> Let's keep the includes alphabetically sorted.
>
> > diff --git a/t/t0450/adoc-help-mismatches b/t/t0450/adoc-help-mismatches
> > index 06b469bdee..2c6ecd5fc8 100644
> > --- a/t/t0450/adoc-help-mismatches
> > +++ b/t/t0450/adoc-help-mismatches
> > @@ -17,7 +17,6 @@ fast-export
> >  fast-import
> >  fetch-pack
> >  fmt-merge-msg
> > -for-each-ref
> >  format-patch
> >  fsck-objects
> >  fsmonitor--daemon
>
> Everything else looks sensible, but this change is surprising as it
> wasn't mentioned in the commit message. I see that you changed the usage
> though to match what we have in the man page. That's a good change
> overall, but should probably be moved into a separate commit so that
> this commit here is not changing any behaviour.
>
> Patrick
Makes sense, I'll send the updated version.
Thanks.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v5 0/6] Add refs list subcommand
  2025-07-31  9:00     ` [GSoC][RFC PATCH v4 0/5] Add " Meet Soni
                         ` (5 preceding siblings ...)
  2025-08-01  5:54       ` [GSoC][RFC PATCH v4 0/5] Add " Patrick Steinhardt
@ 2025-08-04  9:22       ` Meet Soni
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 1/6] doc: factor out common option Meet Soni
                           ` (6 more replies)
  6 siblings, 7 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-04  9:22 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni
Hello Everyone,
This is the fifth version of the patch series that introduces the git
refs list subcommand.
changes in v5:
  - moved changes that fixed the test into a separate commit.
---
(v1 cover-letter text)
This patch series introduces `git refs list` as a modern replacement for
`git for-each-ref`, as part of an effort to consolidate ref-related
functionality under a unified `git refs` command.
Git's ref-related operations are currently handled by several distinct
commands, such as `git show-ref`, `git for-each-ref`, `git update-ref`,
`git pack-refs`, etc. This distribution has a few practical drawbacks:
- Users need to rely on multiple commands for related tasks involving
  refs.
- The commands may differ slightly in behavior and option syntax,
  leading to inconsistency.
We propose a long-term consolidation effort to bring ref-related
subcommands under the umbrella of a single command: `git refs`.
The implementation of `git refs list` is functionally identical to `git
for-each-ref`. It reuses the same internal logic (cmd_for_each_ref) to
ensure complete backward compatibility. The purpose of this patch is not
to introduce new behavior but to provide an alternate entry point under
the consolidated `git refs` namespace.
The motivation behind this change is twofold:
- Consolidation: Centralizing ref-related operations makes them easier
  to discover, use, and maintain.
- Evolution: While the initial goal is parity with existing commands,
  this consolidation allows for careful reconsideration of which
  features are essential. Over time, we can:
  - Remove legacy or obscure options that are no longer needed.
  - Add improvements that wouldn't make sense to bolt onto legacy
    commands.
  - Offering a more consistent and user-friendly surface.
To verify backward compatibility, this patch also includes a test
`t/t1461-refs-list.sh`, which runs the full `t6300-for-each-ref.sh` test
using `git refs list`. The test uses ${GIT_REFS_LIST_CMD:-for-each-ref}
to allow substitution without duplicating tests.
This patch is deliberately conservative: it introduces no behavioral
changes and leaves `for-each-ref` untouched. The goal is to lay
groundwork and demonstrate viability of ref consolidation within `git
refs`.
Going forward, I'd like to initiate a discussion on what the ideal
surface of `git refs list` should look like. Which options and features
from `for-each-ref` should be carried over? Are there any that are
obsolete or overly niche? What improvements might be worth considering
now that we have a new, consolidated interface?
Feedback on this, especially from those who rely on `for-each-ref` in
scripts or tooling would be very helpful.
Meet Soni (6):
  doc: factor out common option
  builtin/for-each-ref: align usage string with the man page
  builtin/for-each-ref: factor out core logic into a helper
  builtin/refs: add list subcommand
  t6300: refactor tests to be shareable
  t: add test for git refs list subcommand
 Documentation/for-each-ref-options.adoc |   79 +
 Documentation/git-for-each-ref.adoc     |   80 +-
 Documentation/git-refs.adoc             |   16 +
 builtin/for-each-ref.c                  |   35 +-
 builtin/refs.c                          |   14 +
 for-each-ref.h                          |   26 +
 t/for-each-ref-tests.sh                 | 2141 +++++++++++++++++++++++
 t/meson.build                           |    1 +
 t/t0450/adoc-help-mismatches            |    1 -
 t/t1461-refs-list.sh                    |    8 +
 t/t6300-for-each-ref.sh                 | 2140 +---------------------
 11 files changed, 2307 insertions(+), 2234 deletions(-)
 create mode 100644 Documentation/for-each-ref-options.adoc
 create mode 100644 for-each-ref.h
 create mode 100644 t/for-each-ref-tests.sh
 create mode 100755 t/t1461-refs-list.sh
Range-diff against v4:
1:  d2fa47a2b9 = 1:  d2fa47a2b9 doc: factor out common option
-:  ---------- > 2:  48a006dca9 builtin/for-each-ref: align usage string with the man page
2:  5084db4d14 ! 3:  b7788d477a builtin/for-each-ref: factor out core logic into a helper
    @@ Commit message
     
      ## builtin/for-each-ref.c ##
     @@
    - #include "ref-filter.h"
    + #include "builtin.h"
    + #include "commit.h"
    + #include "config.h"
    ++#include "for-each-ref.h"
    + #include "gettext.h"
    + #include "object.h"
    + #include "parse-options.h"
    +@@
      #include "strbuf.h"
      #include "strvec.h"
    -+#include "for-each-ref.h"
      
    +-#define COMMON_USAGE_FOR_EACH_REF \
    +-	"[--count=<count>] [--shell|--perl|--python|--tcl]\n" \
    +-	"                         [(--sort=<key>)...] [--format=<format>]\n" \
    +-	"                         [--include-root-refs] [ --stdin | <pattern>... ]\n" \
    +-	"                         [--points-at=<object>]\n" \
    +-	"                         [--merged[=<object>]] [--no-merged[=<object>]]\n" \
    +-	"                         [--contains[=<object>]] [--no-contains[=<object>]]\n" \
    +-	"                         [--exclude=<pattern> ...]"
    +-
     -static char const * const for_each_ref_usage[] = {
    --	N_("git for-each-ref [<options>] [<pattern>]"),
    --	N_("git for-each-ref [--points-at <object>]"),
    --	N_("git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"),
    --	N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"),
    +-	"git for-each-ref " COMMON_USAGE_FOR_EACH_REF,
     -	NULL
     -};
     -
    @@ for-each-ref.h (new)
     +#ifndef FOR_EACH_REF_H
     +#define FOR_EACH_REF_H
     +
    ++struct repository;
    ++
     +/*
     + * Shared usage string for options common to git-for-each-ref(1)
     + * and git-refs-list(1). The command-specific part (e.g., "git refs list ")
    @@ for-each-ref.h (new)
     +		      struct repository *repo, const char *const *usage);
     +
     +#endif /* FOR_EACH_REF_H */
    -
    - ## t/t0450/adoc-help-mismatches ##
    -@@ t/t0450/adoc-help-mismatches: fast-export
    - fast-import
    - fetch-pack
    - fmt-merge-msg
    --for-each-ref
    - format-patch
    - fsck-objects
    - fsmonitor--daemon
3:  1a15cd454b = 4:  97088dab96 builtin/refs: add list subcommand
4:  29c41d682b = 5:  abe9df9c4f t6300: refactor tests to be shareable
5:  0fd1f714c9 = 6:  a037a47dcd t: add test for git refs list subcommand
-- 
2.34.1
^ permalink raw reply	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v5 1/6] doc: factor out common option
  2025-08-04  9:22       ` [GSoC][RFC PATCH v5 0/6] " Meet Soni
@ 2025-08-04  9:22         ` Meet Soni
  2025-08-04 18:34           ` Junio C Hamano
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 2/6] builtin/for-each-ref: align usage string with the man page Meet Soni
                           ` (5 subsequent siblings)
  6 siblings, 1 reply; 65+ messages in thread
From: Meet Soni @ 2025-08-04  9:22 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni, Øystein Walle, Aaron Lipman, Taylor Blau,
	Jeff King, Derrick Stolee, Victoria Dye
In preparation for adding documentation for `git refs list`, factor out
the common options from the `git-for-each-ref` man page into a
shareable file `for-each-ref-options.adoc` and update
`git-for-each-ref.adoc` to use an `include::` macro.
This change is a pure refactoring and results in no change to the
final rendered documentation for `for-each-ref`.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 Documentation/for-each-ref-options.adoc | 79 ++++++++++++++++++++++++
 Documentation/git-for-each-ref.adoc     | 80 +------------------------
 2 files changed, 80 insertions(+), 79 deletions(-)
 create mode 100644 Documentation/for-each-ref-options.adoc
diff --git a/Documentation/for-each-ref-options.adoc b/Documentation/for-each-ref-options.adoc
new file mode 100644
index 0000000000..5f3a85bf64
--- /dev/null
+++ b/Documentation/for-each-ref-options.adoc
@@ -0,0 +1,79 @@
+<pattern>...::
+	If one or more patterns are given, only refs are shown that
+	match against at least one pattern, either using fnmatch(3) or
+	literally, in the latter case matching completely or from the
+	beginning up to a slash.
+
+--stdin::
+	If `--stdin` is supplied, then the list of patterns is read from
+	standard input instead of from the argument list.
+
+--count=<count>::
+	By default the command shows all refs that match
+	`<pattern>`.  This option makes it stop after showing
+	that many refs.
+
+--sort=<key>::
+	A field name to sort on.  Prefix `-` to sort in
+	descending order of the value.  When unspecified,
+	`refname` is used.  You may use the --sort=<key> option
+	multiple times, in which case the last key becomes the primary
+	key.
+
+--format=<format>::
+	A string that interpolates `%(fieldname)` from a ref being shown and
+	the object it points at. In addition, the string literal `%%`
+	renders as `%` and `%xx` - where `xx` are hex digits - renders as
+	the character with hex code `xx`. For example, `%00` interpolates to
+	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
++
+When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
+TAB %(refname)`.
+
+--color[=<when>]::
+	Respect any colors specified in the `--format` option. The
+	`<when>` field must be one of `always`, `never`, or `auto` (if
+	`<when>` is absent, behave as if `always` was given).
+
+--shell::
+--perl::
+--python::
+--tcl::
+	If given, strings that substitute `%(fieldname)`
+	placeholders are quoted as string literals suitable for
+	the specified host language.  This is meant to produce
+	a scriptlet that can directly be `eval`ed.
+
+--points-at=<object>::
+	Only list refs which points at the given object.
+
+--merged[=<object>]::
+	Only list refs whose tips are reachable from the
+	specified commit (HEAD if not specified).
+
+--no-merged[=<object>]::
+	Only list refs whose tips are not reachable from the
+	specified commit (HEAD if not specified).
+
+--contains[=<object>]::
+	Only list refs which contain the specified commit (HEAD if not
+	specified).
+
+--no-contains[=<object>]::
+	Only list refs which don't contain the specified commit (HEAD
+	if not specified).
+
+--ignore-case::
+	Sorting and filtering refs are case insensitive.
+
+--omit-empty::
+	Do not print a newline after formatted refs where the format expands
+	to the empty string.
+
+--exclude=<pattern>::
+	If one or more patterns are given, only refs which do not match
+	any excluded pattern(s) are shown. Matching is done using the
+	same rules as `<pattern>` above.
+
+--include-root-refs::
+	List root refs (HEAD and pseudorefs) apart from regular refs.
diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc
index 5ef89fc0fe..c3cf1752e3 100644
--- a/Documentation/git-for-each-ref.adoc
+++ b/Documentation/git-for-each-ref.adoc
@@ -28,85 +28,7 @@ host language allowing their direct evaluation in that language.
 
 OPTIONS
 -------
-<pattern>...::
-	If one or more patterns are given, only refs are shown that
-	match against at least one pattern, either using fnmatch(3) or
-	literally, in the latter case matching completely or from the
-	beginning up to a slash.
-
---stdin::
-	If `--stdin` is supplied, then the list of patterns is read from
-	standard input instead of from the argument list.
-
---count=<count>::
-	By default the command shows all refs that match
-	`<pattern>`.  This option makes it stop after showing
-	that many refs.
-
---sort=<key>::
-	A field name to sort on.  Prefix `-` to sort in
-	descending order of the value.  When unspecified,
-	`refname` is used.  You may use the --sort=<key> option
-	multiple times, in which case the last key becomes the primary
-	key.
-
---format=<format>::
-	A string that interpolates `%(fieldname)` from a ref being shown and
-	the object it points at. In addition, the string literal `%%`
-	renders as `%` and `%xx` - where `xx` are hex digits - renders as
-	the character with hex code `xx`. For example, `%00` interpolates to
-	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
-+
-When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
-TAB %(refname)`.
-
---color[=<when>]::
-	Respect any colors specified in the `--format` option. The
-	`<when>` field must be one of `always`, `never`, or `auto` (if
-	`<when>` is absent, behave as if `always` was given).
-
---shell::
---perl::
---python::
---tcl::
-	If given, strings that substitute `%(fieldname)`
-	placeholders are quoted as string literals suitable for
-	the specified host language.  This is meant to produce
-	a scriptlet that can directly be `eval`ed.
-
---points-at=<object>::
-	Only list refs which points at the given object.
-
---merged[=<object>]::
-	Only list refs whose tips are reachable from the
-	specified commit (HEAD if not specified).
-
---no-merged[=<object>]::
-	Only list refs whose tips are not reachable from the
-	specified commit (HEAD if not specified).
-
---contains[=<object>]::
-	Only list refs which contain the specified commit (HEAD if not
-	specified).
-
---no-contains[=<object>]::
-	Only list refs which don't contain the specified commit (HEAD
-	if not specified).
-
---ignore-case::
-	Sorting and filtering refs are case insensitive.
-
---omit-empty::
-	Do not print a newline after formatted refs where the format expands
-	to the empty string.
-
---exclude=<pattern>::
-	If one or more patterns are given, only refs which do not match
-	any excluded pattern(s) are shown. Matching is done using the
-	same rules as `<pattern>` above.
-
---include-root-refs::
-	List root refs (HEAD and pseudorefs) apart from regular refs.
+include::for-each-ref-options.adoc[]
 
 FIELD NAMES
 -----------
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v5 2/6] builtin/for-each-ref: align usage string with the man page
  2025-08-04  9:22       ` [GSoC][RFC PATCH v5 0/6] " Meet Soni
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 1/6] doc: factor out common option Meet Soni
@ 2025-08-04  9:22         ` Meet Soni
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 3/6] builtin/for-each-ref: factor out core logic into a helper Meet Soni
                           ` (4 subsequent siblings)
  6 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-04  9:22 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni, Ævar Arnfjörð Bjarmason, Derrick Stolee,
	Elijah Newren, Aaron Lipman
Usage string for `git for-each-ref` was out of sync with its official
documentation. The test `t0450-txt-doc-vs-help.sh` was marked as broken
due to this.
Update the usage string to match the documentation. This allows the test
to pass, so remove the corresponding 'known breakage' marker from the
test file.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 builtin/for-each-ref.c       | 14 ++++++++++----
 t/t0450/adoc-help-mismatches |  1 -
 2 files changed, 10 insertions(+), 5 deletions(-)
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 3d2207ec77..b2186f9f9a 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -8,11 +8,17 @@
 #include "strbuf.h"
 #include "strvec.h"
 
+#define COMMON_USAGE_FOR_EACH_REF \
+	"[--count=<count>] [--shell|--perl|--python|--tcl]\n" \
+	"                         [(--sort=<key>)...] [--format=<format>]\n" \
+	"                         [--include-root-refs] [ --stdin | <pattern>... ]\n" \
+	"                         [--points-at=<object>]\n" \
+	"                         [--merged[=<object>]] [--no-merged[=<object>]]\n" \
+	"                         [--contains[=<object>]] [--no-contains[=<object>]]\n" \
+	"                         [--exclude=<pattern> ...]"
+
 static char const * const for_each_ref_usage[] = {
-	N_("git for-each-ref [<options>] [<pattern>]"),
-	N_("git for-each-ref [--points-at <object>]"),
-	N_("git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"),
-	N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"),
+	"git for-each-ref " COMMON_USAGE_FOR_EACH_REF,
 	NULL
 };
 
diff --git a/t/t0450/adoc-help-mismatches b/t/t0450/adoc-help-mismatches
index 06b469bdee..2c6ecd5fc8 100644
--- a/t/t0450/adoc-help-mismatches
+++ b/t/t0450/adoc-help-mismatches
@@ -17,7 +17,6 @@ fast-export
 fast-import
 fetch-pack
 fmt-merge-msg
-for-each-ref
 format-patch
 fsck-objects
 fsmonitor--daemon
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v5 3/6] builtin/for-each-ref: factor out core logic into a helper
  2025-08-04  9:22       ` [GSoC][RFC PATCH v5 0/6] " Meet Soni
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 1/6] doc: factor out common option Meet Soni
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 2/6] builtin/for-each-ref: align usage string with the man page Meet Soni
@ 2025-08-04  9:22         ` Meet Soni
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 4/6] builtin/refs: add list subcommand Meet Soni
                           ` (3 subsequent siblings)
  6 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-04  9:22 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni, Victoria Dye, Elijah Newren
The implementation of `git for-each-ref` is monolithic within
`cmd_for_each_ref()`, making it impossible to share its logic with other
commands. To enable code reuse for the upcoming `git refs list`
subcommand, refactor the core logic into a shared helper function.
Introduce a new `for-each-ref.h` header to define the public interface
for this shared logic. It contains the declaration for a new helper
function, `for_each_ref_core()`, and a macro for the common usage
options.
Move the option parsing, filtering, and formatting logic from
`cmd_for_each_ref()` into a new helper function named
`for_each_ref_core()`. This helper is made generic by accepting the
command's usage string as a parameter.
The original `cmd_for_each_ref()` is simplified to a thin wrapper that
is only responsible for defining its specific usage array and calling
the shared helper.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 builtin/for-each-ref.c | 41 +++++++++++++++++++----------------------
 for-each-ref.h         | 26 ++++++++++++++++++++++++++
 2 files changed, 45 insertions(+), 22 deletions(-)
 create mode 100644 for-each-ref.h
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index b2186f9f9a..2d4945379a 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -1,6 +1,7 @@
 #include "builtin.h"
 #include "commit.h"
 #include "config.h"
+#include "for-each-ref.h"
 #include "gettext.h"
 #include "object.h"
 #include "parse-options.h"
@@ -8,24 +9,7 @@
 #include "strbuf.h"
 #include "strvec.h"
 
-#define COMMON_USAGE_FOR_EACH_REF \
-	"[--count=<count>] [--shell|--perl|--python|--tcl]\n" \
-	"                         [(--sort=<key>)...] [--format=<format>]\n" \
-	"                         [--include-root-refs] [ --stdin | <pattern>... ]\n" \
-	"                         [--points-at=<object>]\n" \
-	"                         [--merged[=<object>]] [--no-merged[=<object>]]\n" \
-	"                         [--contains[=<object>]] [--no-contains[=<object>]]\n" \
-	"                         [--exclude=<pattern> ...]"
-
-static char const * const for_each_ref_usage[] = {
-	"git for-each-ref " COMMON_USAGE_FOR_EACH_REF,
-	NULL
-};
-
-int cmd_for_each_ref(int argc,
-		     const char **argv,
-		     const char *prefix,
-		     struct repository *repo)
+int for_each_ref_core(int argc, const char **argv, const char *prefix, struct repository *repo, const char *const *usage)
 {
 	struct ref_sorting *sorting;
 	struct string_list sorting_options = STRING_LIST_INIT_DUP;
@@ -73,17 +57,17 @@ int cmd_for_each_ref(int argc,
 	/* Set default (refname) sorting */
 	string_list_append(&sorting_options, "refname");
 
-	parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
+	parse_options(argc, argv, prefix, opts, usage, 0);
 	if (format.array_opts.max_count < 0) {
 		error("invalid --count argument: `%d'", format.array_opts.max_count);
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(usage, opts);
 	}
 	if (HAS_MULTI_BITS(format.quote_style)) {
 		error("more than one quoting style?");
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(usage, opts);
 	}
 	if (verify_ref_format(&format))
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(usage, opts);
 
 	sorting = ref_sorting_options(&sorting_options);
 	ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
@@ -117,3 +101,16 @@ int cmd_for_each_ref(int argc,
 	strvec_clear(&vec);
 	return 0;
 }
+
+int cmd_for_each_ref(int argc,
+		     const char **argv,
+		     const char *prefix,
+		     struct repository *repo)
+{
+	static char const * const for_each_ref_usage[] = {
+		N_("git for-each-ref " COMMON_USAGE_FOR_EACH_REF),
+		NULL
+	};
+
+	return for_each_ref_core(argc, argv, prefix, repo, for_each_ref_usage);
+}
diff --git a/for-each-ref.h b/for-each-ref.h
new file mode 100644
index 0000000000..1662d25417
--- /dev/null
+++ b/for-each-ref.h
@@ -0,0 +1,26 @@
+#ifndef FOR_EACH_REF_H
+#define FOR_EACH_REF_H
+
+struct repository;
+
+/*
+ * Shared usage string for options common to git-for-each-ref(1)
+ * and git-refs-list(1). The command-specific part (e.g., "git refs list ")
+ * must be prepended by the caller.
+ */
+#define COMMON_USAGE_FOR_EACH_REF \
+	"[--count=<count>] [--shell|--perl|--python|--tcl]\n" \
+	"                         [(--sort=<key>)...] [--format=<format>]\n" \
+	"                         [--include-root-refs] [ --stdin | <pattern>... ]\n" \
+	"                         [--points-at=<object>]\n" \
+	"                         [--merged[=<object>]] [--no-merged[=<object>]]\n" \
+	"                         [--contains[=<object>]] [--no-contains[=<object>]]\n" \
+	"                         [--exclude=<pattern> ...]"
+
+/*
+ * The core logic for for-each-ref and its clones.
+ */
+int for_each_ref_core(int argc, const char **argv, const char *prefix,
+		      struct repository *repo, const char *const *usage);
+
+#endif /* FOR_EACH_REF_H */
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v5 4/6] builtin/refs: add list subcommand
  2025-08-04  9:22       ` [GSoC][RFC PATCH v5 0/6] " Meet Soni
                           ` (2 preceding siblings ...)
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 3/6] builtin/for-each-ref: factor out core logic into a helper Meet Soni
@ 2025-08-04  9:22         ` Meet Soni
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 5/6] t6300: refactor tests to be shareable Meet Soni
                           ` (2 subsequent siblings)
  6 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-04  9:22 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni, John Cai
Git's reference management is distributed across multiple commands. As
part of an ongoing effort to consolidate and modernize reference
handling, introduce a `list` subcommand under the `git refs` umbrella as
a replacement for `git for-each-ref`.
Implement `cmd_refs_list` by having it call the `for_each_ref_core()`
helper function. This helper was factored out of the original
`cmd_for_each_ref` in a preceding commit, allowing both commands to
share the same core logic as independent peers.
Add documentation for the new command. The man page leverages the shared
options file, created in a previous commit, by using the AsciiDoc
`include::` macro to ensure consistency with git-for-each-ref(1).
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 Documentation/git-refs.adoc | 16 ++++++++++++++++
 builtin/refs.c              | 14 ++++++++++++++
 2 files changed, 30 insertions(+)
diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
index 4d6dc994f9..ee563aa7e0 100644
--- a/Documentation/git-refs.adoc
+++ b/Documentation/git-refs.adoc
@@ -11,6 +11,13 @@ SYNOPSIS
 [synopsis]
 git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
 git refs verify [--strict] [--verbose]
+git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
+	      [(--sort=<key>)...] [--format=<format>]
+	      [--include-root-refs] [ --stdin | <pattern>... ]
+	      [--points-at=<object>]
+	      [--merged[=<object>]] [--no-merged[=<object>]]
+	      [--contains[=<object>]] [--no-contains[=<object>]]
+	      [--exclude=<pattern> ...]
 
 DESCRIPTION
 -----------
@@ -26,6 +33,11 @@ migrate::
 verify::
 	Verify reference database consistency.
 
+list::
+	List references in the repository with support for filtering,
+	formatting, and sorting. This subcommand is an alias for
+	linkgit:git-for-each-ref[1] and offers identical functionality.
+
 OPTIONS
 -------
 
@@ -57,6 +69,10 @@ The following options are specific to 'git refs verify':
 --verbose::
 	When verifying the reference database consistency, be chatty.
 
+The following options are specific to 'git refs list':
+
+include::for-each-ref-options.adoc[]
+
 KNOWN LIMITATIONS
 -----------------
 
diff --git a/builtin/refs.c b/builtin/refs.c
index 998d2a2c1c..848a7c9072 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -6,6 +6,7 @@
 #include "refs.h"
 #include "strbuf.h"
 #include "worktree.h"
+#include "for-each-ref.h"
 
 #define REFS_MIGRATE_USAGE \
 	N_("git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]")
@@ -101,6 +102,17 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
 	return ret;
 }
 
+static int cmd_refs_list(int argc, const char **argv, const char *prefix,
+			   struct repository *repo)
+{
+	static char const * const refs_list_usage[] = {
+		N_("git refs list " COMMON_USAGE_FOR_EACH_REF),
+		NULL
+	};
+
+	return for_each_ref_core(argc, argv, prefix, repo, refs_list_usage);
+}
+
 int cmd_refs(int argc,
 	     const char **argv,
 	     const char *prefix,
@@ -109,12 +121,14 @@ int cmd_refs(int argc,
 	const char * const refs_usage[] = {
 		REFS_MIGRATE_USAGE,
 		REFS_VERIFY_USAGE,
+		"git refs list " COMMON_USAGE_FOR_EACH_REF,
 		NULL,
 	};
 	parse_opt_subcommand_fn *fn = NULL;
 	struct option opts[] = {
 		OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate),
 		OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify),
+		OPT_SUBCOMMAND("list", &fn, cmd_refs_list),
 		OPT_END(),
 	};
 
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v5 5/6] t6300: refactor tests to be shareable
  2025-08-04  9:22       ` [GSoC][RFC PATCH v5 0/6] " Meet Soni
                           ` (3 preceding siblings ...)
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 4/6] builtin/refs: add list subcommand Meet Soni
@ 2025-08-04  9:22         ` Meet Soni
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 6/6] t: add test for git refs list subcommand Meet Soni
  2025-08-05  9:27         ` [GSoC][PATCH v6 0/6] Add " Meet Soni
  6 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-04  9:22 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni, Jeff King, Hariom Verma, Kousik Sanagavarapu,
	Taylor Blau
In preparation for adding tests for the new `git refs list` command,
refactor the existing t6300 test suite to make its logic shareable.
Move the core test logic from `t6300-for-each-ref.sh` into a new
`for-each-ref-tests.sh` file. Inside this new script, replace hardcoded
calls to "git for-each-ref" with the `$git_for_each_ref` variable.
The original `t6300-for-each-ref.sh` script now becomes a simple
"driver". It is responsible for setting the default value of the
variable and then sourcing the test library.
This new structure follows the established pattern used for sharing
tests between `git-blame` and `git-annotate` and prepares the test suite
for the `refs list` tests to be added in a subsequent commit.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 t/for-each-ref-tests.sh | 2141 +++++++++++++++++++++++++++++++++++++++
 t/t6300-for-each-ref.sh | 2140 +-------------------------------------
 2 files changed, 2143 insertions(+), 2138 deletions(-)
 create mode 100644 t/for-each-ref-tests.sh
diff --git a/t/for-each-ref-tests.sh b/t/for-each-ref-tests.sh
new file mode 100644
index 0000000000..e3ad19298a
--- /dev/null
+++ b/t/for-each-ref-tests.sh
@@ -0,0 +1,2141 @@
+git_for_each_ref=${git_for_each_ref:-git for-each-ref}
+GNUPGHOME_NOT_USED=$GNUPGHOME
+. "$TEST_DIRECTORY"/lib-gpg.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
+
+# Mon Jul 3 23:18:43 2006 +0000
+datestamp=1151968723
+setdate_and_increment () {
+    GIT_COMMITTER_DATE="$datestamp +0200"
+    datestamp=$(expr "$datestamp" + 1)
+    GIT_AUTHOR_DATE="$datestamp +0200"
+    datestamp=$(expr "$datestamp" + 1)
+    export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+}
+
+test_object_file_size () {
+	oid=$(git rev-parse "$1")
+	path=".git/objects/$(test_oid_to_path $oid)"
+	test_file_size "$path"
+}
+
+test_expect_success setup '
+	# setup .mailmap
+	cat >.mailmap <<-EOF &&
+	A Thor <athor@example.com> A U Thor <author@example.com>
+	C Mitter <cmitter@example.com> C O Mitter <committer@example.com>
+	EOF
+
+	setdate_and_increment &&
+	echo "Using $datestamp" > one &&
+	git add one &&
+	git commit -m "Initial" &&
+	git branch -M main &&
+	setdate_and_increment &&
+	git tag -a -m "Tagging at $datestamp" testtag &&
+	git update-ref refs/remotes/origin/main main &&
+	git remote add origin nowhere &&
+	git config branch.main.remote origin &&
+	git config branch.main.merge refs/heads/main &&
+	git remote add myfork elsewhere &&
+	git config remote.pushdefault myfork &&
+	git config push.default current
+'
+
+test_atom () {
+	case "$1" in
+		head) ref=refs/heads/main ;;
+		 tag) ref=refs/tags/testtag ;;
+		 sym) ref=refs/heads/sym ;;
+		   *) ref=$1 ;;
+	esac
+	format=$2
+	test_do=test_expect_${4:-success}
+
+	printf '%s\n' "$3" >expected
+	$test_do $PREREQ "basic atom: $ref $format" '
+		${git_for_each_ref} --format="%($format)" "$ref" >actual &&
+		sanitize_pgp <actual >actual.clean &&
+		test_cmp expected actual.clean
+	'
+
+	# Automatically test "contents:size" atom after testing "contents"
+	if test "$format" = "contents"
+	then
+		# for commit leg, $3 is changed there
+		expect=$(printf '%s' "$3" | wc -c)
+		$test_do $PREREQ "basic atom: $ref contents:size" '
+			type=$(git cat-file -t "$ref") &&
+			case $type in
+			tag)
+				# We cannot use $3 as it expects sanitize_pgp to run
+				git cat-file tag $ref >out &&
+				expect=$(tail -n +6 out | wc -c) &&
+				rm -f out ;;
+			tree | blob)
+				expect="" ;;
+			commit)
+				: "use the calculated expect" ;;
+			*)
+				BUG "unknown object type" ;;
+			esac &&
+			# Leave $expect unquoted to lose possible leading whitespaces
+			echo $expect >expected &&
+			${git_for_each_ref} --format="%(contents:size)" "$ref" >actual &&
+			test_cmp expected actual
+		'
+	fi
+}
+
+hexlen=$(test_oid hexsz)
+
+test_atom head refname refs/heads/main
+test_atom head refname: refs/heads/main
+test_atom head refname:short main
+test_atom head refname:lstrip=1 heads/main
+test_atom head refname:lstrip=2 main
+test_atom head refname:lstrip=-1 main
+test_atom head refname:lstrip=-2 heads/main
+test_atom head refname:rstrip=1 refs/heads
+test_atom head refname:rstrip=2 refs
+test_atom head refname:rstrip=-1 refs
+test_atom head refname:rstrip=-2 refs/heads
+test_atom head refname:strip=1 heads/main
+test_atom head refname:strip=2 main
+test_atom head refname:strip=-1 main
+test_atom head refname:strip=-2 heads/main
+test_atom head upstream refs/remotes/origin/main
+test_atom head upstream:short origin/main
+test_atom head upstream:lstrip=2 origin/main
+test_atom head upstream:lstrip=-2 origin/main
+test_atom head upstream:rstrip=2 refs/remotes
+test_atom head upstream:rstrip=-2 refs/remotes
+test_atom head upstream:strip=2 origin/main
+test_atom head upstream:strip=-2 origin/main
+test_atom head push refs/remotes/myfork/main
+test_atom head push:short myfork/main
+test_atom head push:lstrip=1 remotes/myfork/main
+test_atom head push:lstrip=-1 main
+test_atom head push:rstrip=1 refs/remotes/myfork
+test_atom head push:rstrip=-1 refs
+test_atom head push:strip=1 remotes/myfork/main
+test_atom head push:strip=-1 main
+test_atom head objecttype commit
+test_atom head objectsize $((131 + hexlen))
+test_atom head objectsize:disk $(test_object_file_size refs/heads/main)
+test_atom head deltabase $ZERO_OID
+test_atom head objectname $(git rev-parse refs/heads/main)
+test_atom head objectname:short $(git rev-parse --short refs/heads/main)
+test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
+test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
+test_atom head tree $(git rev-parse refs/heads/main^{tree})
+test_atom head tree:short $(git rev-parse --short refs/heads/main^{tree})
+test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/main^{tree})
+test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/main^{tree})
+test_atom head parent ''
+test_atom head parent:short ''
+test_atom head parent:short=1 ''
+test_atom head parent:short=10 ''
+test_atom head numparent 0
+test_atom head object ''
+test_atom head type ''
+test_atom head raw "$(git cat-file commit refs/heads/main)
+"
+test_atom head '*objectname' ''
+test_atom head '*objecttype' ''
+test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
+test_atom head authorname 'A U Thor'
+test_atom head authorname:mailmap 'A Thor'
+test_atom head authoremail '<author@example.com>'
+test_atom head authoremail:trim 'author@example.com'
+test_atom head authoremail:localpart 'author'
+test_atom head authoremail:trim,localpart 'author'
+test_atom head authoremail:mailmap '<athor@example.com>'
+test_atom head authoremail:mailmap,trim 'athor@example.com'
+test_atom head authoremail:trim,mailmap 'athor@example.com'
+test_atom head authoremail:mailmap,localpart 'athor'
+test_atom head authoremail:localpart,mailmap 'athor'
+test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor'
+test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
+test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
+test_atom head committername 'C O Mitter'
+test_atom head committername:mailmap 'C Mitter'
+test_atom head committeremail '<committer@example.com>'
+test_atom head committeremail:trim 'committer@example.com'
+test_atom head committeremail:localpart 'committer'
+test_atom head committeremail:localpart,trim 'committer'
+test_atom head committeremail:mailmap '<cmitter@example.com>'
+test_atom head committeremail:mailmap,trim 'cmitter@example.com'
+test_atom head committeremail:trim,mailmap 'cmitter@example.com'
+test_atom head committeremail:mailmap,localpart 'cmitter'
+test_atom head committeremail:localpart,mailmap 'cmitter'
+test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter'
+test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
+test_atom head tag ''
+test_atom head tagger ''
+test_atom head taggername ''
+test_atom head taggeremail ''
+test_atom head taggeremail:trim ''
+test_atom head taggeremail:localpart ''
+test_atom head taggerdate ''
+test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
+test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
+test_atom head subject 'Initial'
+test_atom head subject:sanitize 'Initial'
+test_atom head contents:subject 'Initial'
+test_atom head body ''
+test_atom head contents:body ''
+test_atom head contents:signature ''
+test_atom head contents 'Initial
+'
+test_atom head HEAD '*'
+
+test_atom tag refname refs/tags/testtag
+test_atom tag refname:short testtag
+test_atom tag upstream ''
+test_atom tag push ''
+test_atom tag objecttype tag
+test_atom tag objectsize $((114 + hexlen))
+test_atom tag objectsize:disk $(test_object_file_size refs/tags/testtag)
+test_atom tag '*objectsize:disk' $(test_object_file_size refs/heads/main)
+test_atom tag deltabase $ZERO_OID
+test_atom tag '*deltabase' $ZERO_OID
+test_atom tag objectname $(git rev-parse refs/tags/testtag)
+test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
+test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
+test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
+test_atom tag tree ''
+test_atom tag tree:short ''
+test_atom tag tree:short=1 ''
+test_atom tag tree:short=10 ''
+test_atom tag parent ''
+test_atom tag parent:short ''
+test_atom tag parent:short=1 ''
+test_atom tag parent:short=10 ''
+test_atom tag numparent ''
+test_atom tag object $(git rev-parse refs/tags/testtag^0)
+test_atom tag type 'commit'
+test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{})
+test_atom tag '*objecttype' 'commit'
+test_atom tag author ''
+test_atom tag authorname ''
+test_atom tag authorname:mailmap ''
+test_atom tag authoremail ''
+test_atom tag authoremail:trim ''
+test_atom tag authoremail:localpart ''
+test_atom tag authoremail:trim,localpart ''
+test_atom tag authoremail:mailmap ''
+test_atom tag authoremail:mailmap,trim ''
+test_atom tag authoremail:trim,mailmap ''
+test_atom tag authoremail:mailmap,localpart ''
+test_atom tag authoremail:localpart,mailmap ''
+test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim ''
+test_atom tag authordate ''
+test_atom tag committer ''
+test_atom tag committername ''
+test_atom tag committername:mailmap ''
+test_atom tag committeremail ''
+test_atom tag committeremail:trim ''
+test_atom tag committeremail:localpart ''
+test_atom tag committeremail:localpart,trim ''
+test_atom tag committeremail:mailmap ''
+test_atom tag committeremail:mailmap,trim ''
+test_atom tag committeremail:trim,mailmap ''
+test_atom tag committeremail:mailmap,localpart ''
+test_atom tag committeremail:localpart,mailmap ''
+test_atom tag committeremail:trim,mailmap,trim,trim,localpart ''
+test_atom tag committerdate ''
+test_atom tag tag 'testtag'
+test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
+test_atom tag taggername 'C O Mitter'
+test_atom tag taggername:mailmap 'C Mitter'
+test_atom tag taggeremail '<committer@example.com>'
+test_atom tag taggeremail:trim 'committer@example.com'
+test_atom tag taggeremail:localpart 'committer'
+test_atom tag taggeremail:trim,localpart 'committer'
+test_atom tag taggeremail:mailmap '<cmitter@example.com>'
+test_atom tag taggeremail:mailmap,trim 'cmitter@example.com'
+test_atom tag taggeremail:trim,mailmap 'cmitter@example.com'
+test_atom tag taggeremail:mailmap,localpart 'cmitter'
+test_atom tag taggeremail:localpart,mailmap 'cmitter'
+test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter'
+test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
+test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag subject 'Tagging at 1151968727'
+test_atom tag subject:sanitize 'Tagging-at-1151968727'
+test_atom tag contents:subject 'Tagging at 1151968727'
+test_atom tag body ''
+test_atom tag contents:body ''
+test_atom tag contents:signature ''
+test_atom tag contents 'Tagging at 1151968727
+'
+test_atom tag HEAD ' '
+
+test_expect_success 'basic atom: refs/tags/testtag *raw' '
+	git cat-file commit refs/tags/testtag^{} >expected &&
+	${git_for_each_ref} --format="%(*raw)" refs/tags/testtag >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_expect_success 'Check invalid atoms names are errors' '
+	test_must_fail ${git_for_each_ref} --format="%(INVALID)" refs/heads
+'
+
+test_expect_success 'Check format specifiers are ignored in naming date atoms' '
+	${git_for_each_ref} --format="%(authordate)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:default) %(authordate)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate) %(authordate:default)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:default) %(authordate:default)" refs/heads
+'
+
+test_expect_success 'Check valid format specifiers for date fields' '
+	${git_for_each_ref} --format="%(authordate:default)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:relative)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:short)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:local)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:iso8601)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:rfc2822)" refs/heads
+'
+
+test_expect_success 'Check invalid format specifiers are errors' '
+	test_must_fail ${git_for_each_ref} --format="%(authordate:INVALID)" refs/heads
+'
+
+test_expect_success 'arguments to %(objectname:short=) must be positive integers' '
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=0)" &&
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=-1)" &&
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=foo)"
+'
+
+test_bad_atom () {
+	case "$1" in
+	head) ref=refs/heads/main ;;
+	 tag) ref=refs/tags/testtag ;;
+	 sym) ref=refs/heads/sym ;;
+	   *) ref=$1 ;;
+	esac
+	format=$2
+	test_do=test_expect_${4:-success}
+
+	printf '%s\n' "$3" >expect
+	$test_do $PREREQ "err basic atom: $ref $format" '
+		test_must_fail ${git_for_each_ref} \
+			--format="%($format)" "$ref" 2>error &&
+		test_cmp expect error
+	'
+}
+
+test_bad_atom head 'authoremail:foo' \
+	'fatal: unrecognized %(authoremail) argument: foo'
+
+test_bad_atom head 'authoremail:mailmap,trim,bar' \
+	'fatal: unrecognized %(authoremail) argument: bar'
+
+test_bad_atom head 'authoremail:trim,' \
+	'fatal: unrecognized %(authoremail) argument: '
+
+test_bad_atom head 'authoremail:mailmaptrim' \
+	'fatal: unrecognized %(authoremail) argument: trim'
+
+test_bad_atom head 'committeremail: ' \
+	'fatal: unrecognized %(committeremail) argument:  '
+
+test_bad_atom head 'committeremail: trim,foo' \
+	'fatal: unrecognized %(committeremail) argument:  trim,foo'
+
+test_bad_atom head 'committeremail:mailmap,localpart ' \
+	'fatal: unrecognized %(committeremail) argument:  '
+
+test_bad_atom head 'committeremail:trim_localpart' \
+	'fatal: unrecognized %(committeremail) argument: _localpart'
+
+test_bad_atom head 'committeremail:localpart,,,trim' \
+	'fatal: unrecognized %(committeremail) argument: ,,trim'
+
+test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \
+	'fatal: unrecognized %(taggeremail) argument:  foo '
+
+test_bad_atom tag 'taggeremail:trim,localpart,' \
+	'fatal: unrecognized %(taggeremail) argument: '
+
+test_bad_atom tag 'taggeremail:mailmap;localpart trim' \
+	'fatal: unrecognized %(taggeremail) argument: ;localpart trim'
+
+test_bad_atom tag 'taggeremail:localpart trim' \
+	'fatal: unrecognized %(taggeremail) argument:  trim'
+
+test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \
+	'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim'
+
+test_date () {
+	f=$1 &&
+	committer_date=$2 &&
+	author_date=$3 &&
+	tagger_date=$4 &&
+	cat >expected <<-EOF &&
+	'refs/heads/main' '$committer_date' '$author_date'
+	'refs/tags/testtag' '$tagger_date'
+	EOF
+	(
+		${git_for_each_ref} --shell \
+			--format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
+			refs/heads &&
+		${git_for_each_ref} --shell \
+			--format="%(refname) %(taggerdate${f:+:$f})" \
+			refs/tags
+	) >actual &&
+	test_cmp expected actual
+}
+
+test_expect_success 'Check unformatted date fields output' '
+	test_date "" \
+		"Tue Jul 4 01:18:43 2006 +0200" \
+		"Tue Jul 4 01:18:44 2006 +0200" \
+		"Tue Jul 4 01:18:45 2006 +0200"
+'
+
+test_expect_success 'Check format "default" formatted date fields output' '
+	test_date default \
+		"Tue Jul 4 01:18:43 2006 +0200" \
+		"Tue Jul 4 01:18:44 2006 +0200" \
+		"Tue Jul 4 01:18:45 2006 +0200"
+'
+
+test_expect_success 'Check format "default-local" date fields output' '
+	test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006"
+'
+
+# Don't know how to do relative check because I can't know when this script
+# is going to be run and can't fake the current time to git, and hence can't
+# provide expected output.  Instead, I'll just make sure that "relative"
+# doesn't exit in error
+test_expect_success 'Check format "relative" date fields output' '
+	f=relative &&
+	(${git_for_each_ref} --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
+	${git_for_each_ref} --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
+'
+
+# We just check that this is the same as "relative" for now.
+test_expect_success 'Check format "relative-local" date fields output' '
+	test_date relative-local \
+		"$(${git_for_each_ref} --format="%(committerdate:relative)" refs/heads)" \
+		"$(${git_for_each_ref} --format="%(authordate:relative)" refs/heads)" \
+		"$(${git_for_each_ref} --format="%(taggerdate:relative)" refs/tags)"
+'
+
+test_expect_success 'Check format "short" date fields output' '
+	test_date short 2006-07-04 2006-07-04 2006-07-04
+'
+
+test_expect_success 'Check format "short-local" date fields output' '
+	test_date short-local 2006-07-03 2006-07-03 2006-07-03
+'
+
+test_expect_success 'Check format "local" date fields output' '
+	test_date local \
+		"Mon Jul 3 23:18:43 2006" \
+		"Mon Jul 3 23:18:44 2006" \
+		"Mon Jul 3 23:18:45 2006"
+'
+
+test_expect_success 'Check format "iso8601" date fields output' '
+	test_date iso8601 \
+		"2006-07-04 01:18:43 +0200" \
+		"2006-07-04 01:18:44 +0200" \
+		"2006-07-04 01:18:45 +0200"
+'
+
+test_expect_success 'Check format "iso8601-local" date fields output' '
+	test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000"
+'
+
+test_expect_success 'Check format "rfc2822" date fields output' '
+	test_date rfc2822 \
+		"Tue, 4 Jul 2006 01:18:43 +0200" \
+		"Tue, 4 Jul 2006 01:18:44 +0200" \
+		"Tue, 4 Jul 2006 01:18:45 +0200"
+'
+
+test_expect_success 'Check format "rfc2822-local" date fields output' '
+	test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000"
+'
+
+test_expect_success 'Check format "raw" date fields output' '
+	test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200"
+'
+
+test_expect_success 'Check format "raw-local" date fields output' '
+	test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000"
+'
+
+test_expect_success 'Check format of strftime date fields' '
+	echo "my date is 2006-07-04" >expected &&
+	${git_for_each_ref} \
+	  --format="%(authordate:format:my date is %Y-%m-%d)" \
+	  refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Check format of strftime-local date fields' '
+	echo "my date is 2006-07-03" >expected &&
+	${git_for_each_ref} \
+	  --format="%(authordate:format-local:my date is %Y-%m-%d)" \
+	  refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'exercise strftime with odd fields' '
+	echo >expected &&
+	${git_for_each_ref} --format="%(authordate:format:)" refs/heads >actual &&
+	test_cmp expected actual &&
+	long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" &&
+	echo $long >expected &&
+	${git_for_each_ref} --format="%(authordate:format:$long)" refs/heads >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/heads/main
+refs/remotes/origin/main
+refs/tags/testtag
+EOF
+
+test_expect_success 'Verify ascending sort' '
+	${git_for_each_ref} --format="%(refname)" --sort=refname >actual &&
+	test_cmp expected actual
+'
+
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/remotes/origin/main
+refs/heads/main
+EOF
+
+test_expect_success 'Verify descending sort' '
+	${git_for_each_ref} --format="%(refname)" --sort=-refname >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Give help even with invalid sort atoms' '
+	test_expect_code 129 ${git_for_each_ref} --sort=bogus -h >actual 2>&1 &&
+	grep "^usage: ${git_for_each_ref}" actual
+'
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/tags/testtag-2
+EOF
+
+test_expect_success 'exercise patterns with prefixes' '
+	git tag testtag-2 &&
+	test_when_finished "git tag -d testtag-2" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/testtag refs/tags/testtag-2 >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/tags/testtag-2
+EOF
+
+test_expect_success 'exercise glob patterns with prefixes' '
+	git tag testtag-2 &&
+	test_when_finished "git tag -d testtag-2" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/testtag "refs/tags/testtag-*" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/bar
+refs/tags/baz
+refs/tags/testtag
+EOF
+
+test_expect_success 'exercise patterns with prefix exclusions' '
+	for tag in foo/one foo/two foo/three bar baz
+	do
+		git tag "$tag" || return 1
+	done &&
+	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/ --exclude=refs/tags/foo >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/bar
+refs/tags/baz
+refs/tags/foo/one
+refs/tags/testtag
+EOF
+
+test_expect_success 'exercise patterns with pattern exclusions' '
+	for tag in foo/one foo/two foo/three bar baz
+	do
+		git tag "$tag" || return 1
+	done &&
+	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/ --exclude="refs/tags/foo/t*" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+'refs/heads/main'
+'refs/remotes/origin/main'
+'refs/tags/testtag'
+EOF
+
+test_expect_success 'Quoting style: shell' '
+	${git_for_each_ref} --shell --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Quoting style: perl' '
+	${git_for_each_ref} --perl --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Quoting style: python' '
+	${git_for_each_ref} --python --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+"refs/heads/main"
+"refs/remotes/origin/main"
+"refs/tags/testtag"
+EOF
+
+test_expect_success 'Quoting style: tcl' '
+	${git_for_each_ref} --tcl --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
+	test_expect_success "more than one quoting style: $i" "
+		test_must_fail ${git_for_each_ref} $i 2>err &&
+		grep '^error: more than one quoting style' err
+	"
+done
+
+test_expect_success 'setup for upstream:track[short]' '
+	test_commit two
+'
+
+test_atom head upstream:track '[ahead 1]'
+test_atom head upstream:trackshort '>'
+test_atom head upstream:track,nobracket 'ahead 1'
+test_atom head upstream:nobracket,track 'ahead 1'
+
+test_expect_success 'setup for push:track[short]' '
+	test_commit third &&
+	git update-ref refs/remotes/myfork/main main &&
+	git reset main~1
+'
+
+test_atom head push:track '[behind 1]'
+test_atom head push:trackshort '<'
+
+test_expect_success 'Check that :track[short] cannot be used with other atoms' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:track)" 2>/dev/null &&
+	test_must_fail ${git_for_each_ref} --format="%(refname:trackshort)" 2>/dev/null
+'
+
+test_expect_success 'Check that :track[short] works when upstream is invalid' '
+	cat >expected <<-\EOF &&
+	[gone]
+
+	EOF
+	test_when_finished "git config branch.main.merge refs/heads/main" &&
+	git config branch.main.merge refs/heads/does-not-exist &&
+	${git_for_each_ref} \
+		--format="%(upstream:track)$LF%(upstream:trackshort)" \
+		refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Check for invalid refname format' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:INVALID)"
+'
+
+test_expect_success 'set up color tests' '
+	cat >expected.color <<-EOF &&
+	$(git rev-parse --short refs/heads/main) <GREEN>main<RESET>
+	$(git rev-parse --short refs/remotes/myfork/main) <GREEN>myfork/main<RESET>
+	$(git rev-parse --short refs/remotes/origin/main) <GREEN>origin/main<RESET>
+	$(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET>
+	$(git rev-parse --short refs/tags/third) <GREEN>third<RESET>
+	$(git rev-parse --short refs/tags/two) <GREEN>two<RESET>
+	EOF
+	sed "s/<[^>]*>//g" <expected.color >expected.bare &&
+	color_format="%(objectname:short) %(color:green)%(refname:short)"
+'
+
+test_expect_success TTY '%(color) shows color with a tty' '
+	test_terminal ${git_for_each_ref} --format="$color_format" >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	test_cmp expected.color actual
+'
+
+test_expect_success '%(color) does not show color without tty' '
+	TERM=vt100 ${git_for_each_ref} --format="$color_format" >actual &&
+	test_cmp expected.bare actual
+'
+
+test_expect_success '--color can override tty check' '
+	${git_for_each_ref} --color --format="$color_format" >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	test_cmp expected.color actual
+'
+
+test_expect_success 'color.ui=always does not override tty check' '
+	git -c color.ui=always ${git_for_each_ref#git} --format="$color_format" >actual &&
+	test_cmp expected.bare actual
+'
+
+test_expect_success 'setup for describe atom tests' '
+	git init -b master describe-repo &&
+	(
+		cd describe-repo &&
+
+		test_commit --no-tag one &&
+		git tag tagone &&
+
+		test_commit --no-tag two &&
+		git tag -a -m "tag two" tagtwo
+	)
+'
+
+test_expect_success 'describe atom vs git describe' '
+	(
+		cd describe-repo &&
+
+		${git_for_each_ref} --format="%(objectname)" \
+			refs/tags/ >obj &&
+		while read hash
+		do
+			if desc=$(git describe $hash)
+			then
+				: >expect-contains-good
+			else
+				: >expect-contains-bad
+			fi &&
+			echo "$hash $desc" || return 1
+		done <obj >expect &&
+		test_path_exists expect-contains-good &&
+		test_path_exists expect-contains-bad &&
+
+		${git_for_each_ref} --format="%(objectname) %(describe)" \
+			refs/tags/ >actual 2>err &&
+		test_cmp expect actual &&
+		test_must_be_empty err
+	)
+'
+
+test_expect_success 'describe:tags vs describe --tags' '
+	(
+		cd describe-repo &&
+		git describe --tags >expect &&
+		${git_for_each_ref} --format="%(describe:tags)" \
+				refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
+	(
+		cd describe-repo &&
+
+		# Case 1: We have commits between HEAD and the most
+		#	  recent tag reachable from it
+		test_commit --no-tag file &&
+		git describe --abbrev=14 >expect &&
+		${git_for_each_ref} --format="%(describe:abbrev=14)" \
+			refs/heads/master >actual &&
+		test_cmp expect actual &&
+
+		# Make sure the hash used is at least 14 digits long
+		sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart &&
+		test 15 -le $(wc -c <hexpart) &&
+
+		# Case 2: We have a tag at HEAD, describe directly gives
+		#	  the name of the tag
+		git tag -a -m tagged tagname &&
+		git describe --abbrev=14 >expect &&
+		${git_for_each_ref} --format="%(describe:abbrev=14)" \
+			refs/heads/master >actual &&
+		test_cmp expect actual &&
+		test tagname = $(cat actual)
+	)
+'
+
+test_expect_success 'describe:match=... vs describe --match ...' '
+	(
+		cd describe-repo &&
+		git tag -a -m "tag foo" tag-foo &&
+		git describe --match "*-foo" >expect &&
+		${git_for_each_ref} --format="%(describe:match="*-foo")" \
+			refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'describe:exclude:... vs describe --exclude ...' '
+	(
+		cd describe-repo &&
+		git tag -a -m "tag bar" tag-bar &&
+		git describe --exclude "*-bar" >expect &&
+		${git_for_each_ref} --format="%(describe:exclude="*-bar")" \
+			refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'deref with describe atom' '
+	(
+		cd describe-repo &&
+		cat >expect <<-\EOF &&
+
+		tagname
+		tagname
+		tagname
+
+		tagtwo
+		EOF
+		${git_for_each_ref} --format="%(*describe)" >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'err on bad describe atom arg' '
+	(
+		cd describe-repo &&
+
+		# The bad arg is the only arg passed to describe atom
+		cat >expect <<-\EOF &&
+		fatal: unrecognized %(describe) argument: baz
+		EOF
+		test_must_fail ${git_for_each_ref} --format="%(describe:baz)" \
+			refs/heads/master 2>actual &&
+		test_cmp expect actual &&
+
+		# The bad arg is in the middle of the option string
+		# passed to the describe atom
+		cat >expect <<-\EOF &&
+		fatal: unrecognized %(describe) argument: qux=1,abbrev=14
+		EOF
+		test_must_fail ${git_for_each_ref} \
+			--format="%(describe:tags,qux=1,abbrev=14)" \
+			ref/heads/master 2>actual &&
+		test_cmp expect actual
+	)
+'
+
+cat >expected <<\EOF
+heads/main
+tags/main
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs (strict)' '
+	git config --bool core.warnambiguousrefs true &&
+	git checkout -b newtag &&
+	echo "Using $datestamp" > one &&
+	git add one &&
+	git commit -m "Branch" &&
+	setdate_and_increment &&
+	git tag -m "Tagging at $datestamp" main &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+heads/main
+main
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs (loose)' '
+	git config --bool core.warnambiguousrefs false &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+heads/ambiguous
+ambiguous
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs II (loose)' '
+	git checkout main &&
+	git tag ambiguous testtag^0 &&
+	git branch ambiguous testtag^0 &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'create tag without tagger' '
+	git tag -a -m "Broken tag" taggerless &&
+	git tag -f taggerless $(git cat-file tag taggerless |
+		sed -e "/^tagger /d" |
+		git hash-object --literally --stdin -w -t tag)
+'
+
+test_atom refs/tags/taggerless type 'commit'
+test_atom refs/tags/taggerless tag 'taggerless'
+test_atom refs/tags/taggerless tagger ''
+test_atom refs/tags/taggerless taggername ''
+test_atom refs/tags/taggerless taggeremail ''
+test_atom refs/tags/taggerless taggeremail:trim ''
+test_atom refs/tags/taggerless taggeremail:localpart ''
+test_atom refs/tags/taggerless taggerdate ''
+test_atom refs/tags/taggerless committer ''
+test_atom refs/tags/taggerless committername ''
+test_atom refs/tags/taggerless committeremail ''
+test_atom refs/tags/taggerless committeremail:trim ''
+test_atom refs/tags/taggerless committeremail:localpart ''
+test_atom refs/tags/taggerless committerdate ''
+test_atom refs/tags/taggerless subject 'Broken tag'
+
+test_expect_success 'an unusual tag with an incomplete line' '
+
+	git tag -m "bogo" bogo &&
+	bogo=$(git cat-file tag bogo) &&
+	bogo=$(printf "%s" "$bogo" | git mktag) &&
+	git tag -f bogo "$bogo" &&
+	${git_for_each_ref} --format "%(body)" refs/tags/bogo
+
+'
+
+test_expect_success 'create tag with subject and body content' '
+	cat >>msg <<-\EOF &&
+		the subject line
+
+		first body line
+		second body line
+	EOF
+	git tag -F msg subject-body
+'
+test_atom refs/tags/subject-body subject 'the subject line'
+test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
+test_atom refs/tags/subject-body body 'first body line
+second body line
+'
+test_atom refs/tags/subject-body contents 'the subject line
+
+first body line
+second body line
+'
+
+test_expect_success 'create tag with multiline subject' '
+	cat >msg <<-\EOF &&
+		first subject line
+		second subject line
+
+		first body line
+		second body line
+	EOF
+	git tag -F msg multiline
+'
+test_atom refs/tags/multiline subject 'first subject line second subject line'
+test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
+test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
+test_atom refs/tags/multiline body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:signature ''
+test_atom refs/tags/multiline contents 'first subject line
+second subject line
+
+first body line
+second body line
+'
+
+test_expect_success GPG 'create signed tags' '
+	git tag -s -m "" signed-empty &&
+	git tag -s -m "subject line" signed-short &&
+	cat >msg <<-\EOF &&
+	subject line
+
+	body contents
+	EOF
+	git tag -s -F msg signed-long
+'
+
+sig='-----BEGIN PGP SIGNATURE-----
+-----END PGP SIGNATURE-----
+'
+
+PREREQ=GPG
+test_atom refs/tags/signed-empty subject ''
+test_atom refs/tags/signed-empty subject:sanitize ''
+test_atom refs/tags/signed-empty contents:subject ''
+test_atom refs/tags/signed-empty body "$sig"
+test_atom refs/tags/signed-empty contents:body ''
+test_atom refs/tags/signed-empty contents:signature "$sig"
+test_atom refs/tags/signed-empty contents "$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
+	git cat-file tag refs/tags/signed-empty >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-empty >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_atom refs/tags/signed-short subject 'subject line'
+test_atom refs/tags/signed-short subject:sanitize 'subject-line'
+test_atom refs/tags/signed-short contents:subject 'subject line'
+test_atom refs/tags/signed-short body "$sig"
+test_atom refs/tags/signed-short contents:body ''
+test_atom refs/tags/signed-short contents:signature "$sig"
+test_atom refs/tags/signed-short contents "subject line
+$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
+	git cat-file tag refs/tags/signed-short >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-short >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_atom refs/tags/signed-long subject 'subject line'
+test_atom refs/tags/signed-long subject:sanitize 'subject-line'
+test_atom refs/tags/signed-long contents:subject 'subject line'
+test_atom refs/tags/signed-long body "body contents
+$sig"
+test_atom refs/tags/signed-long contents:body 'body contents
+'
+test_atom refs/tags/signed-long contents:signature "$sig"
+test_atom refs/tags/signed-long contents "subject line
+
+body contents
+$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
+	git cat-file tag refs/tags/signed-long >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-long >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_expect_success 'set up refs pointing to tree and blob' '
+	git update-ref refs/mytrees/first refs/heads/main^{tree} &&
+	git update-ref refs/myblobs/first refs/heads/main:one
+'
+
+test_atom refs/mytrees/first subject ""
+test_atom refs/mytrees/first contents:subject ""
+test_atom refs/mytrees/first body ""
+test_atom refs/mytrees/first contents:body ""
+test_atom refs/mytrees/first contents:signature ""
+test_atom refs/mytrees/first contents ""
+
+test_expect_success 'basic atom: refs/mytrees/first raw' '
+	git cat-file tree refs/mytrees/first >expected &&
+	echo >>expected &&
+	${git_for_each_ref} --format="%(raw)" refs/mytrees/first >actual &&
+	test_cmp expected actual &&
+	git cat-file -s refs/mytrees/first >expected &&
+	${git_for_each_ref} --format="%(raw:size)" refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_atom refs/myblobs/first subject ""
+test_atom refs/myblobs/first contents:subject ""
+test_atom refs/myblobs/first body ""
+test_atom refs/myblobs/first contents:body ""
+test_atom refs/myblobs/first contents:signature ""
+test_atom refs/myblobs/first contents ""
+
+test_expect_success 'basic atom: refs/myblobs/first raw' '
+	git cat-file blob refs/myblobs/first >expected &&
+	echo >>expected &&
+	${git_for_each_ref} --format="%(raw)" refs/myblobs/first >actual &&
+	test_cmp expected actual &&
+	git cat-file -s refs/myblobs/first >expected &&
+	${git_for_each_ref} --format="%(raw:size)" refs/myblobs/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'set up refs pointing to binary blob' '
+	printf "a\0b\0c" >blob1 &&
+	printf "a\0c\0b" >blob2 &&
+	printf "\0a\0b\0c" >blob3 &&
+	printf "abc" >blob4 &&
+	printf "\0 \0 \0 " >blob5 &&
+	printf "\0 \0a\0 " >blob6 &&
+	printf "  " >blob7 &&
+	>blob8 &&
+	obj=$(git hash-object -w blob1) &&
+	git update-ref refs/myblobs/blob1 "$obj" &&
+	obj=$(git hash-object -w blob2) &&
+	git update-ref refs/myblobs/blob2 "$obj" &&
+	obj=$(git hash-object -w blob3) &&
+	git update-ref refs/myblobs/blob3 "$obj" &&
+	obj=$(git hash-object -w blob4) &&
+	git update-ref refs/myblobs/blob4 "$obj" &&
+	obj=$(git hash-object -w blob5) &&
+	git update-ref refs/myblobs/blob5 "$obj" &&
+	obj=$(git hash-object -w blob6) &&
+	git update-ref refs/myblobs/blob6 "$obj" &&
+	obj=$(git hash-object -w blob7) &&
+	git update-ref refs/myblobs/blob7 "$obj" &&
+	obj=$(git hash-object -w blob8) &&
+	git update-ref refs/myblobs/blob8 "$obj"
+'
+
+test_expect_success 'Verify sorts with raw' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob8
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/blob3
+	refs/myblobs/blob7
+	refs/mytrees/first
+	refs/myblobs/first
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob4
+	refs/heads/main
+	EOF
+	${git_for_each_ref} --format="%(refname)" --sort=raw \
+		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Verify sorts with raw:size' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob8
+	refs/myblobs/blob7
+	refs/myblobs/blob4
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob3
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/first
+	refs/mytrees/first
+	refs/heads/main
+	EOF
+	${git_for_each_ref} --format="%(refname)" --sort=raw:size \
+		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:equals)' '
+	cat >expected <<-EOF &&
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	refs/myblobs/blob4
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	EOF
+	${git_for_each_ref} --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
+		refs/myblobs/ refs/heads/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:notequals)' '
+	cat >expected <<-EOF &&
+	refs/heads/ambiguous
+	refs/heads/main
+	refs/heads/newtag
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob3
+	equals
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/blob7
+	refs/myblobs/blob8
+	refs/myblobs/first
+	EOF
+	${git_for_each_ref} --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
+		refs/myblobs/ refs/heads/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'empty raw refs with %(if)' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob1 not empty
+	refs/myblobs/blob2 not empty
+	refs/myblobs/blob3 not empty
+	refs/myblobs/blob4 not empty
+	refs/myblobs/blob5 not empty
+	refs/myblobs/blob6 not empty
+	refs/myblobs/blob7 empty
+	refs/myblobs/blob8 empty
+	refs/myblobs/first not empty
+	EOF
+	${git_for_each_ref} --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
+		refs/myblobs/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '%(raw) with --python must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --python
+'
+
+test_expect_success '%(raw) with --tcl must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --tcl
+'
+
+test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' '
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
+	cmp blob1 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
+	cmp blob3 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
+	cmp blob8 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
+	cmp one actual &&
+	git cat-file tree refs/mytrees/first > expected &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
+	cmp expected actual
+'
+
+test_expect_success '%(raw) with --shell must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --shell
+'
+
+test_expect_success '%(raw) with --shell and --sort=raw must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --sort=raw --shell
+'
+
+test_expect_success '%(raw:size) with --shell' '
+	${git_for_each_ref} --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
+	${git_for_each_ref} --format="%(raw:size)" --shell >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --format compare with cat-file --batch" '
+	git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
+	${git_for_each_ref} --format="%(objectname) %(objecttype) %(objectsize)
+%(raw)" refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'verify sorts with contents:size' '
+	cat >expect <<-\EOF &&
+	refs/heads/main
+	refs/heads/newtag
+	refs/heads/ambiguous
+	EOF
+	${git_for_each_ref} --format="%(refname)" \
+		--sort=contents:size refs/heads/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'set up multiple-sort tags' '
+	for when in 100000 200000
+	do
+		for email in user1 user2
+		do
+			for ref in ref1 ref2
+			do
+				GIT_COMMITTER_DATE="@$when +0000" \
+				GIT_COMMITTER_EMAIL="$email@example.com" \
+				git tag -m "tag $ref-$when-$email" \
+				multi-$ref-$when-$email || return 1
+			done
+		done
+	done
+'
+
+test_expect_success 'Verify sort with multiple keys' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=-refname \
+		--sort=taggeremail \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'equivalent sorts fall back on refname' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '--no-sort cancels the previous sort keys' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=-refname \
+		--sort=taggeremail \
+		--no-sort \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '--no-sort without subsequent --sort prints expected refs' '
+	cat >expected <<-\EOF &&
+	refs/tags/multi-ref1-100000-user1
+	refs/tags/multi-ref1-100000-user2
+	refs/tags/multi-ref1-200000-user1
+	refs/tags/multi-ref1-200000-user2
+	refs/tags/multi-ref2-100000-user1
+	refs/tags/multi-ref2-100000-user2
+	refs/tags/multi-ref2-200000-user1
+	refs/tags/multi-ref2-200000-user2
+	EOF
+
+	# Sort the results with `sort` for a consistent comparison against
+	# expected
+	${git_for_each_ref} \
+		--format="%(refname)" \
+		--no-sort \
+		"refs/tags/multi-*" | sort >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'set up custom date sorting' '
+	# Dates:
+	# - Wed Feb 07 2024 21:34:20 +0000
+	# - Tue Dec 14 1999 00:05:22 +0000
+	# - Fri Jun 04 2021 11:26:51 +0000
+	# - Mon Jan 22 2007 16:44:01 GMT+0000
+	i=1 &&
+	for when in 1707341660 945129922 1622806011 1169484241
+	do
+		GIT_COMMITTER_DATE="@$when +0000" \
+		GIT_COMMITTER_EMAIL="user@example.com" \
+		git tag -m "tag $when" custom-dates-$i &&
+		i=$(($i+1)) || return 1
+	done
+'
+
+test_expect_success 'sort by date defaults to full timestamp' '
+	cat >expected <<-\EOF &&
+	945129922 refs/tags/custom-dates-2
+	1169484241 refs/tags/custom-dates-4
+	1622806011 refs/tags/custom-dates-3
+	1707341660 refs/tags/custom-dates-1
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(creatordate:unix) %(refname)" \
+		--sort=creatordate \
+		"refs/tags/custom-dates-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'sort by custom date format' '
+	cat >expected <<-\EOF &&
+	00:05:22 refs/tags/custom-dates-2
+	11:26:51 refs/tags/custom-dates-3
+	16:44:01 refs/tags/custom-dates-4
+	21:34:20 refs/tags/custom-dates-1
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(creatordate:format:%H:%M:%S) %(refname)" \
+		--sort="creatordate:format:%H:%M:%S" \
+		"refs/tags/custom-dates-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
+	test_when_finished "git checkout main" &&
+	${git_for_each_ref} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	sed -e "s/^\* /  /" actual >expect &&
+	git checkout --orphan orphaned-branch &&
+	${git_for_each_ref} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	test_cmp expect actual
+'
+
+cat >trailers <<EOF
+Reviewed-by: A U Thor <author@example.com>
+Signed-off-by: A U Thor <author@example.com>
+[ v2 updated patch description ]
+Acked-by: A U Thor
+  <author@example.com>
+EOF
+
+unfold () {
+	perl -0pe 's/\n\s+/ /g'
+}
+
+test_expect_success 'set up trailers for next test' '
+	echo "Some contents" > two &&
+	git add two &&
+	git commit -F - <<-EOF
+	trailers: this commit message has trailers
+
+	Some message contents
+
+	$(cat trailers)
+	EOF
+'
+
+test_trailer_option () {
+	if test "$#" -eq 3
+	then
+		prereq="$1"
+		shift
+	fi &&
+	title=$1 option=$2
+	cat >expect
+	test_expect_success $prereq "$title" '
+		${git_for_each_ref} --format="%($option)" refs/heads/main >actual &&
+		test_cmp expect actual &&
+		${git_for_each_ref} --format="%(contents:$option)" refs/heads/main >actual &&
+		test_cmp expect actual
+	'
+}
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' \
+	'trailers:unfold' <<-EOF
+	$(unfold <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only) shows only "key: value" trailers' \
+	'trailers:only' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=no,only=true) shows only "key: value" trailers' \
+	'trailers:only=no,only=true' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=yes) shows only "key: value" trailers' \
+	'trailers:only=yes' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=no) shows all trailers' \
+	'trailers:only=no' <<-EOF
+	$(cat trailers)
+
+	EOF
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:only) and %(trailers:unfold) work together' \
+	'trailers:only,unfold' <<-EOF
+	$(grep -v patch.description <trailers | unfold)
+
+	EOF
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) and %(trailers:only) work together' \
+	'trailers:unfold,only' <<-EOF
+	$(grep -v patch.description <trailers | unfold)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) shows that trailer' \
+	'trailers:key=Signed-off-by' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) is case insensitive' \
+	'trailers:key=SiGned-oFf-bY' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo:) trailing colon also works' \
+	'trailers:key=Signed-off-by:' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) multiple keys' \
+	'trailers:key=Reviewed-by:,key=Signed-off-by' <<-EOF
+	Reviewed-by: A U Thor <author@example.com>
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=nonexistent) becomes empty' \
+	'trailers:key=Shined-off-by:' <<-EOF
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) handles multiple lines even if folded' \
+	'trailers:key=Acked-by' <<-EOF
+	$(grep -v patch.description <trailers | grep -v Signed-off-by | grep -v Reviewed-by)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,unfold) properly unfolds' \
+	'trailers:key=Signed-Off-by,unfold' <<-EOF
+	$(unfold <trailers | grep Signed-off-by)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,only=no) also includes nontrailer lines' \
+	'trailers:key=Signed-off-by,only=no' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+	$(grep patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,valueonly) shows only value' \
+	'trailers:key=Signed-off-by,valueonly' <<-EOF
+	A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:separator) changes separator' \
+	'trailers:separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by: A U Thor <author@example.com>,Signed-off-by: A U Thor <author@example.com>
+	EOF
+
+test_trailer_option '%(trailers:key_value_separator) changes key-value separator' \
+	'trailers:key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by,A U Thor <author@example.com>
+	Signed-off-by,A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:separator,key_value_separator) changes both separators' \
+	'trailers:separator=%x2C,key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by,A U Thor <author@example.com>,Signed-off-by,A U Thor <author@example.com>
+	EOF
+
+test_expect_success 'multiple %(trailers) use their own options' '
+	git tag -F - tag-with-trailers <<-\EOF &&
+	body
+
+	one: foo
+	one: bar
+	two: baz
+	two: qux
+	EOF
+	t1="%(trailers:key=one,key_value_separator=W,separator=X)" &&
+	t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" &&
+	${git_for_each_ref} --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
+	cat >expect <<-\EOF &&
+	oneWfooXoneWbar
+	twoYbazZtwoYqux
+	EOF
+	test_cmp expect actual
+'
+
+test_failing_trailer_option () {
+	title=$1 option=$2
+	cat >expect
+	test_expect_success "$title" '
+		# error message cannot be checked under i18n
+		test_must_fail ${git_for_each_ref} --format="%($option)" refs/heads/main 2>actual &&
+		test_cmp expect actual &&
+		test_must_fail ${git_for_each_ref} --format="%(contents:$option)" refs/heads/main 2>actual &&
+		test_cmp expect actual
+	'
+}
+
+test_failing_trailer_option '%(trailers) rejects unknown trailers arguments' \
+	'trailers:unsupported' <<-\EOF
+	fatal: unknown %(trailers) argument: unsupported
+	EOF
+
+test_failing_trailer_option '%(trailers:key) without value is error' \
+	'trailers:key' <<-\EOF
+	fatal: expected %(trailers:key=<value>)
+	EOF
+
+test_expect_success 'if arguments, %(contents:trailers) shows error if colon is missing' '
+	cat >expect <<-EOF &&
+	fatal: unrecognized %(contents) argument: trailersonly
+	EOF
+	test_must_fail ${git_for_each_ref} --format="%(contents:trailersonly)" 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'basic atom: head contents:trailers' '
+	${git_for_each_ref} --format="%(contents:trailers)" refs/heads/main >actual &&
+	sanitize_pgp <actual >actual.clean &&
+	# ${git_for_each_ref} ends with a blank line
+	cat >expect <<-EOF &&
+	$(cat trailers)
+
+	EOF
+	test_cmp expect actual.clean
+'
+
+test_expect_success 'basic atom: rest must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(rest)" refs/heads/main
+'
+
+test_expect_success 'HEAD atom does not take arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(HEAD:foo)" 2>err &&
+	echo "fatal: %(HEAD) does not take arguments" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'subject atom rejects unknown arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(subject:foo)" 2>err &&
+	echo "fatal: unrecognized %(subject) argument: foo" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'refname atom rejects unknown arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:foo)" 2>err &&
+	echo "fatal: unrecognized %(refname) argument: foo" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'trailer parsing not fooled by --- line' '
+	git commit --allow-empty -F - <<-\EOF &&
+	this is the subject
+
+	This is the body. The message has a "---" line which would confuse a
+	message+patch parser. But here we know we have only a commit message,
+	so we get it right.
+
+	trailer: wrong
+	---
+	This is more body.
+
+	trailer: right
+	EOF
+
+	{
+		echo "trailer: right" &&
+		echo
+	} >expect &&
+	${git_for_each_ref} --format="%(trailers)" refs/heads/main >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'Add symbolic ref for the following tests' '
+	git symbolic-ref refs/heads/sym refs/heads/main
+'
+
+cat >expected <<EOF
+refs/heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref) atom' '
+	${git_for_each_ref} --format="%(symref)" refs/heads/sym >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref:short) atom' '
+	${git_for_each_ref} --format="%(symref:short)" refs/heads/sym >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+main
+heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref:lstrip) atom' '
+	${git_for_each_ref} --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual &&
+
+	${git_for_each_ref} --format="%(symref:strip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+refs
+refs/heads
+EOF
+
+test_expect_success 'Verify usage of %(symref:rstrip) atom' '
+	${git_for_each_ref} --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual
+'
+
+test_expect_success ':remotename and :remoteref' '
+	git init remote-tests &&
+	(
+		cd remote-tests &&
+		test_commit initial &&
+		git branch -M main &&
+		git remote add from fifth.coffee:blub &&
+		git config branch.main.remote from &&
+		git config branch.main.merge refs/heads/stable &&
+		git remote add to southridge.audio:repo &&
+		git config remote.to.push "refs/heads/*:refs/heads/pushed/*" &&
+		git config branch.main.pushRemote to &&
+		for pair in "%(upstream)=refs/remotes/from/stable" \
+			"%(upstream:remotename)=from" \
+			"%(upstream:remoteref)=refs/heads/stable" \
+			"%(push)=refs/remotes/to/pushed/main" \
+			"%(push:remotename)=to" \
+			"%(push:remoteref)=refs/heads/pushed/main"
+		do
+			echo "${pair#*=}" >expect &&
+			${git_for_each_ref} --format="${pair%=*}" \
+				refs/heads/main >actual &&
+			test_cmp expect actual || exit 1
+		done &&
+		git branch push-simple &&
+		git config branch.push-simple.pushRemote from &&
+		actual="$(${git_for_each_ref} \
+			--format="%(push:remotename),%(push:remoteref)" \
+			refs/heads/push-simple)" &&
+		test from, = "$actual"
+	)
+'
+
+test_expect_success "${git_for_each_ref} --ignore-case ignores case" '
+	${git_for_each_ref} --format="%(refname)" refs/heads/MAIN >actual &&
+	test_must_be_empty actual &&
+
+	echo refs/heads/main >expect &&
+	${git_for_each_ref} --format="%(refname)" --ignore-case \
+		refs/heads/MAIN >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --omit-empty works" '
+	${git_for_each_ref} --format="%(refname)" >actual &&
+	test_line_count -gt 1 actual &&
+	${git_for_each_ref} --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
+	echo refs/heads/main >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --ignore-case works on multiple sort keys" '
+	# name refs numerically to avoid case-insensitive filesystem conflicts
+	nr=0 &&
+	for email in a A b B
+	do
+		for subject in a A b B
+		do
+			GIT_COMMITTER_EMAIL="$email@example.com" \
+			git tag -m "tag $subject" icase-$(printf %02d $nr) &&
+			nr=$((nr+1))||
+			return 1
+		done
+	done &&
+	${git_for_each_ref} --ignore-case \
+		--format="%(taggeremail) %(subject) %(refname)" \
+		--sort=refname \
+		--sort=subject \
+		--sort=taggeremail \
+		refs/tags/icase-* >actual &&
+	cat >expect <<-\EOF &&
+	<a@example.com> tag a refs/tags/icase-00
+	<a@example.com> tag A refs/tags/icase-01
+	<A@example.com> tag a refs/tags/icase-04
+	<A@example.com> tag A refs/tags/icase-05
+	<a@example.com> tag b refs/tags/icase-02
+	<a@example.com> tag B refs/tags/icase-03
+	<A@example.com> tag b refs/tags/icase-06
+	<A@example.com> tag B refs/tags/icase-07
+	<b@example.com> tag a refs/tags/icase-08
+	<b@example.com> tag A refs/tags/icase-09
+	<B@example.com> tag a refs/tags/icase-12
+	<B@example.com> tag A refs/tags/icase-13
+	<b@example.com> tag b refs/tags/icase-10
+	<b@example.com> tag B refs/tags/icase-11
+	<B@example.com> tag b refs/tags/icase-14
+	<B@example.com> tag B refs/tags/icase-15
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} reports broken tags" '
+	git tag -m "good tag" broken-tag-good HEAD &&
+	git cat-file tag broken-tag-good >good &&
+	sed s/commit/blob/ <good >bad &&
+	bad=$(git hash-object -w -t tag bad) &&
+	git update-ref refs/tags/broken-tag-bad $bad &&
+	test_must_fail ${git_for_each_ref} --format="%(*objectname)" \
+		refs/tags/broken-tag-*
+'
+
+test_expect_success 'set up tag with signature and no blank lines' '
+	git tag -F - fake-sig-no-blanks <<-\EOF
+	this is the subject
+	-----BEGIN PGP SIGNATURE-----
+	not a real signature, but we just care about the
+	subject/body parsing. It is important here that
+	there are no blank lines in the signature.
+	-----END PGP SIGNATURE-----
+	EOF
+'
+
+test_atom refs/tags/fake-sig-no-blanks contents:subject 'this is the subject'
+test_atom refs/tags/fake-sig-no-blanks contents:body ''
+test_atom refs/tags/fake-sig-no-blanks contents:signature "$sig"
+
+test_expect_success 'set up tag with CRLF signature' '
+	append_cr <<-\EOF |
+	this is the subject
+	-----BEGIN PGP SIGNATURE-----
+
+	not a real signature, but we just care about
+	the subject/body parsing. It is important here
+	that there is a blank line separating this
+	from the signature header.
+	-----END PGP SIGNATURE-----
+	EOF
+	git tag -F - --cleanup=verbatim fake-sig-crlf
+'
+
+test_atom refs/tags/fake-sig-crlf contents:subject 'this is the subject'
+test_atom refs/tags/fake-sig-crlf contents:body ''
+
+# CRLF is retained in the signature, so we have to pass our expected value
+# through append_cr. But test_atom requires a shell string, which means command
+# substitution, and the shell will strip trailing newlines from the output of
+# the substitution. Hack around it by adding and then removing a dummy line.
+sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
+sig_crlf=${sig_crlf%dummy}
+test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
+
+test_expect_success 'set up tag with signature and trailers' '
+	git tag -F - fake-sig-trailer <<-\EOF
+	this is the subject
+
+	this is the body
+
+	My-Trailer: foo
+	-----BEGIN PGP SIGNATURE-----
+
+	not a real signature, but we just care about the
+	subject/body/trailer parsing.
+	-----END PGP SIGNATURE-----
+	EOF
+'
+
+# use "separator=" here to suppress the terminating newline
+test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo'
+
+test_expect_success "${git_for_each_ref} --stdin: empty" '
+	>in &&
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	${git_for_each_ref} --format="%(refname)" >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --stdin: fails if extra args" '
+	>in &&
+	test_must_fail ${git_for_each_ref} --format="%(refname)" \
+		--stdin refs/heads/extra <in 2>err &&
+	grep "unknown arguments supplied with --stdin" err
+'
+
+test_expect_success "${git_for_each_ref} --stdin: matches" '
+	cat >in <<-EOF &&
+	refs/tags/multi*
+	refs/heads/amb*
+	EOF
+
+	cat >expect <<-EOF &&
+	refs/heads/ambiguous
+	refs/tags/multi-ref1-100000-user1
+	refs/tags/multi-ref1-100000-user2
+	refs/tags/multi-ref1-200000-user1
+	refs/tags/multi-ref1-200000-user2
+	refs/tags/multi-ref2-100000-user1
+	refs/tags/multi-ref2-100000-user2
+	refs/tags/multi-ref2-200000-user1
+	refs/tags/multi-ref2-200000-user2
+	refs/tags/multiline
+	EOF
+
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} with non-existing refs" '
+	cat >in <<-EOF &&
+	refs/heads/this-ref-does-not-exist
+	refs/tags/bogus
+	EOF
+
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	test_must_be_empty actual &&
+
+	xargs ${git_for_each_ref} --format="%(refname)" <in >actual &&
+	test_must_be_empty actual
+'
+
+test_expect_success "${git_for_each_ref} with nested tags" '
+	git tag -am "Normal tag" nested/base HEAD &&
+	git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
+	git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
+
+	head_oid="$(git rev-parse HEAD)" &&
+	base_tag_oid="$(git rev-parse refs/tags/nested/base)" &&
+	nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" &&
+	nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" &&
+
+	cat >expect <<-EOF &&
+	refs/tags/nested/base $base_tag_oid tag $head_oid commit
+	refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit
+	refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
+		refs/tags/nested/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'is-base atom with non-commits' '
+	${git_for_each_ref} --format="%(is-base:HEAD) %(refname)" >out 2>err &&
+	grep "(HEAD) refs/heads/main" out &&
+
+	test_line_count = 2 err &&
+	grep "error: object .* is a commit, not a blob" err &&
+	grep "error: bad tag pointer to" err
+'
+
+GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
+TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
+
+test_expect_success GPG 'setup for signature atom using gpg' '
+	git checkout -b signed &&
+
+	test_when_finished "test_unconfig commit.gpgSign" &&
+
+	echo "1" >file &&
+	git add file &&
+	test_tick &&
+	git commit -S -m "file: 1" &&
+	git tag first-signed &&
+
+	echo "2" >file &&
+	test_tick &&
+	git commit -a -m "file: 2" &&
+	git tag second-unsigned &&
+
+	git config commit.gpgSign 1 &&
+	echo "3" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 3" &&
+	git tag third-unsigned &&
+
+	test_tick &&
+	git rebase -f HEAD^^ && git tag second-signed HEAD^ &&
+	git tag third-signed &&
+
+	echo "4" >file &&
+	test_tick &&
+	git commit -a -SB7227189 -m "file: 4" &&
+	git tag fourth-signed &&
+
+	echo "5" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 5" &&
+	git tag fifth-unsigned &&
+
+	echo "6" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 6" &&
+
+	test_tick &&
+	git rebase -f HEAD^^ &&
+	git tag fifth-signed HEAD^ &&
+	git tag sixth-signed &&
+
+	echo "7" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 7" &&
+	git tag seventh-unsigned
+'
+
+test_expect_success GPGSSH 'setup for signature atom using ssh' '
+	test_when_finished "test_unconfig gpg.format user.signingkey" &&
+
+	test_config gpg.format ssh &&
+	test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+	echo "8" >file &&
+	test_tick &&
+	git add file &&
+	git commit -S -m "file: 8" &&
+	git tag eighth-signed-ssh
+'
+
+test_expect_success GPG2 'bare signature atom' '
+	git verify-commit first-signed 2>expect &&
+	echo  >>expect &&
+	${git_for_each_ref} refs/tags/first-signed \
+		--format="%(signature)" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show good signature with custom format' '
+	git verify-commit first-signed &&
+	cat >expect <<-\EOF &&
+	G
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	EOF
+	${git_for_each_ref} refs/tags/first-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+test_expect_success GPGSSH 'show good signature with custom format with ssh' '
+	test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+	FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+	cat >expect.tmpl <<-\EOF &&
+	G
+	FINGERPRINT
+	principal with number 1
+	FINGERPRINT
+
+	EOF
+	sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
+	${git_for_each_ref} refs/tags/eighth-signed-ssh \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'signature atom with grade option and bad signature' '
+	git cat-file commit third-signed >raw &&
+	sed -e "s/^file: 3/file: 3 forged/" raw >forged1 &&
+	FORGED1=$(git hash-object -w -t commit forged1) &&
+	git update-ref refs/tags/third-signed "$FORGED1" &&
+	test_must_fail git verify-commit "$FORGED1" &&
+
+	cat >expect <<-\EOF &&
+	B
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+
+
+	EOF
+	${git_for_each_ref} refs/tags/third-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with custom format' '
+	cat >expect <<-\EOF &&
+	U
+	65A0EEA02E30CAD7
+	Eris Discordia <discord@example.net>
+	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+	D4BE22311AD3131E5EDA29A461092E85B7227189
+	EOF
+	${git_for_each_ref} refs/tags/fourth-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with undefined trust level' '
+	cat >expect <<-\EOF &&
+	undefined
+	65A0EEA02E30CAD7
+	Eris Discordia <discord@example.net>
+	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+	D4BE22311AD3131E5EDA29A461092E85B7227189
+	EOF
+	${git_for_each_ref} refs/tags/fourth-signed \
+		--format="$TRUSTLEVEL_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with ultimate trust level' '
+	cat >expect <<-\EOF &&
+	ultimate
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	EOF
+	${git_for_each_ref} refs/tags/sixth-signed \
+		--format="$TRUSTLEVEL_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show unknown signature with custom format' '
+	cat >expect <<-\EOF &&
+	E
+	13B6F51ECDDE430D
+
+
+
+	EOF
+	GNUPGHOME="$GNUPGHOME_NOT_USED" ${git_for_each_ref} \
+		refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show lack of signature with custom format' '
+	cat >expect <<-\EOF &&
+	N
+
+
+
+
+	EOF
+	${git_for_each_ref} refs/tags/seventh-unsigned \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index ce9af79ab1..1d9809114d 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -6,2150 +6,14 @@
 test_description='for-each-ref test'
 
 . ./test-lib.sh
-GNUPGHOME_NOT_USED=$GNUPGHOME
-. "$TEST_DIRECTORY"/lib-gpg.sh
-. "$TEST_DIRECTORY"/lib-terminal.sh
 
-# Mon Jul 3 23:18:43 2006 +0000
-datestamp=1151968723
-setdate_and_increment () {
-    GIT_COMMITTER_DATE="$datestamp +0200"
-    datestamp=$(expr "$datestamp" + 1)
-    GIT_AUTHOR_DATE="$datestamp +0200"
-    datestamp=$(expr "$datestamp" + 1)
-    export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
-}
-
-test_object_file_size () {
-	oid=$(git rev-parse "$1")
-	path=".git/objects/$(test_oid_to_path $oid)"
-	test_file_size "$path"
-}
-
-test_expect_success setup '
-	# setup .mailmap
-	cat >.mailmap <<-EOF &&
-	A Thor <athor@example.com> A U Thor <author@example.com>
-	C Mitter <cmitter@example.com> C O Mitter <committer@example.com>
-	EOF
-
-	setdate_and_increment &&
-	echo "Using $datestamp" > one &&
-	git add one &&
-	git commit -m "Initial" &&
-	git branch -M main &&
-	setdate_and_increment &&
-	git tag -a -m "Tagging at $datestamp" testtag &&
-	git update-ref refs/remotes/origin/main main &&
-	git remote add origin nowhere &&
-	git config branch.main.remote origin &&
-	git config branch.main.merge refs/heads/main &&
-	git remote add myfork elsewhere &&
-	git config remote.pushdefault myfork &&
-	git config push.default current
-'
-
-test_atom () {
-	case "$1" in
-		head) ref=refs/heads/main ;;
-		 tag) ref=refs/tags/testtag ;;
-		 sym) ref=refs/heads/sym ;;
-		   *) ref=$1 ;;
-	esac
-	format=$2
-	test_do=test_expect_${4:-success}
-
-	printf '%s\n' "$3" >expected
-	$test_do $PREREQ "basic atom: $ref $format" '
-		git for-each-ref --format="%($format)" "$ref" >actual &&
-		sanitize_pgp <actual >actual.clean &&
-		test_cmp expected actual.clean
-	'
-
-	# Automatically test "contents:size" atom after testing "contents"
-	if test "$format" = "contents"
-	then
-		# for commit leg, $3 is changed there
-		expect=$(printf '%s' "$3" | wc -c)
-		$test_do $PREREQ "basic atom: $ref contents:size" '
-			type=$(git cat-file -t "$ref") &&
-			case $type in
-			tag)
-				# We cannot use $3 as it expects sanitize_pgp to run
-				git cat-file tag $ref >out &&
-				expect=$(tail -n +6 out | wc -c) &&
-				rm -f out ;;
-			tree | blob)
-				expect="" ;;
-			commit)
-				: "use the calculated expect" ;;
-			*)
-				BUG "unknown object type" ;;
-			esac &&
-			# Leave $expect unquoted to lose possible leading whitespaces
-			echo $expect >expected &&
-			git for-each-ref --format="%(contents:size)" "$ref" >actual &&
-			test_cmp expected actual
-		'
-	fi
-}
-
-hexlen=$(test_oid hexsz)
-
-test_atom head refname refs/heads/main
-test_atom head refname: refs/heads/main
-test_atom head refname:short main
-test_atom head refname:lstrip=1 heads/main
-test_atom head refname:lstrip=2 main
-test_atom head refname:lstrip=-1 main
-test_atom head refname:lstrip=-2 heads/main
-test_atom head refname:rstrip=1 refs/heads
-test_atom head refname:rstrip=2 refs
-test_atom head refname:rstrip=-1 refs
-test_atom head refname:rstrip=-2 refs/heads
-test_atom head refname:strip=1 heads/main
-test_atom head refname:strip=2 main
-test_atom head refname:strip=-1 main
-test_atom head refname:strip=-2 heads/main
-test_atom head upstream refs/remotes/origin/main
-test_atom head upstream:short origin/main
-test_atom head upstream:lstrip=2 origin/main
-test_atom head upstream:lstrip=-2 origin/main
-test_atom head upstream:rstrip=2 refs/remotes
-test_atom head upstream:rstrip=-2 refs/remotes
-test_atom head upstream:strip=2 origin/main
-test_atom head upstream:strip=-2 origin/main
-test_atom head push refs/remotes/myfork/main
-test_atom head push:short myfork/main
-test_atom head push:lstrip=1 remotes/myfork/main
-test_atom head push:lstrip=-1 main
-test_atom head push:rstrip=1 refs/remotes/myfork
-test_atom head push:rstrip=-1 refs
-test_atom head push:strip=1 remotes/myfork/main
-test_atom head push:strip=-1 main
-test_atom head objecttype commit
-test_atom head objectsize $((131 + hexlen))
-test_atom head objectsize:disk $(test_object_file_size refs/heads/main)
-test_atom head deltabase $ZERO_OID
-test_atom head objectname $(git rev-parse refs/heads/main)
-test_atom head objectname:short $(git rev-parse --short refs/heads/main)
-test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
-test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
-test_atom head tree $(git rev-parse refs/heads/main^{tree})
-test_atom head tree:short $(git rev-parse --short refs/heads/main^{tree})
-test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/main^{tree})
-test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/main^{tree})
-test_atom head parent ''
-test_atom head parent:short ''
-test_atom head parent:short=1 ''
-test_atom head parent:short=10 ''
-test_atom head numparent 0
-test_atom head object ''
-test_atom head type ''
-test_atom head raw "$(git cat-file commit refs/heads/main)
-"
-test_atom head '*objectname' ''
-test_atom head '*objecttype' ''
-test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
-test_atom head authorname 'A U Thor'
-test_atom head authorname:mailmap 'A Thor'
-test_atom head authoremail '<author@example.com>'
-test_atom head authoremail:trim 'author@example.com'
-test_atom head authoremail:localpart 'author'
-test_atom head authoremail:trim,localpart 'author'
-test_atom head authoremail:mailmap '<athor@example.com>'
-test_atom head authoremail:mailmap,trim 'athor@example.com'
-test_atom head authoremail:trim,mailmap 'athor@example.com'
-test_atom head authoremail:mailmap,localpart 'athor'
-test_atom head authoremail:localpart,mailmap 'athor'
-test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor'
-test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
-test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
-test_atom head committername 'C O Mitter'
-test_atom head committername:mailmap 'C Mitter'
-test_atom head committeremail '<committer@example.com>'
-test_atom head committeremail:trim 'committer@example.com'
-test_atom head committeremail:localpart 'committer'
-test_atom head committeremail:localpart,trim 'committer'
-test_atom head committeremail:mailmap '<cmitter@example.com>'
-test_atom head committeremail:mailmap,trim 'cmitter@example.com'
-test_atom head committeremail:trim,mailmap 'cmitter@example.com'
-test_atom head committeremail:mailmap,localpart 'cmitter'
-test_atom head committeremail:localpart,mailmap 'cmitter'
-test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter'
-test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
-test_atom head tag ''
-test_atom head tagger ''
-test_atom head taggername ''
-test_atom head taggeremail ''
-test_atom head taggeremail:trim ''
-test_atom head taggeremail:localpart ''
-test_atom head taggerdate ''
-test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
-test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
-test_atom head subject 'Initial'
-test_atom head subject:sanitize 'Initial'
-test_atom head contents:subject 'Initial'
-test_atom head body ''
-test_atom head contents:body ''
-test_atom head contents:signature ''
-test_atom head contents 'Initial
-'
-test_atom head HEAD '*'
-
-test_atom tag refname refs/tags/testtag
-test_atom tag refname:short testtag
-test_atom tag upstream ''
-test_atom tag push ''
-test_atom tag objecttype tag
-test_atom tag objectsize $((114 + hexlen))
-test_atom tag objectsize:disk $(test_object_file_size refs/tags/testtag)
-test_atom tag '*objectsize:disk' $(test_object_file_size refs/heads/main)
-test_atom tag deltabase $ZERO_OID
-test_atom tag '*deltabase' $ZERO_OID
-test_atom tag objectname $(git rev-parse refs/tags/testtag)
-test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
-test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
-test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
-test_atom tag tree ''
-test_atom tag tree:short ''
-test_atom tag tree:short=1 ''
-test_atom tag tree:short=10 ''
-test_atom tag parent ''
-test_atom tag parent:short ''
-test_atom tag parent:short=1 ''
-test_atom tag parent:short=10 ''
-test_atom tag numparent ''
-test_atom tag object $(git rev-parse refs/tags/testtag^0)
-test_atom tag type 'commit'
-test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{})
-test_atom tag '*objecttype' 'commit'
-test_atom tag author ''
-test_atom tag authorname ''
-test_atom tag authorname:mailmap ''
-test_atom tag authoremail ''
-test_atom tag authoremail:trim ''
-test_atom tag authoremail:localpart ''
-test_atom tag authoremail:trim,localpart ''
-test_atom tag authoremail:mailmap ''
-test_atom tag authoremail:mailmap,trim ''
-test_atom tag authoremail:trim,mailmap ''
-test_atom tag authoremail:mailmap,localpart ''
-test_atom tag authoremail:localpart,mailmap ''
-test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim ''
-test_atom tag authordate ''
-test_atom tag committer ''
-test_atom tag committername ''
-test_atom tag committername:mailmap ''
-test_atom tag committeremail ''
-test_atom tag committeremail:trim ''
-test_atom tag committeremail:localpart ''
-test_atom tag committeremail:localpart,trim ''
-test_atom tag committeremail:mailmap ''
-test_atom tag committeremail:mailmap,trim ''
-test_atom tag committeremail:trim,mailmap ''
-test_atom tag committeremail:mailmap,localpart ''
-test_atom tag committeremail:localpart,mailmap ''
-test_atom tag committeremail:trim,mailmap,trim,trim,localpart ''
-test_atom tag committerdate ''
-test_atom tag tag 'testtag'
-test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
-test_atom tag taggername 'C O Mitter'
-test_atom tag taggername:mailmap 'C Mitter'
-test_atom tag taggeremail '<committer@example.com>'
-test_atom tag taggeremail:trim 'committer@example.com'
-test_atom tag taggeremail:localpart 'committer'
-test_atom tag taggeremail:trim,localpart 'committer'
-test_atom tag taggeremail:mailmap '<cmitter@example.com>'
-test_atom tag taggeremail:mailmap,trim 'cmitter@example.com'
-test_atom tag taggeremail:trim,mailmap 'cmitter@example.com'
-test_atom tag taggeremail:mailmap,localpart 'cmitter'
-test_atom tag taggeremail:localpart,mailmap 'cmitter'
-test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter'
-test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
-test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
-test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
-test_atom tag subject 'Tagging at 1151968727'
-test_atom tag subject:sanitize 'Tagging-at-1151968727'
-test_atom tag contents:subject 'Tagging at 1151968727'
-test_atom tag body ''
-test_atom tag contents:body ''
-test_atom tag contents:signature ''
-test_atom tag contents 'Tagging at 1151968727
-'
-test_atom tag HEAD ' '
-
-test_expect_success 'basic atom: refs/tags/testtag *raw' '
-	git cat-file commit refs/tags/testtag^{} >expected &&
-	git for-each-ref --format="%(*raw)" refs/tags/testtag >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_expect_success 'Check invalid atoms names are errors' '
-	test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
-'
-
-test_expect_success 'for-each-ref does not crash with -h' '
+test_expect_success "for-each-ref does not crash with -h" '
 	test_expect_code 129 git for-each-ref -h >usage &&
 	test_grep "[Uu]sage: git for-each-ref " usage &&
 	test_expect_code 129 nongit git for-each-ref -h >usage &&
 	test_grep "[Uu]sage: git for-each-ref " usage
 '
 
-test_expect_success 'Check format specifiers are ignored in naming date atoms' '
-	git for-each-ref --format="%(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate) %(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate:default)" refs/heads
-'
-
-test_expect_success 'Check valid format specifiers for date fields' '
-	git for-each-ref --format="%(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:relative)" refs/heads &&
-	git for-each-ref --format="%(authordate:short)" refs/heads &&
-	git for-each-ref --format="%(authordate:local)" refs/heads &&
-	git for-each-ref --format="%(authordate:iso8601)" refs/heads &&
-	git for-each-ref --format="%(authordate:rfc2822)" refs/heads
-'
-
-test_expect_success 'Check invalid format specifiers are errors' '
-	test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads
-'
-
-test_expect_success 'arguments to %(objectname:short=) must be positive integers' '
-	test_must_fail git for-each-ref --format="%(objectname:short=0)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=-1)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=foo)"
-'
-
-test_bad_atom () {
-	case "$1" in
-	head) ref=refs/heads/main ;;
-	 tag) ref=refs/tags/testtag ;;
-	 sym) ref=refs/heads/sym ;;
-	   *) ref=$1 ;;
-	esac
-	format=$2
-	test_do=test_expect_${4:-success}
-
-	printf '%s\n' "$3" >expect
-	$test_do $PREREQ "err basic atom: $ref $format" '
-		test_must_fail git for-each-ref \
-			--format="%($format)" "$ref" 2>error &&
-		test_cmp expect error
-	'
-}
-
-test_bad_atom head 'authoremail:foo' \
-	'fatal: unrecognized %(authoremail) argument: foo'
-
-test_bad_atom head 'authoremail:mailmap,trim,bar' \
-	'fatal: unrecognized %(authoremail) argument: bar'
-
-test_bad_atom head 'authoremail:trim,' \
-	'fatal: unrecognized %(authoremail) argument: '
-
-test_bad_atom head 'authoremail:mailmaptrim' \
-	'fatal: unrecognized %(authoremail) argument: trim'
-
-test_bad_atom head 'committeremail: ' \
-	'fatal: unrecognized %(committeremail) argument:  '
-
-test_bad_atom head 'committeremail: trim,foo' \
-	'fatal: unrecognized %(committeremail) argument:  trim,foo'
-
-test_bad_atom head 'committeremail:mailmap,localpart ' \
-	'fatal: unrecognized %(committeremail) argument:  '
-
-test_bad_atom head 'committeremail:trim_localpart' \
-	'fatal: unrecognized %(committeremail) argument: _localpart'
-
-test_bad_atom head 'committeremail:localpart,,,trim' \
-	'fatal: unrecognized %(committeremail) argument: ,,trim'
-
-test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \
-	'fatal: unrecognized %(taggeremail) argument:  foo '
-
-test_bad_atom tag 'taggeremail:trim,localpart,' \
-	'fatal: unrecognized %(taggeremail) argument: '
-
-test_bad_atom tag 'taggeremail:mailmap;localpart trim' \
-	'fatal: unrecognized %(taggeremail) argument: ;localpart trim'
-
-test_bad_atom tag 'taggeremail:localpart trim' \
-	'fatal: unrecognized %(taggeremail) argument:  trim'
-
-test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \
-	'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim'
-
-test_date () {
-	f=$1 &&
-	committer_date=$2 &&
-	author_date=$3 &&
-	tagger_date=$4 &&
-	cat >expected <<-EOF &&
-	'refs/heads/main' '$committer_date' '$author_date'
-	'refs/tags/testtag' '$tagger_date'
-	EOF
-	(
-		git for-each-ref --shell \
-			--format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
-			refs/heads &&
-		git for-each-ref --shell \
-			--format="%(refname) %(taggerdate${f:+:$f})" \
-			refs/tags
-	) >actual &&
-	test_cmp expected actual
-}
-
-test_expect_success 'Check unformatted date fields output' '
-	test_date "" \
-		"Tue Jul 4 01:18:43 2006 +0200" \
-		"Tue Jul 4 01:18:44 2006 +0200" \
-		"Tue Jul 4 01:18:45 2006 +0200"
-'
-
-test_expect_success 'Check format "default" formatted date fields output' '
-	test_date default \
-		"Tue Jul 4 01:18:43 2006 +0200" \
-		"Tue Jul 4 01:18:44 2006 +0200" \
-		"Tue Jul 4 01:18:45 2006 +0200"
-'
-
-test_expect_success 'Check format "default-local" date fields output' '
-	test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006"
-'
-
-# Don't know how to do relative check because I can't know when this script
-# is going to be run and can't fake the current time to git, and hence can't
-# provide expected output.  Instead, I'll just make sure that "relative"
-# doesn't exit in error
-test_expect_success 'Check format "relative" date fields output' '
-	f=relative &&
-	(git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-	git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
-'
-
-# We just check that this is the same as "relative" for now.
-test_expect_success 'Check format "relative-local" date fields output' '
-	test_date relative-local \
-		"$(git for-each-ref --format="%(committerdate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(authordate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(taggerdate:relative)" refs/tags)"
-'
-
-test_expect_success 'Check format "short" date fields output' '
-	test_date short 2006-07-04 2006-07-04 2006-07-04
-'
-
-test_expect_success 'Check format "short-local" date fields output' '
-	test_date short-local 2006-07-03 2006-07-03 2006-07-03
-'
-
-test_expect_success 'Check format "local" date fields output' '
-	test_date local \
-		"Mon Jul 3 23:18:43 2006" \
-		"Mon Jul 3 23:18:44 2006" \
-		"Mon Jul 3 23:18:45 2006"
-'
-
-test_expect_success 'Check format "iso8601" date fields output' '
-	test_date iso8601 \
-		"2006-07-04 01:18:43 +0200" \
-		"2006-07-04 01:18:44 +0200" \
-		"2006-07-04 01:18:45 +0200"
-'
-
-test_expect_success 'Check format "iso8601-local" date fields output' '
-	test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000"
-'
-
-test_expect_success 'Check format "rfc2822" date fields output' '
-	test_date rfc2822 \
-		"Tue, 4 Jul 2006 01:18:43 +0200" \
-		"Tue, 4 Jul 2006 01:18:44 +0200" \
-		"Tue, 4 Jul 2006 01:18:45 +0200"
-'
-
-test_expect_success 'Check format "rfc2822-local" date fields output' '
-	test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000"
-'
-
-test_expect_success 'Check format "raw" date fields output' '
-	test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200"
-'
-
-test_expect_success 'Check format "raw-local" date fields output' '
-	test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000"
-'
-
-test_expect_success 'Check format of strftime date fields' '
-	echo "my date is 2006-07-04" >expected &&
-	git for-each-ref \
-	  --format="%(authordate:format:my date is %Y-%m-%d)" \
-	  refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Check format of strftime-local date fields' '
-	echo "my date is 2006-07-03" >expected &&
-	git for-each-ref \
-	  --format="%(authordate:format-local:my date is %Y-%m-%d)" \
-	  refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'exercise strftime with odd fields' '
-	echo >expected &&
-	git for-each-ref --format="%(authordate:format:)" refs/heads >actual &&
-	test_cmp expected actual &&
-	long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" &&
-	echo $long >expected &&
-	git for-each-ref --format="%(authordate:format:$long)" refs/heads >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/heads/main
-refs/remotes/origin/main
-refs/tags/testtag
-EOF
-
-test_expect_success 'Verify ascending sort' '
-	git for-each-ref --format="%(refname)" --sort=refname >actual &&
-	test_cmp expected actual
-'
-
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/remotes/origin/main
-refs/heads/main
-EOF
-
-test_expect_success 'Verify descending sort' '
-	git for-each-ref --format="%(refname)" --sort=-refname >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Give help even with invalid sort atoms' '
-	test_expect_code 129 git for-each-ref --sort=bogus -h >actual 2>&1 &&
-	grep "^usage: git for-each-ref" actual
-'
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/tags/testtag-2
-EOF
-
-test_expect_success 'exercise patterns with prefixes' '
-	git tag testtag-2 &&
-	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/testtag refs/tags/testtag-2 >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/tags/testtag-2
-EOF
-
-test_expect_success 'exercise glob patterns with prefixes' '
-	git tag testtag-2 &&
-	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/testtag "refs/tags/testtag-*" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/bar
-refs/tags/baz
-refs/tags/testtag
-EOF
-
-test_expect_success 'exercise patterns with prefix exclusions' '
-	for tag in foo/one foo/two foo/three bar baz
-	do
-		git tag "$tag" || return 1
-	done &&
-	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/ --exclude=refs/tags/foo >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/bar
-refs/tags/baz
-refs/tags/foo/one
-refs/tags/testtag
-EOF
-
-test_expect_success 'exercise patterns with pattern exclusions' '
-	for tag in foo/one foo/two foo/three bar baz
-	do
-		git tag "$tag" || return 1
-	done &&
-	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/ --exclude="refs/tags/foo/t*" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-'refs/heads/main'
-'refs/remotes/origin/main'
-'refs/tags/testtag'
-EOF
-
-test_expect_success 'Quoting style: shell' '
-	git for-each-ref --shell --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Quoting style: perl' '
-	git for-each-ref --perl --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Quoting style: python' '
-	git for-each-ref --python --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-"refs/heads/main"
-"refs/remotes/origin/main"
-"refs/tags/testtag"
-EOF
-
-test_expect_success 'Quoting style: tcl' '
-	git for-each-ref --tcl --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
-	test_expect_success "more than one quoting style: $i" "
-		test_must_fail git for-each-ref $i 2>err &&
-		grep '^error: more than one quoting style' err
-	"
-done
-
-test_expect_success 'setup for upstream:track[short]' '
-	test_commit two
-'
-
-test_atom head upstream:track '[ahead 1]'
-test_atom head upstream:trackshort '>'
-test_atom head upstream:track,nobracket 'ahead 1'
-test_atom head upstream:nobracket,track 'ahead 1'
-
-test_expect_success 'setup for push:track[short]' '
-	test_commit third &&
-	git update-ref refs/remotes/myfork/main main &&
-	git reset main~1
-'
-
-test_atom head push:track '[behind 1]'
-test_atom head push:trackshort '<'
-
-test_expect_success 'Check that :track[short] cannot be used with other atoms' '
-	test_must_fail git for-each-ref --format="%(refname:track)" 2>/dev/null &&
-	test_must_fail git for-each-ref --format="%(refname:trackshort)" 2>/dev/null
-'
-
-test_expect_success 'Check that :track[short] works when upstream is invalid' '
-	cat >expected <<-\EOF &&
-	[gone]
-
-	EOF
-	test_when_finished "git config branch.main.merge refs/heads/main" &&
-	git config branch.main.merge refs/heads/does-not-exist &&
-	git for-each-ref \
-		--format="%(upstream:track)$LF%(upstream:trackshort)" \
-		refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Check for invalid refname format' '
-	test_must_fail git for-each-ref --format="%(refname:INVALID)"
-'
-
-test_expect_success 'set up color tests' '
-	cat >expected.color <<-EOF &&
-	$(git rev-parse --short refs/heads/main) <GREEN>main<RESET>
-	$(git rev-parse --short refs/remotes/myfork/main) <GREEN>myfork/main<RESET>
-	$(git rev-parse --short refs/remotes/origin/main) <GREEN>origin/main<RESET>
-	$(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET>
-	$(git rev-parse --short refs/tags/third) <GREEN>third<RESET>
-	$(git rev-parse --short refs/tags/two) <GREEN>two<RESET>
-	EOF
-	sed "s/<[^>]*>//g" <expected.color >expected.bare &&
-	color_format="%(objectname:short) %(color:green)%(refname:short)"
-'
-
-test_expect_success TTY '%(color) shows color with a tty' '
-	test_terminal git for-each-ref --format="$color_format" >actual.raw &&
-	test_decode_color <actual.raw >actual &&
-	test_cmp expected.color actual
-'
-
-test_expect_success '%(color) does not show color without tty' '
-	TERM=vt100 git for-each-ref --format="$color_format" >actual &&
-	test_cmp expected.bare actual
-'
-
-test_expect_success '--color can override tty check' '
-	git for-each-ref --color --format="$color_format" >actual.raw &&
-	test_decode_color <actual.raw >actual &&
-	test_cmp expected.color actual
-'
-
-test_expect_success 'color.ui=always does not override tty check' '
-	git -c color.ui=always for-each-ref --format="$color_format" >actual &&
-	test_cmp expected.bare actual
-'
-
-test_expect_success 'setup for describe atom tests' '
-	git init -b master describe-repo &&
-	(
-		cd describe-repo &&
-
-		test_commit --no-tag one &&
-		git tag tagone &&
-
-		test_commit --no-tag two &&
-		git tag -a -m "tag two" tagtwo
-	)
-'
-
-test_expect_success 'describe atom vs git describe' '
-	(
-		cd describe-repo &&
-
-		git for-each-ref --format="%(objectname)" \
-			refs/tags/ >obj &&
-		while read hash
-		do
-			if desc=$(git describe $hash)
-			then
-				: >expect-contains-good
-			else
-				: >expect-contains-bad
-			fi &&
-			echo "$hash $desc" || return 1
-		done <obj >expect &&
-		test_path_exists expect-contains-good &&
-		test_path_exists expect-contains-bad &&
-
-		git for-each-ref --format="%(objectname) %(describe)" \
-			refs/tags/ >actual 2>err &&
-		test_cmp expect actual &&
-		test_must_be_empty err
-	)
-'
-
-test_expect_success 'describe:tags vs describe --tags' '
-	(
-		cd describe-repo &&
-		git describe --tags >expect &&
-		git for-each-ref --format="%(describe:tags)" \
-				refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
-	(
-		cd describe-repo &&
-
-		# Case 1: We have commits between HEAD and the most
-		#	  recent tag reachable from it
-		test_commit --no-tag file &&
-		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
-			refs/heads/master >actual &&
-		test_cmp expect actual &&
-
-		# Make sure the hash used is at least 14 digits long
-		sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart &&
-		test 15 -le $(wc -c <hexpart) &&
-
-		# Case 2: We have a tag at HEAD, describe directly gives
-		#	  the name of the tag
-		git tag -a -m tagged tagname &&
-		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
-			refs/heads/master >actual &&
-		test_cmp expect actual &&
-		test tagname = $(cat actual)
-	)
-'
-
-test_expect_success 'describe:match=... vs describe --match ...' '
-	(
-		cd describe-repo &&
-		git tag -a -m "tag foo" tag-foo &&
-		git describe --match "*-foo" >expect &&
-		git for-each-ref --format="%(describe:match="*-foo")" \
-			refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'describe:exclude:... vs describe --exclude ...' '
-	(
-		cd describe-repo &&
-		git tag -a -m "tag bar" tag-bar &&
-		git describe --exclude "*-bar" >expect &&
-		git for-each-ref --format="%(describe:exclude="*-bar")" \
-			refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'deref with describe atom' '
-	(
-		cd describe-repo &&
-		cat >expect <<-\EOF &&
-
-		tagname
-		tagname
-		tagname
-
-		tagtwo
-		EOF
-		git for-each-ref --format="%(*describe)" >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'err on bad describe atom arg' '
-	(
-		cd describe-repo &&
-
-		# The bad arg is the only arg passed to describe atom
-		cat >expect <<-\EOF &&
-		fatal: unrecognized %(describe) argument: baz
-		EOF
-		test_must_fail git for-each-ref --format="%(describe:baz)" \
-			refs/heads/master 2>actual &&
-		test_cmp expect actual &&
-
-		# The bad arg is in the middle of the option string
-		# passed to the describe atom
-		cat >expect <<-\EOF &&
-		fatal: unrecognized %(describe) argument: qux=1,abbrev=14
-		EOF
-		test_must_fail git for-each-ref \
-			--format="%(describe:tags,qux=1,abbrev=14)" \
-			ref/heads/master 2>actual &&
-		test_cmp expect actual
-	)
-'
-
-cat >expected <<\EOF
-heads/main
-tags/main
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs (strict)' '
-	git config --bool core.warnambiguousrefs true &&
-	git checkout -b newtag &&
-	echo "Using $datestamp" > one &&
-	git add one &&
-	git commit -m "Branch" &&
-	setdate_and_increment &&
-	git tag -m "Tagging at $datestamp" main &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-heads/main
-main
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs (loose)' '
-	git config --bool core.warnambiguousrefs false &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-heads/ambiguous
-ambiguous
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs II (loose)' '
-	git checkout main &&
-	git tag ambiguous testtag^0 &&
-	git branch ambiguous testtag^0 &&
-	git for-each-ref --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'create tag without tagger' '
-	git tag -a -m "Broken tag" taggerless &&
-	git tag -f taggerless $(git cat-file tag taggerless |
-		sed -e "/^tagger /d" |
-		git hash-object --literally --stdin -w -t tag)
-'
-
-test_atom refs/tags/taggerless type 'commit'
-test_atom refs/tags/taggerless tag 'taggerless'
-test_atom refs/tags/taggerless tagger ''
-test_atom refs/tags/taggerless taggername ''
-test_atom refs/tags/taggerless taggeremail ''
-test_atom refs/tags/taggerless taggeremail:trim ''
-test_atom refs/tags/taggerless taggeremail:localpart ''
-test_atom refs/tags/taggerless taggerdate ''
-test_atom refs/tags/taggerless committer ''
-test_atom refs/tags/taggerless committername ''
-test_atom refs/tags/taggerless committeremail ''
-test_atom refs/tags/taggerless committeremail:trim ''
-test_atom refs/tags/taggerless committeremail:localpart ''
-test_atom refs/tags/taggerless committerdate ''
-test_atom refs/tags/taggerless subject 'Broken tag'
-
-test_expect_success 'an unusual tag with an incomplete line' '
-
-	git tag -m "bogo" bogo &&
-	bogo=$(git cat-file tag bogo) &&
-	bogo=$(printf "%s" "$bogo" | git mktag) &&
-	git tag -f bogo "$bogo" &&
-	git for-each-ref --format "%(body)" refs/tags/bogo
-
-'
-
-test_expect_success 'create tag with subject and body content' '
-	cat >>msg <<-\EOF &&
-		the subject line
-
-		first body line
-		second body line
-	EOF
-	git tag -F msg subject-body
-'
-test_atom refs/tags/subject-body subject 'the subject line'
-test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
-test_atom refs/tags/subject-body body 'first body line
-second body line
-'
-test_atom refs/tags/subject-body contents 'the subject line
-
-first body line
-second body line
-'
-
-test_expect_success 'create tag with multiline subject' '
-	cat >msg <<-\EOF &&
-		first subject line
-		second subject line
-
-		first body line
-		second body line
-	EOF
-	git tag -F msg multiline
-'
-test_atom refs/tags/multiline subject 'first subject line second subject line'
-test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
-test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
-test_atom refs/tags/multiline body 'first body line
-second body line
-'
-test_atom refs/tags/multiline contents:body 'first body line
-second body line
-'
-test_atom refs/tags/multiline contents:signature ''
-test_atom refs/tags/multiline contents 'first subject line
-second subject line
-
-first body line
-second body line
-'
-
-test_expect_success GPG 'create signed tags' '
-	git tag -s -m "" signed-empty &&
-	git tag -s -m "subject line" signed-short &&
-	cat >msg <<-\EOF &&
-	subject line
-
-	body contents
-	EOF
-	git tag -s -F msg signed-long
-'
-
-sig='-----BEGIN PGP SIGNATURE-----
------END PGP SIGNATURE-----
-'
-
-PREREQ=GPG
-test_atom refs/tags/signed-empty subject ''
-test_atom refs/tags/signed-empty subject:sanitize ''
-test_atom refs/tags/signed-empty contents:subject ''
-test_atom refs/tags/signed-empty body "$sig"
-test_atom refs/tags/signed-empty contents:body ''
-test_atom refs/tags/signed-empty contents:signature "$sig"
-test_atom refs/tags/signed-empty contents "$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
-	git cat-file tag refs/tags/signed-empty >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-empty >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_atom refs/tags/signed-short subject 'subject line'
-test_atom refs/tags/signed-short subject:sanitize 'subject-line'
-test_atom refs/tags/signed-short contents:subject 'subject line'
-test_atom refs/tags/signed-short body "$sig"
-test_atom refs/tags/signed-short contents:body ''
-test_atom refs/tags/signed-short contents:signature "$sig"
-test_atom refs/tags/signed-short contents "subject line
-$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
-	git cat-file tag refs/tags/signed-short >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-short >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_atom refs/tags/signed-long subject 'subject line'
-test_atom refs/tags/signed-long subject:sanitize 'subject-line'
-test_atom refs/tags/signed-long contents:subject 'subject line'
-test_atom refs/tags/signed-long body "body contents
-$sig"
-test_atom refs/tags/signed-long contents:body 'body contents
-'
-test_atom refs/tags/signed-long contents:signature "$sig"
-test_atom refs/tags/signed-long contents "subject line
-
-body contents
-$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
-	git cat-file tag refs/tags/signed-long >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-long >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_expect_success 'set up refs pointing to tree and blob' '
-	git update-ref refs/mytrees/first refs/heads/main^{tree} &&
-	git update-ref refs/myblobs/first refs/heads/main:one
-'
-
-test_atom refs/mytrees/first subject ""
-test_atom refs/mytrees/first contents:subject ""
-test_atom refs/mytrees/first body ""
-test_atom refs/mytrees/first contents:body ""
-test_atom refs/mytrees/first contents:signature ""
-test_atom refs/mytrees/first contents ""
-
-test_expect_success 'basic atom: refs/mytrees/first raw' '
-	git cat-file tree refs/mytrees/first >expected &&
-	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/mytrees/first >actual &&
-	test_cmp expected actual &&
-	git cat-file -s refs/mytrees/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_atom refs/myblobs/first subject ""
-test_atom refs/myblobs/first contents:subject ""
-test_atom refs/myblobs/first body ""
-test_atom refs/myblobs/first contents:body ""
-test_atom refs/myblobs/first contents:signature ""
-test_atom refs/myblobs/first contents ""
-
-test_expect_success 'basic atom: refs/myblobs/first raw' '
-	git cat-file blob refs/myblobs/first >expected &&
-	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/myblobs/first >actual &&
-	test_cmp expected actual &&
-	git cat-file -s refs/myblobs/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/myblobs/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'set up refs pointing to binary blob' '
-	printf "a\0b\0c" >blob1 &&
-	printf "a\0c\0b" >blob2 &&
-	printf "\0a\0b\0c" >blob3 &&
-	printf "abc" >blob4 &&
-	printf "\0 \0 \0 " >blob5 &&
-	printf "\0 \0a\0 " >blob6 &&
-	printf "  " >blob7 &&
-	>blob8 &&
-	obj=$(git hash-object -w blob1) &&
-	git update-ref refs/myblobs/blob1 "$obj" &&
-	obj=$(git hash-object -w blob2) &&
-	git update-ref refs/myblobs/blob2 "$obj" &&
-	obj=$(git hash-object -w blob3) &&
-	git update-ref refs/myblobs/blob3 "$obj" &&
-	obj=$(git hash-object -w blob4) &&
-	git update-ref refs/myblobs/blob4 "$obj" &&
-	obj=$(git hash-object -w blob5) &&
-	git update-ref refs/myblobs/blob5 "$obj" &&
-	obj=$(git hash-object -w blob6) &&
-	git update-ref refs/myblobs/blob6 "$obj" &&
-	obj=$(git hash-object -w blob7) &&
-	git update-ref refs/myblobs/blob7 "$obj" &&
-	obj=$(git hash-object -w blob8) &&
-	git update-ref refs/myblobs/blob8 "$obj"
-'
-
-test_expect_success 'Verify sorts with raw' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob8
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/blob3
-	refs/myblobs/blob7
-	refs/mytrees/first
-	refs/myblobs/first
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob4
-	refs/heads/main
-	EOF
-	git for-each-ref --format="%(refname)" --sort=raw \
-		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Verify sorts with raw:size' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob8
-	refs/myblobs/blob7
-	refs/myblobs/blob4
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob3
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/first
-	refs/mytrees/first
-	refs/heads/main
-	EOF
-	git for-each-ref --format="%(refname)" --sort=raw:size \
-		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'validate raw atom with %(if:equals)' '
-	cat >expected <<-EOF &&
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	refs/myblobs/blob4
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	EOF
-	git for-each-ref --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
-		refs/myblobs/ refs/heads/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'validate raw atom with %(if:notequals)' '
-	cat >expected <<-EOF &&
-	refs/heads/ambiguous
-	refs/heads/main
-	refs/heads/newtag
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob3
-	equals
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/blob7
-	refs/myblobs/blob8
-	refs/myblobs/first
-	EOF
-	git for-each-ref --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
-		refs/myblobs/ refs/heads/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'empty raw refs with %(if)' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob1 not empty
-	refs/myblobs/blob2 not empty
-	refs/myblobs/blob3 not empty
-	refs/myblobs/blob4 not empty
-	refs/myblobs/blob5 not empty
-	refs/myblobs/blob6 not empty
-	refs/myblobs/blob7 empty
-	refs/myblobs/blob8 empty
-	refs/myblobs/first not empty
-	EOF
-	git for-each-ref --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
-		refs/myblobs/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '%(raw) with --python must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --python
-'
-
-test_expect_success '%(raw) with --tcl must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --tcl
-'
-
-test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' '
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
-	cmp blob1 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
-	cmp blob3 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
-	cmp blob8 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
-	cmp one actual &&
-	git cat-file tree refs/mytrees/first > expected &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
-	cmp expected actual
-'
-
-test_expect_success '%(raw) with --shell must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --shell
-'
-
-test_expect_success '%(raw) with --shell and --sort=raw must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --sort=raw --shell
-'
-
-test_expect_success '%(raw:size) with --shell' '
-	git for-each-ref --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
-	git for-each-ref --format="%(raw:size)" --shell >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --format compare with cat-file --batch' '
-	git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
-	git for-each-ref --format="%(objectname) %(objecttype) %(objectsize)
-%(raw)" refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'verify sorts with contents:size' '
-	cat >expect <<-\EOF &&
-	refs/heads/main
-	refs/heads/newtag
-	refs/heads/ambiguous
-	EOF
-	git for-each-ref --format="%(refname)" \
-		--sort=contents:size refs/heads/ >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'set up multiple-sort tags' '
-	for when in 100000 200000
-	do
-		for email in user1 user2
-		do
-			for ref in ref1 ref2
-			do
-				GIT_COMMITTER_DATE="@$when +0000" \
-				GIT_COMMITTER_EMAIL="$email@example.com" \
-				git tag -m "tag $ref-$when-$email" \
-				multi-$ref-$when-$email || return 1
-			done
-		done
-	done
-'
-
-test_expect_success 'Verify sort with multiple keys' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=-refname \
-		--sort=taggeremail \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'equivalent sorts fall back on refname' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '--no-sort cancels the previous sort keys' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=-refname \
-		--sort=taggeremail \
-		--no-sort \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '--no-sort without subsequent --sort prints expected refs' '
-	cat >expected <<-\EOF &&
-	refs/tags/multi-ref1-100000-user1
-	refs/tags/multi-ref1-100000-user2
-	refs/tags/multi-ref1-200000-user1
-	refs/tags/multi-ref1-200000-user2
-	refs/tags/multi-ref2-100000-user1
-	refs/tags/multi-ref2-100000-user2
-	refs/tags/multi-ref2-200000-user1
-	refs/tags/multi-ref2-200000-user2
-	EOF
-
-	# Sort the results with `sort` for a consistent comparison against
-	# expected
-	git for-each-ref \
-		--format="%(refname)" \
-		--no-sort \
-		"refs/tags/multi-*" | sort >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'set up custom date sorting' '
-	# Dates:
-	# - Wed Feb 07 2024 21:34:20 +0000
-	# - Tue Dec 14 1999 00:05:22 +0000
-	# - Fri Jun 04 2021 11:26:51 +0000
-	# - Mon Jan 22 2007 16:44:01 GMT+0000
-	i=1 &&
-	for when in 1707341660 945129922 1622806011 1169484241
-	do
-		GIT_COMMITTER_DATE="@$when +0000" \
-		GIT_COMMITTER_EMAIL="user@example.com" \
-		git tag -m "tag $when" custom-dates-$i &&
-		i=$(($i+1)) || return 1
-	done
-'
-
-test_expect_success 'sort by date defaults to full timestamp' '
-	cat >expected <<-\EOF &&
-	945129922 refs/tags/custom-dates-2
-	1169484241 refs/tags/custom-dates-4
-	1622806011 refs/tags/custom-dates-3
-	1707341660 refs/tags/custom-dates-1
-	EOF
-
-	git for-each-ref \
-		--format="%(creatordate:unix) %(refname)" \
-		--sort=creatordate \
-		"refs/tags/custom-dates-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'sort by custom date format' '
-	cat >expected <<-\EOF &&
-	00:05:22 refs/tags/custom-dates-2
-	11:26:51 refs/tags/custom-dates-3
-	16:44:01 refs/tags/custom-dates-4
-	21:34:20 refs/tags/custom-dates-1
-	EOF
-
-	git for-each-ref \
-		--format="%(creatordate:format:%H:%M:%S) %(refname)" \
-		--sort="creatordate:format:%H:%M:%S" \
-		"refs/tags/custom-dates-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
-	test_when_finished "git checkout main" &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
-	sed -e "s/^\* /  /" actual >expect &&
-	git checkout --orphan orphaned-branch &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
-	test_cmp expect actual
-'
-
-cat >trailers <<EOF
-Reviewed-by: A U Thor <author@example.com>
-Signed-off-by: A U Thor <author@example.com>
-[ v2 updated patch description ]
-Acked-by: A U Thor
-  <author@example.com>
-EOF
-
-unfold () {
-	perl -0pe 's/\n\s+/ /g'
-}
-
-test_expect_success 'set up trailers for next test' '
-	echo "Some contents" > two &&
-	git add two &&
-	git commit -F - <<-EOF
-	trailers: this commit message has trailers
-
-	Some message contents
-
-	$(cat trailers)
-	EOF
-'
-
-test_trailer_option () {
-	if test "$#" -eq 3
-	then
-		prereq="$1"
-		shift
-	fi &&
-	title=$1 option=$2
-	cat >expect
-	test_expect_success $prereq "$title" '
-		git for-each-ref --format="%($option)" refs/heads/main >actual &&
-		test_cmp expect actual &&
-		git for-each-ref --format="%(contents:$option)" refs/heads/main >actual &&
-		test_cmp expect actual
-	'
-}
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' \
-	'trailers:unfold' <<-EOF
-	$(unfold <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only) shows only "key: value" trailers' \
-	'trailers:only' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=no,only=true) shows only "key: value" trailers' \
-	'trailers:only=no,only=true' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=yes) shows only "key: value" trailers' \
-	'trailers:only=yes' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=no) shows all trailers' \
-	'trailers:only=no' <<-EOF
-	$(cat trailers)
-
-	EOF
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:only) and %(trailers:unfold) work together' \
-	'trailers:only,unfold' <<-EOF
-	$(grep -v patch.description <trailers | unfold)
-
-	EOF
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) and %(trailers:only) work together' \
-	'trailers:unfold,only' <<-EOF
-	$(grep -v patch.description <trailers | unfold)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) shows that trailer' \
-	'trailers:key=Signed-off-by' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) is case insensitive' \
-	'trailers:key=SiGned-oFf-bY' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo:) trailing colon also works' \
-	'trailers:key=Signed-off-by:' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) multiple keys' \
-	'trailers:key=Reviewed-by:,key=Signed-off-by' <<-EOF
-	Reviewed-by: A U Thor <author@example.com>
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=nonexistent) becomes empty' \
-	'trailers:key=Shined-off-by:' <<-EOF
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) handles multiple lines even if folded' \
-	'trailers:key=Acked-by' <<-EOF
-	$(grep -v patch.description <trailers | grep -v Signed-off-by | grep -v Reviewed-by)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,unfold) properly unfolds' \
-	'trailers:key=Signed-Off-by,unfold' <<-EOF
-	$(unfold <trailers | grep Signed-off-by)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,only=no) also includes nontrailer lines' \
-	'trailers:key=Signed-off-by,only=no' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-	$(grep patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,valueonly) shows only value' \
-	'trailers:key=Signed-off-by,valueonly' <<-EOF
-	A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:separator) changes separator' \
-	'trailers:separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by: A U Thor <author@example.com>,Signed-off-by: A U Thor <author@example.com>
-	EOF
-
-test_trailer_option '%(trailers:key_value_separator) changes key-value separator' \
-	'trailers:key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by,A U Thor <author@example.com>
-	Signed-off-by,A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:separator,key_value_separator) changes both separators' \
-	'trailers:separator=%x2C,key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by,A U Thor <author@example.com>,Signed-off-by,A U Thor <author@example.com>
-	EOF
-
-test_expect_success 'multiple %(trailers) use their own options' '
-	git tag -F - tag-with-trailers <<-\EOF &&
-	body
-
-	one: foo
-	one: bar
-	two: baz
-	two: qux
-	EOF
-	t1="%(trailers:key=one,key_value_separator=W,separator=X)" &&
-	t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" &&
-	git for-each-ref --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
-	cat >expect <<-\EOF &&
-	oneWfooXoneWbar
-	twoYbazZtwoYqux
-	EOF
-	test_cmp expect actual
-'
-
-test_failing_trailer_option () {
-	title=$1 option=$2
-	cat >expect
-	test_expect_success "$title" '
-		# error message cannot be checked under i18n
-		test_must_fail git for-each-ref --format="%($option)" refs/heads/main 2>actual &&
-		test_cmp expect actual &&
-		test_must_fail git for-each-ref --format="%(contents:$option)" refs/heads/main 2>actual &&
-		test_cmp expect actual
-	'
-}
-
-test_failing_trailer_option '%(trailers) rejects unknown trailers arguments' \
-	'trailers:unsupported' <<-\EOF
-	fatal: unknown %(trailers) argument: unsupported
-	EOF
-
-test_failing_trailer_option '%(trailers:key) without value is error' \
-	'trailers:key' <<-\EOF
-	fatal: expected %(trailers:key=<value>)
-	EOF
-
-test_expect_success 'if arguments, %(contents:trailers) shows error if colon is missing' '
-	cat >expect <<-EOF &&
-	fatal: unrecognized %(contents) argument: trailersonly
-	EOF
-	test_must_fail git for-each-ref --format="%(contents:trailersonly)" 2>actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'basic atom: head contents:trailers' '
-	git for-each-ref --format="%(contents:trailers)" refs/heads/main >actual &&
-	sanitize_pgp <actual >actual.clean &&
-	# git for-each-ref ends with a blank line
-	cat >expect <<-EOF &&
-	$(cat trailers)
-
-	EOF
-	test_cmp expect actual.clean
-'
-
-test_expect_success 'basic atom: rest must fail' '
-	test_must_fail git for-each-ref --format="%(rest)" refs/heads/main
-'
-
-test_expect_success 'HEAD atom does not take arguments' '
-	test_must_fail git for-each-ref --format="%(HEAD:foo)" 2>err &&
-	echo "fatal: %(HEAD) does not take arguments" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'subject atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(subject:foo)" 2>err &&
-	echo "fatal: unrecognized %(subject) argument: foo" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'refname atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(refname:foo)" 2>err &&
-	echo "fatal: unrecognized %(refname) argument: foo" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'trailer parsing not fooled by --- line' '
-	git commit --allow-empty -F - <<-\EOF &&
-	this is the subject
-
-	This is the body. The message has a "---" line which would confuse a
-	message+patch parser. But here we know we have only a commit message,
-	so we get it right.
-
-	trailer: wrong
-	---
-	This is more body.
-
-	trailer: right
-	EOF
-
-	{
-		echo "trailer: right" &&
-		echo
-	} >expect &&
-	git for-each-ref --format="%(trailers)" refs/heads/main >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'Add symbolic ref for the following tests' '
-	git symbolic-ref refs/heads/sym refs/heads/main
-'
-
-cat >expected <<EOF
-refs/heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref) atom' '
-	git for-each-ref --format="%(symref)" refs/heads/sym >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref:short) atom' '
-	git for-each-ref --format="%(symref:short)" refs/heads/sym >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-main
-heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref:lstrip) atom' '
-	git for-each-ref --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual &&
-
-	git for-each-ref --format="%(symref:strip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-refs
-refs/heads
-EOF
-
-test_expect_success 'Verify usage of %(symref:rstrip) atom' '
-	git for-each-ref --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual
-'
-
-test_expect_success ':remotename and :remoteref' '
-	git init remote-tests &&
-	(
-		cd remote-tests &&
-		test_commit initial &&
-		git branch -M main &&
-		git remote add from fifth.coffee:blub &&
-		git config branch.main.remote from &&
-		git config branch.main.merge refs/heads/stable &&
-		git remote add to southridge.audio:repo &&
-		git config remote.to.push "refs/heads/*:refs/heads/pushed/*" &&
-		git config branch.main.pushRemote to &&
-		for pair in "%(upstream)=refs/remotes/from/stable" \
-			"%(upstream:remotename)=from" \
-			"%(upstream:remoteref)=refs/heads/stable" \
-			"%(push)=refs/remotes/to/pushed/main" \
-			"%(push:remotename)=to" \
-			"%(push:remoteref)=refs/heads/pushed/main"
-		do
-			echo "${pair#*=}" >expect &&
-			git for-each-ref --format="${pair%=*}" \
-				refs/heads/main >actual &&
-			test_cmp expect actual || exit 1
-		done &&
-		git branch push-simple &&
-		git config branch.push-simple.pushRemote from &&
-		actual="$(git for-each-ref \
-			--format="%(push:remotename),%(push:remoteref)" \
-			refs/heads/push-simple)" &&
-		test from, = "$actual"
-	)
-'
-
-test_expect_success 'for-each-ref --ignore-case ignores case' '
-	git for-each-ref --format="%(refname)" refs/heads/MAIN >actual &&
-	test_must_be_empty actual &&
-
-	echo refs/heads/main >expect &&
-	git for-each-ref --format="%(refname)" --ignore-case \
-		refs/heads/MAIN >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --omit-empty works' '
-	git for-each-ref --format="%(refname)" >actual &&
-	test_line_count -gt 1 actual &&
-	git for-each-ref --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
-	echo refs/heads/main >expect &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
-	# name refs numerically to avoid case-insensitive filesystem conflicts
-	nr=0 &&
-	for email in a A b B
-	do
-		for subject in a A b B
-		do
-			GIT_COMMITTER_EMAIL="$email@example.com" \
-			git tag -m "tag $subject" icase-$(printf %02d $nr) &&
-			nr=$((nr+1))||
-			return 1
-		done
-	done &&
-	git for-each-ref --ignore-case \
-		--format="%(taggeremail) %(subject) %(refname)" \
-		--sort=refname \
-		--sort=subject \
-		--sort=taggeremail \
-		refs/tags/icase-* >actual &&
-	cat >expect <<-\EOF &&
-	<a@example.com> tag a refs/tags/icase-00
-	<a@example.com> tag A refs/tags/icase-01
-	<A@example.com> tag a refs/tags/icase-04
-	<A@example.com> tag A refs/tags/icase-05
-	<a@example.com> tag b refs/tags/icase-02
-	<a@example.com> tag B refs/tags/icase-03
-	<A@example.com> tag b refs/tags/icase-06
-	<A@example.com> tag B refs/tags/icase-07
-	<b@example.com> tag a refs/tags/icase-08
-	<b@example.com> tag A refs/tags/icase-09
-	<B@example.com> tag a refs/tags/icase-12
-	<B@example.com> tag A refs/tags/icase-13
-	<b@example.com> tag b refs/tags/icase-10
-	<b@example.com> tag B refs/tags/icase-11
-	<B@example.com> tag b refs/tags/icase-14
-	<B@example.com> tag B refs/tags/icase-15
-	EOF
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref reports broken tags' '
-	git tag -m "good tag" broken-tag-good HEAD &&
-	git cat-file tag broken-tag-good >good &&
-	sed s/commit/blob/ <good >bad &&
-	bad=$(git hash-object -w -t tag bad) &&
-	git update-ref refs/tags/broken-tag-bad $bad &&
-	test_must_fail git for-each-ref --format="%(*objectname)" \
-		refs/tags/broken-tag-*
-'
-
-test_expect_success 'set up tag with signature and no blank lines' '
-	git tag -F - fake-sig-no-blanks <<-\EOF
-	this is the subject
-	-----BEGIN PGP SIGNATURE-----
-	not a real signature, but we just care about the
-	subject/body parsing. It is important here that
-	there are no blank lines in the signature.
-	-----END PGP SIGNATURE-----
-	EOF
-'
-
-test_atom refs/tags/fake-sig-no-blanks contents:subject 'this is the subject'
-test_atom refs/tags/fake-sig-no-blanks contents:body ''
-test_atom refs/tags/fake-sig-no-blanks contents:signature "$sig"
-
-test_expect_success 'set up tag with CRLF signature' '
-	append_cr <<-\EOF |
-	this is the subject
-	-----BEGIN PGP SIGNATURE-----
-
-	not a real signature, but we just care about
-	the subject/body parsing. It is important here
-	that there is a blank line separating this
-	from the signature header.
-	-----END PGP SIGNATURE-----
-	EOF
-	git tag -F - --cleanup=verbatim fake-sig-crlf
-'
-
-test_atom refs/tags/fake-sig-crlf contents:subject 'this is the subject'
-test_atom refs/tags/fake-sig-crlf contents:body ''
-
-# CRLF is retained in the signature, so we have to pass our expected value
-# through append_cr. But test_atom requires a shell string, which means command
-# substitution, and the shell will strip trailing newlines from the output of
-# the substitution. Hack around it by adding and then removing a dummy line.
-sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
-sig_crlf=${sig_crlf%dummy}
-test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
-
-test_expect_success 'set up tag with signature and trailers' '
-	git tag -F - fake-sig-trailer <<-\EOF
-	this is the subject
-
-	this is the body
-
-	My-Trailer: foo
-	-----BEGIN PGP SIGNATURE-----
-
-	not a real signature, but we just care about the
-	subject/body/trailer parsing.
-	-----END PGP SIGNATURE-----
-	EOF
-'
-
-# use "separator=" here to suppress the terminating newline
-test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo'
-
-test_expect_success 'git for-each-ref --stdin: empty' '
-	>in &&
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	git for-each-ref --format="%(refname)" >expect &&
-	test_cmp expect actual
-'
-
-test_expect_success 'git for-each-ref --stdin: fails if extra args' '
-	>in &&
-	test_must_fail git for-each-ref --format="%(refname)" \
-		--stdin refs/heads/extra <in 2>err &&
-	grep "unknown arguments supplied with --stdin" err
-'
-
-test_expect_success 'git for-each-ref --stdin: matches' '
-	cat >in <<-EOF &&
-	refs/tags/multi*
-	refs/heads/amb*
-	EOF
-
-	cat >expect <<-EOF &&
-	refs/heads/ambiguous
-	refs/tags/multi-ref1-100000-user1
-	refs/tags/multi-ref1-100000-user2
-	refs/tags/multi-ref1-200000-user1
-	refs/tags/multi-ref1-200000-user2
-	refs/tags/multi-ref2-100000-user1
-	refs/tags/multi-ref2-100000-user2
-	refs/tags/multi-ref2-200000-user1
-	refs/tags/multi-ref2-200000-user2
-	refs/tags/multiline
-	EOF
-
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'git for-each-ref with non-existing refs' '
-	cat >in <<-EOF &&
-	refs/heads/this-ref-does-not-exist
-	refs/tags/bogus
-	EOF
-
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	test_must_be_empty actual &&
-
-	xargs git for-each-ref --format="%(refname)" <in >actual &&
-	test_must_be_empty actual
-'
-
-test_expect_success 'git for-each-ref with nested tags' '
-	git tag -am "Normal tag" nested/base HEAD &&
-	git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
-	git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
-
-	head_oid="$(git rev-parse HEAD)" &&
-	base_tag_oid="$(git rev-parse refs/tags/nested/base)" &&
-	nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" &&
-	nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" &&
-
-	cat >expect <<-EOF &&
-	refs/tags/nested/base $base_tag_oid tag $head_oid commit
-	refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit
-	refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
-	EOF
-
-	git for-each-ref \
-		--format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
-		refs/tags/nested/ >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'is-base atom with non-commits' '
-	git for-each-ref --format="%(is-base:HEAD) %(refname)" >out 2>err &&
-	grep "(HEAD) refs/heads/main" out &&
-
-	test_line_count = 2 err &&
-	grep "error: object .* is a commit, not a blob" err &&
-	grep "error: bad tag pointer to" err
-'
-
-GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
-TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
-
-test_expect_success GPG 'setup for signature atom using gpg' '
-	git checkout -b signed &&
-
-	test_when_finished "test_unconfig commit.gpgSign" &&
-
-	echo "1" >file &&
-	git add file &&
-	test_tick &&
-	git commit -S -m "file: 1" &&
-	git tag first-signed &&
-
-	echo "2" >file &&
-	test_tick &&
-	git commit -a -m "file: 2" &&
-	git tag second-unsigned &&
-
-	git config commit.gpgSign 1 &&
-	echo "3" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 3" &&
-	git tag third-unsigned &&
-
-	test_tick &&
-	git rebase -f HEAD^^ && git tag second-signed HEAD^ &&
-	git tag third-signed &&
-
-	echo "4" >file &&
-	test_tick &&
-	git commit -a -SB7227189 -m "file: 4" &&
-	git tag fourth-signed &&
-
-	echo "5" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 5" &&
-	git tag fifth-unsigned &&
-
-	echo "6" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 6" &&
-
-	test_tick &&
-	git rebase -f HEAD^^ &&
-	git tag fifth-signed HEAD^ &&
-	git tag sixth-signed &&
-
-	echo "7" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 7" &&
-	git tag seventh-unsigned
-'
-
-test_expect_success GPGSSH 'setup for signature atom using ssh' '
-	test_when_finished "test_unconfig gpg.format user.signingkey" &&
-
-	test_config gpg.format ssh &&
-	test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
-	echo "8" >file &&
-	test_tick &&
-	git add file &&
-	git commit -S -m "file: 8" &&
-	git tag eighth-signed-ssh
-'
-
-test_expect_success GPG2 'bare signature atom' '
-	git verify-commit first-signed 2>expect &&
-	echo  >>expect &&
-	git for-each-ref refs/tags/first-signed \
-		--format="%(signature)" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show good signature with custom format' '
-	git verify-commit first-signed &&
-	cat >expect <<-\EOF &&
-	G
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	EOF
-	git for-each-ref refs/tags/first-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-test_expect_success GPGSSH 'show good signature with custom format with ssh' '
-	test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
-	FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
-	cat >expect.tmpl <<-\EOF &&
-	G
-	FINGERPRINT
-	principal with number 1
-	FINGERPRINT
-
-	EOF
-	sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
-	git for-each-ref refs/tags/eighth-signed-ssh \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'signature atom with grade option and bad signature' '
-	git cat-file commit third-signed >raw &&
-	sed -e "s/^file: 3/file: 3 forged/" raw >forged1 &&
-	FORGED1=$(git hash-object -w -t commit forged1) &&
-	git update-ref refs/tags/third-signed "$FORGED1" &&
-	test_must_fail git verify-commit "$FORGED1" &&
-
-	cat >expect <<-\EOF &&
-	B
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-
-
-	EOF
-	git for-each-ref refs/tags/third-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with custom format' '
-	cat >expect <<-\EOF &&
-	U
-	65A0EEA02E30CAD7
-	Eris Discordia <discord@example.net>
-	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
-	D4BE22311AD3131E5EDA29A461092E85B7227189
-	EOF
-	git for-each-ref refs/tags/fourth-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with undefined trust level' '
-	cat >expect <<-\EOF &&
-	undefined
-	65A0EEA02E30CAD7
-	Eris Discordia <discord@example.net>
-	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
-	D4BE22311AD3131E5EDA29A461092E85B7227189
-	EOF
-	git for-each-ref refs/tags/fourth-signed \
-		--format="$TRUSTLEVEL_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with ultimate trust level' '
-	cat >expect <<-\EOF &&
-	ultimate
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	EOF
-	git for-each-ref refs/tags/sixth-signed \
-		--format="$TRUSTLEVEL_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show unknown signature with custom format' '
-	cat >expect <<-\EOF &&
-	E
-	13B6F51ECDDE430D
-
-
-
-	EOF
-	GNUPGHOME="$GNUPGHOME_NOT_USED" git for-each-ref \
-		refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show lack of signature with custom format' '
-	cat >expect <<-\EOF &&
-	N
-
-
-
-
-	EOF
-	git for-each-ref refs/tags/seventh-unsigned \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
+. "$TEST_DIRECTORY"/for-each-ref-tests.sh
 
 test_done
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][RFC PATCH v5 6/6] t: add test for git refs list subcommand
  2025-08-04  9:22       ` [GSoC][RFC PATCH v5 0/6] " Meet Soni
                           ` (4 preceding siblings ...)
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 5/6] t6300: refactor tests to be shareable Meet Soni
@ 2025-08-04  9:22         ` Meet Soni
  2025-08-05  9:27         ` [GSoC][PATCH v6 0/6] Add " Meet Soni
  6 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-04  9:22 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni
Add a test script, `t/t1461-refs-list.sh`, for the new `git refs list`
command.
This script acts as a simple driver, leveraging the shared test library
created in the preceding commit. It works by overriding the
`$git_for_each_ref` variable to "git refs list" and then sourcing the
shared library (`t/for-each-ref-tests.sh`).
This approach ensures that `git refs list` is tested against the
entire comprehensive test suite of `git for-each-ref`, verifying
that it acts as a compatible drop-in replacement.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 t/meson.build        | 1 +
 t/t1461-refs-list.sh | 8 ++++++++
 2 files changed, 9 insertions(+)
 create mode 100755 t/t1461-refs-list.sh
diff --git a/t/meson.build b/t/meson.build
index 50e89e764a..c959c039d0 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -224,6 +224,7 @@ integration_tests = [
   't1450-fsck.sh',
   't1451-fsck-buffer.sh',
   't1460-refs-migrate.sh',
+  't1461-refs-list.sh',
   't1500-rev-parse.sh',
   't1501-work-tree.sh',
   't1502-rev-parse-parseopt.sh',
diff --git a/t/t1461-refs-list.sh b/t/t1461-refs-list.sh
new file mode 100755
index 0000000000..36e3d81e59
--- /dev/null
+++ b/t/t1461-refs-list.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+test_description='git refs list tests'
+
+. ./test-lib.sh
+
+git_for_each_ref='git refs list'
+. "$TEST_DIRECTORY"/for-each-ref-tests.sh
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v4 3/5] builtin/refs: add list subcommand
  2025-08-01 15:49             ` Phillip Wood
  2025-08-01 17:14               ` Junio C Hamano
  2025-08-04  6:32               ` Meet Soni
@ 2025-08-04  9:27               ` Phillip Wood
  2025-08-04 15:35                 ` Junio C Hamano
  2 siblings, 1 reply; 65+ messages in thread
From: Phillip Wood @ 2025-08-04  9:27 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Meet Soni, git, ps, shejialuo, karthik.188, sunshine, John Cai
On 01/08/2025 16:49, Phillip Wood wrote:
> On 01/08/2025 15:43, Junio C Hamano wrote:
>> Phillip Wood <phillip.wood123@gmail.com> writes:
>>
>> What does a double-asterisk currently do in these patterns?
> 
> refs/heads/m** seems to behave like refs/heads/m*. I'm a bit surprised 
> by that as for-each-ref seems to set WM_PATHNAME and I thought that our 
> wildmatch code used '**' to match any character in that case.
I'd forgotten the rules for '**' - it must come after a slash and be 
followed by a slash if it is not at the end of a pattern otherwise it is 
silently converted to '*'. I wish our wildmatch code at least warned 
when it did that. So one can query all the branches beginning with "m" 
by passing
     'refs/heads/m*' 'refs/heads/m*/**'
which isn't as convenient as it could be but it is possible.
>> If it
>> is not doing anything useful, perhaps we should make it match any
>> letter, without getting constrained by hierarchy boundaries?  IOW,
>> a "fix" might be to make sure the following happens?
>>
>>   - "refs/heads/m*" matches all local branches whose name starts with
>>     'm' like 'morning', but not the ones inside subhierarchies that
>>     start with 'm' like 'mid/night'.
>>
>>   - "refs/heads/m**" matches all local branches whose name starts
>>     with 'm' and in the ones inside subhierarchies that start with
>>     'm'.
> 
> That sounds like a good idea
Now I'm not so sure. We could add a flag to wildmatch() that allows ** 
to match any character anywhere in a pattern (I think that is how the 
code behaved when it was first imported from rsync and it also rejected 
invalid patterns) but it if we use that flag in for-each-ref we'd be 
changing the behavior.
Thanks
Phillip
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v4 3/5] builtin/refs: add list subcommand
  2025-08-01 17:14               ` Junio C Hamano
@ 2025-08-04  9:28                 ` Phillip Wood
  0 siblings, 0 replies; 65+ messages in thread
From: Phillip Wood @ 2025-08-04  9:28 UTC (permalink / raw)
  To: Junio C Hamano
  Cc: Meet Soni, git, ps, shejialuo, karthik.188, sunshine, John Cai
On 01/08/2025 18:14, Junio C Hamano wrote:
> Phillip Wood <phillip.wood123@gmail.com> writes:
> 
>> It is confusing that refs/heads does a prefix match but refs/heads/m
>> does not (unless there is a hierarchy boundary after the m).
> 
> And refs/hea would not show branches, for the same reason.  It is
> what "anchored at hierarchy boundary" in the message you are
> responding to means.  In other words, it is not a simple textual
> prefix match.
The last sentence sums up why I find it confusing - it is not possible 
to tell from the pattern what sort of match will be returned. If a 
script wants to query some property of a ref it either has to know it 
exists (which is racy but probably not a practical problem most of the 
time) or it has to include %(refname) in the format argument and check 
that it matches the ref in the query string. A script can force a prefix 
match by adding a trailing slash but there is no way to force an exact 
match.
Thanks
Phillip
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v4 3/5] builtin/refs: add list subcommand
  2025-08-04  9:27               ` Phillip Wood
@ 2025-08-04 15:35                 ` Junio C Hamano
  0 siblings, 0 replies; 65+ messages in thread
From: Junio C Hamano @ 2025-08-04 15:35 UTC (permalink / raw)
  To: Phillip Wood
  Cc: Meet Soni, git, ps, shejialuo, karthik.188, sunshine, John Cai
Phillip Wood <phillip.wood123@gmail.com> writes:
> On 01/08/2025 16:49, Phillip Wood wrote:
>> On 01/08/2025 15:43, Junio C Hamano wrote:
>>> Phillip Wood <phillip.wood123@gmail.com> writes:
>>>
>>> What does a double-asterisk currently do in these patterns?
>> refs/heads/m** seems to behave like refs/heads/m*. I'm a bit
>> surprised by that as for-each-ref seems to set WM_PATHNAME and I
>> thought that our wildmatch code used '**' to match any character in
>> that case.
>
> I'd forgotten the rules for '**' - it must come after a slash and be
> followed by a slash if it is not at the end of a pattern otherwise it
> is silently converted to '*'. I wish our wildmatch code at least
> warned when it did that. So one can query all the branches beginning
> with "m" by passing
>
>     'refs/heads/m*' 'refs/heads/m*/**'
>
> which isn't as convenient as it could be but it is possible.
As long as the rules are consistent and understandable (once you
understand it, that is), then I am perfectly fine.  And "** is
written as /**/ (but you can omit slashes at either ends)" I find
acceptable.
>>>   - "refs/heads/m*" matches all local branches whose name starts with
>>>     'm' like 'morning', but not the ones inside subhierarchies that
>>>     start with 'm' like 'mid/night'.
>>>
>>>   - "refs/heads/m**" matches all local branches whose name starts
>>>     with 'm' and in the ones inside subhierarchies that start with
>>>     'm'.
>> That sounds like a good idea
>
> Now I'm not so sure.
As long as the existing rule is serviceable (and you seem to have
found that it is), we do not need to make such a change.
Thanks for thinking it through.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][RFC PATCH v5 1/6] doc: factor out common option
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 1/6] doc: factor out common option Meet Soni
@ 2025-08-04 18:34           ` Junio C Hamano
  0 siblings, 0 replies; 65+ messages in thread
From: Junio C Hamano @ 2025-08-04 18:34 UTC (permalink / raw)
  To: Meet Soni
  Cc: git, ps, shejialuo, karthik.188, sunshine, phillip.wood123,
	Øystein Walle, Aaron Lipman, Taylor Blau, Jeff King,
	Derrick Stolee, Victoria Dye
Meet Soni <meetsoni3017@gmail.com> writes:
> In preparation for adding documentation for `git refs list`, factor out
> the common options from the `git-for-each-ref` man page into a
> shareable file `for-each-ref-options.adoc` and update
> `git-for-each-ref.adoc` to use an `include::` macro.
>
> This change is a pure refactoring and results in no change to the
> final rendered documentation for `for-each-ref`.
>
> Mentored-by: Patrick Steinhardt <ps@pks.im>
> Mentored-by: shejialuo <shejialuo@gmail.com>
> Mentored-by: Karthik Nayak <karthik.188@gmail.com>
> Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
> ---
>  Documentation/for-each-ref-options.adoc | 79 ++++++++++++++++++++++++
>  Documentation/git-for-each-ref.adoc     | 80 +------------------------
>  2 files changed, 80 insertions(+), 79 deletions(-)
>  create mode 100644 Documentation/for-each-ref-options.adoc
This contradicts with Karthik's "for-each-ref --start-after=<>"
topic that has been in 'next' and now in 'master'.  Mechanically
merging the result of applying these patches on top of 'master'
before the "--start-after" series into 'seen' would leave the
description of --start-after in git-for-each-refs.adoc; there isn't
any textual conflict, which makes it even worse.
Rebasing the series on top 2.51-rc0 may not be a bad idea.
Thanks.
^ permalink raw reply	[flat|nested] 65+ messages in thread
* [GSoC][PATCH v6 0/6] Add refs list subcommand
  2025-08-04  9:22       ` [GSoC][RFC PATCH v5 0/6] " Meet Soni
                           ` (5 preceding siblings ...)
  2025-08-04  9:22         ` [GSoC][RFC PATCH v5 6/6] t: add test for git refs list subcommand Meet Soni
@ 2025-08-05  9:27         ` Meet Soni
  2025-08-05  9:27           ` [GSoC][PATCH v6 1/6] doc: factor out common option Meet Soni
                             ` (7 more replies)
  6 siblings, 8 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-05  9:27 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni
Hello everyone,
This is the sixth version of the patch series that introduces the git
refs list subcommand.
changes in v6:
  - rebased onto v2.51.0-rc0
Meet Soni (6):
  doc: factor out common option
  builtin/for-each-ref: align usage string with the man page
  builtin/for-each-ref: factor out core logic into a helper
  builtin/refs: add list subcommand
  t6300: refactor tests to be shareable
  t: add test for git refs list subcommand
 Documentation/for-each-ref-options.adoc |   88 +
 Documentation/git-for-each-ref.adoc     |   89 +-
 Documentation/git-refs.adoc             |   16 +
 builtin/for-each-ref.c                  |   36 +-
 builtin/refs.c                          |   14 +
 for-each-ref.h                          |   26 +
 t/for-each-ref-tests.sh                 | 2141 +++++++++++++++++++++++
 t/meson.build                           |    1 +
 t/t0450/adoc-help-mismatches            |    1 -
 t/t1461-refs-list.sh                    |    8 +
 t/t6300-for-each-ref.sh                 | 2140 +---------------------
 11 files changed, 2316 insertions(+), 2244 deletions(-)
 create mode 100644 Documentation/for-each-ref-options.adoc
 create mode 100644 for-each-ref.h
 create mode 100644 t/for-each-ref-tests.sh
 create mode 100755 t/t1461-refs-list.sh
Range-diff against v5:
1:  d2fa47a2b9 ! 1:  fd4959594c doc: factor out common option
    @@ Documentation/for-each-ref-options.adoc (new)
     +
     +--include-root-refs::
     +	List root refs (HEAD and pseudorefs) apart from regular refs.
    ++
    ++--start-after=<marker>::
    ++    Allows paginating the output by skipping references up to and including the
    ++    specified marker. When paging, it should be noted that references may be
    ++    deleted, modified or added between invocations. Output will only yield those
    ++    references which follow the marker lexicographically. Output begins from the
    ++    first reference that would come after the marker alphabetically. Cannot be
    ++    used with `--sort=<key>` or `--stdin` options, or the _<pattern>_ argument(s)
    ++    to limit the refs.
     
      ## Documentation/git-for-each-ref.adoc ##
     @@ Documentation/git-for-each-ref.adoc: host language allowing their direct evaluation in that language.
    @@ Documentation/git-for-each-ref.adoc: host language allowing their direct evaluat
     -
     ---include-root-refs::
     -	List root refs (HEAD and pseudorefs) apart from regular refs.
    +-
    +---start-after=<marker>::
    +-    Allows paginating the output by skipping references up to and including the
    +-    specified marker. When paging, it should be noted that references may be
    +-    deleted, modified or added between invocations. Output will only yield those
    +-    references which follow the marker lexicographically. Output begins from the
    +-    first reference that would come after the marker alphabetically. Cannot be
    +-    used with `--sort=<key>` or `--stdin` options, or the _<pattern>_ argument(s)
    +-    to limit the refs.
     +include::for-each-ref-options.adoc[]
      
      FIELD NAMES
2:  48a006dca9 ! 2:  1ed0717f6b builtin/for-each-ref: align usage string with the man page
    @@ builtin/for-each-ref.c
     +#define COMMON_USAGE_FOR_EACH_REF \
     +	"[--count=<count>] [--shell|--perl|--python|--tcl]\n" \
     +	"                         [(--sort=<key>)...] [--format=<format>]\n" \
    -+	"                         [--include-root-refs] [ --stdin | <pattern>... ]\n" \
    -+	"                         [--points-at=<object>]\n" \
    ++	"                         [--include-root-refs] [--points-at=<object>]\n" \
     +	"                         [--merged[=<object>]] [--no-merged[=<object>]]\n" \
     +	"                         [--contains[=<object>]] [--no-contains[=<object>]]\n" \
    -+	"                         [--exclude=<pattern> ...]"
    ++	"                         [(--exclude=<pattern>)...] [--start-after=<marker>]\n" \
    ++	"                         [ --stdin | <pattern>... ]"
     +
      static char const * const for_each_ref_usage[] = {
     -	N_("git for-each-ref [<options>] [<pattern>]"),
     -	N_("git for-each-ref [--points-at <object>]"),
     -	N_("git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"),
     -	N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"),
    +-	N_("git for-each-ref [--start-after <marker>]"),
     +	"git for-each-ref " COMMON_USAGE_FOR_EACH_REF,
      	NULL
      };
3:  b7788d477a ! 3:  69f147aa12 builtin/for-each-ref: factor out core logic into a helper
    @@ Commit message
     
      ## builtin/for-each-ref.c ##
     @@
    - #include "builtin.h"
      #include "commit.h"
      #include "config.h"
    + #include "environment.h"
     +#include "for-each-ref.h"
      #include "gettext.h"
      #include "object.h"
    @@ builtin/for-each-ref.c
     -#define COMMON_USAGE_FOR_EACH_REF \
     -	"[--count=<count>] [--shell|--perl|--python|--tcl]\n" \
     -	"                         [(--sort=<key>)...] [--format=<format>]\n" \
    --	"                         [--include-root-refs] [ --stdin | <pattern>... ]\n" \
    --	"                         [--points-at=<object>]\n" \
    +-	"                         [--include-root-refs] [--points-at=<object>]\n" \
     -	"                         [--merged[=<object>]] [--no-merged[=<object>]]\n" \
     -	"                         [--contains[=<object>]] [--no-contains[=<object>]]\n" \
    --	"                         [--exclude=<pattern> ...]"
    +-	"                         [(--exclude=<pattern>)...] [--start-after=<marker>]\n" \
    +-	"                         [ --stdin | <pattern>... ]"
     -
     -static char const * const for_each_ref_usage[] = {
     -	"git for-each-ref " COMMON_USAGE_FOR_EACH_REF,
    @@ builtin/for-each-ref.c: int cmd_for_each_ref(int argc,
     -		usage_with_options(for_each_ref_usage, opts);
     +		usage_with_options(usage, opts);
      
    - 	sorting = ref_sorting_options(&sorting_options);
    - 	ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
    + 	if (filter.start_after && sorting_options.nr > 1)
    + 		die(_("cannot use --start-after with custom sort options"));
     @@ builtin/for-each-ref.c: int cmd_for_each_ref(int argc,
      	strvec_clear(&vec);
      	return 0;
    @@ for-each-ref.h (new)
     +#define COMMON_USAGE_FOR_EACH_REF \
     +	"[--count=<count>] [--shell|--perl|--python|--tcl]\n" \
     +	"                         [(--sort=<key>)...] [--format=<format>]\n" \
    -+	"                         [--include-root-refs] [ --stdin | <pattern>... ]\n" \
    -+	"                         [--points-at=<object>]\n" \
    ++	"                         [--include-root-refs] [--points-at=<object>]\n" \
     +	"                         [--merged[=<object>]] [--no-merged[=<object>]]\n" \
     +	"                         [--contains[=<object>]] [--no-contains[=<object>]]\n" \
    -+	"                         [--exclude=<pattern> ...]"
    ++	"                         [(--exclude=<pattern>)...] [--start-after=<marker>]\n" \
    ++	"                         [ --stdin | <pattern>... ]"
     +
     +/*
     + * The core logic for for-each-ref and its clones.
4:  97088dab96 ! 4:  4195415eb5 builtin/refs: add list subcommand
    @@ Documentation/git-refs.adoc: SYNOPSIS
      git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
      git refs verify [--strict] [--verbose]
     +git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
    -+	      [(--sort=<key>)...] [--format=<format>]
    -+	      [--include-root-refs] [ --stdin | <pattern>... ]
    -+	      [--points-at=<object>]
    -+	      [--merged[=<object>]] [--no-merged[=<object>]]
    -+	      [--contains[=<object>]] [--no-contains[=<object>]]
    -+	      [--exclude=<pattern> ...]
    ++		   [(--sort=<key>)...] [--format=<format>]
    ++		   [--include-root-refs] [--points-at=<object>]
    ++		   [--merged[=<object>]] [--no-merged[=<object>]]
    ++		   [--contains[=<object>]] [--no-contains[=<object>]]
    ++		   [(--exclude=<pattern>)...] [--start-after=<marker>]
    ++		   [ --stdin | <pattern>... ]
      
      DESCRIPTION
      -----------
5:  abe9df9c4f = 5:  d3da47e950 t6300: refactor tests to be shareable
6:  a037a47dcd = 6:  df2c3fc720 t: add test for git refs list subcommand
-- 
2.34.1
^ permalink raw reply	[flat|nested] 65+ messages in thread
* [GSoC][PATCH v6 1/6] doc: factor out common option
  2025-08-05  9:27         ` [GSoC][PATCH v6 0/6] Add " Meet Soni
@ 2025-08-05  9:27           ` Meet Soni
  2025-08-05  9:27           ` [GSoC][PATCH v6 2/6] builtin/for-each-ref: align usage string with the man page Meet Soni
                             ` (6 subsequent siblings)
  7 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-05  9:27 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni, Aaron Lipman, Victoria Dye, Taylor Blau, Jeff King,
	Øystein Walle, Derrick Stolee
In preparation for adding documentation for `git refs list`, factor out
the common options from the `git-for-each-ref` man page into a
shareable file `for-each-ref-options.adoc` and update
`git-for-each-ref.adoc` to use an `include::` macro.
This change is a pure refactoring and results in no change to the
final rendered documentation for `for-each-ref`.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 Documentation/for-each-ref-options.adoc | 88 ++++++++++++++++++++++++
 Documentation/git-for-each-ref.adoc     | 89 +------------------------
 2 files changed, 89 insertions(+), 88 deletions(-)
 create mode 100644 Documentation/for-each-ref-options.adoc
diff --git a/Documentation/for-each-ref-options.adoc b/Documentation/for-each-ref-options.adoc
new file mode 100644
index 0000000000..4a033d3e16
--- /dev/null
+++ b/Documentation/for-each-ref-options.adoc
@@ -0,0 +1,88 @@
+<pattern>...::
+	If one or more patterns are given, only refs are shown that
+	match against at least one pattern, either using fnmatch(3) or
+	literally, in the latter case matching completely or from the
+	beginning up to a slash.
+
+--stdin::
+	If `--stdin` is supplied, then the list of patterns is read from
+	standard input instead of from the argument list.
+
+--count=<count>::
+	By default the command shows all refs that match
+	`<pattern>`.  This option makes it stop after showing
+	that many refs.
+
+--sort=<key>::
+	A field name to sort on.  Prefix `-` to sort in
+	descending order of the value.  When unspecified,
+	`refname` is used.  You may use the --sort=<key> option
+	multiple times, in which case the last key becomes the primary
+	key.
+
+--format=<format>::
+	A string that interpolates `%(fieldname)` from a ref being shown and
+	the object it points at. In addition, the string literal `%%`
+	renders as `%` and `%xx` - where `xx` are hex digits - renders as
+	the character with hex code `xx`. For example, `%00` interpolates to
+	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
++
+When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
+TAB %(refname)`.
+
+--color[=<when>]::
+	Respect any colors specified in the `--format` option. The
+	`<when>` field must be one of `always`, `never`, or `auto` (if
+	`<when>` is absent, behave as if `always` was given).
+
+--shell::
+--perl::
+--python::
+--tcl::
+	If given, strings that substitute `%(fieldname)`
+	placeholders are quoted as string literals suitable for
+	the specified host language.  This is meant to produce
+	a scriptlet that can directly be `eval`ed.
+
+--points-at=<object>::
+	Only list refs which points at the given object.
+
+--merged[=<object>]::
+	Only list refs whose tips are reachable from the
+	specified commit (HEAD if not specified).
+
+--no-merged[=<object>]::
+	Only list refs whose tips are not reachable from the
+	specified commit (HEAD if not specified).
+
+--contains[=<object>]::
+	Only list refs which contain the specified commit (HEAD if not
+	specified).
+
+--no-contains[=<object>]::
+	Only list refs which don't contain the specified commit (HEAD
+	if not specified).
+
+--ignore-case::
+	Sorting and filtering refs are case insensitive.
+
+--omit-empty::
+	Do not print a newline after formatted refs where the format expands
+	to the empty string.
+
+--exclude=<pattern>::
+	If one or more patterns are given, only refs which do not match
+	any excluded pattern(s) are shown. Matching is done using the
+	same rules as `<pattern>` above.
+
+--include-root-refs::
+	List root refs (HEAD and pseudorefs) apart from regular refs.
+
+--start-after=<marker>::
+    Allows paginating the output by skipping references up to and including the
+    specified marker. When paging, it should be noted that references may be
+    deleted, modified or added between invocations. Output will only yield those
+    references which follow the marker lexicographically. Output begins from the
+    first reference that would come after the marker alphabetically. Cannot be
+    used with `--sort=<key>` or `--stdin` options, or the _<pattern>_ argument(s)
+    to limit the refs.
diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc
index 060940904d..130d452de0 100644
--- a/Documentation/git-for-each-ref.adoc
+++ b/Documentation/git-for-each-ref.adoc
@@ -28,94 +28,7 @@ host language allowing their direct evaluation in that language.
 
 OPTIONS
 -------
-<pattern>...::
-	If one or more patterns are given, only refs are shown that
-	match against at least one pattern, either using fnmatch(3) or
-	literally, in the latter case matching completely or from the
-	beginning up to a slash.
-
---stdin::
-	If `--stdin` is supplied, then the list of patterns is read from
-	standard input instead of from the argument list.
-
---count=<count>::
-	By default the command shows all refs that match
-	`<pattern>`.  This option makes it stop after showing
-	that many refs.
-
---sort=<key>::
-	A field name to sort on.  Prefix `-` to sort in
-	descending order of the value.  When unspecified,
-	`refname` is used.  You may use the --sort=<key> option
-	multiple times, in which case the last key becomes the primary
-	key.
-
---format=<format>::
-	A string that interpolates `%(fieldname)` from a ref being shown and
-	the object it points at. In addition, the string literal `%%`
-	renders as `%` and `%xx` - where `xx` are hex digits - renders as
-	the character with hex code `xx`. For example, `%00` interpolates to
-	`\0` (NUL), `%09` to `\t` (TAB), and `%0a` to `\n` (LF).
-+
-When unspecified, `<format>` defaults to `%(objectname) SPC %(objecttype)
-TAB %(refname)`.
-
---color[=<when>]::
-	Respect any colors specified in the `--format` option. The
-	`<when>` field must be one of `always`, `never`, or `auto` (if
-	`<when>` is absent, behave as if `always` was given).
-
---shell::
---perl::
---python::
---tcl::
-	If given, strings that substitute `%(fieldname)`
-	placeholders are quoted as string literals suitable for
-	the specified host language.  This is meant to produce
-	a scriptlet that can directly be `eval`ed.
-
---points-at=<object>::
-	Only list refs which points at the given object.
-
---merged[=<object>]::
-	Only list refs whose tips are reachable from the
-	specified commit (HEAD if not specified).
-
---no-merged[=<object>]::
-	Only list refs whose tips are not reachable from the
-	specified commit (HEAD if not specified).
-
---contains[=<object>]::
-	Only list refs which contain the specified commit (HEAD if not
-	specified).
-
---no-contains[=<object>]::
-	Only list refs which don't contain the specified commit (HEAD
-	if not specified).
-
---ignore-case::
-	Sorting and filtering refs are case insensitive.
-
---omit-empty::
-	Do not print a newline after formatted refs where the format expands
-	to the empty string.
-
---exclude=<pattern>::
-	If one or more patterns are given, only refs which do not match
-	any excluded pattern(s) are shown. Matching is done using the
-	same rules as `<pattern>` above.
-
---include-root-refs::
-	List root refs (HEAD and pseudorefs) apart from regular refs.
-
---start-after=<marker>::
-    Allows paginating the output by skipping references up to and including the
-    specified marker. When paging, it should be noted that references may be
-    deleted, modified or added between invocations. Output will only yield those
-    references which follow the marker lexicographically. Output begins from the
-    first reference that would come after the marker alphabetically. Cannot be
-    used with `--sort=<key>` or `--stdin` options, or the _<pattern>_ argument(s)
-    to limit the refs.
+include::for-each-ref-options.adoc[]
 
 FIELD NAMES
 -----------
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][PATCH v6 2/6] builtin/for-each-ref: align usage string with the man page
  2025-08-05  9:27         ` [GSoC][PATCH v6 0/6] Add " Meet Soni
  2025-08-05  9:27           ` [GSoC][PATCH v6 1/6] doc: factor out common option Meet Soni
@ 2025-08-05  9:27           ` Meet Soni
  2025-08-05  9:27           ` [GSoC][PATCH v6 3/6] builtin/for-each-ref: factor out core logic into a helper Meet Soni
                             ` (5 subsequent siblings)
  7 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-05  9:27 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni, Aaron Lipman, Derrick Stolee,
	Ævar Arnfjörð Bjarmason, Elijah Newren
Usage string for `git for-each-ref` was out of sync with its official
documentation. The test `t0450-txt-doc-vs-help.sh` was marked as broken
due to this.
Update the usage string to match the documentation. This allows the test
to pass, so remove the corresponding 'known breakage' marker from the
test file.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 builtin/for-each-ref.c       | 15 ++++++++++-----
 t/t0450/adoc-help-mismatches |  1 -
 2 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index 8b5fe7b65e..fe62f07861 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -9,12 +9,17 @@
 #include "strbuf.h"
 #include "strvec.h"
 
+#define COMMON_USAGE_FOR_EACH_REF \
+	"[--count=<count>] [--shell|--perl|--python|--tcl]\n" \
+	"                         [(--sort=<key>)...] [--format=<format>]\n" \
+	"                         [--include-root-refs] [--points-at=<object>]\n" \
+	"                         [--merged[=<object>]] [--no-merged[=<object>]]\n" \
+	"                         [--contains[=<object>]] [--no-contains[=<object>]]\n" \
+	"                         [(--exclude=<pattern>)...] [--start-after=<marker>]\n" \
+	"                         [ --stdin | <pattern>... ]"
+
 static char const * const for_each_ref_usage[] = {
-	N_("git for-each-ref [<options>] [<pattern>]"),
-	N_("git for-each-ref [--points-at <object>]"),
-	N_("git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"),
-	N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"),
-	N_("git for-each-ref [--start-after <marker>]"),
+	"git for-each-ref " COMMON_USAGE_FOR_EACH_REF,
 	NULL
 };
 
diff --git a/t/t0450/adoc-help-mismatches b/t/t0450/adoc-help-mismatches
index 06b469bdee..2c6ecd5fc8 100644
--- a/t/t0450/adoc-help-mismatches
+++ b/t/t0450/adoc-help-mismatches
@@ -17,7 +17,6 @@ fast-export
 fast-import
 fetch-pack
 fmt-merge-msg
-for-each-ref
 format-patch
 fsck-objects
 fsmonitor--daemon
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][PATCH v6 3/6] builtin/for-each-ref: factor out core logic into a helper
  2025-08-05  9:27         ` [GSoC][PATCH v6 0/6] Add " Meet Soni
  2025-08-05  9:27           ` [GSoC][PATCH v6 1/6] doc: factor out common option Meet Soni
  2025-08-05  9:27           ` [GSoC][PATCH v6 2/6] builtin/for-each-ref: align usage string with the man page Meet Soni
@ 2025-08-05  9:27           ` Meet Soni
  2025-08-05  9:27           ` [GSoC][PATCH v6 4/6] builtin/refs: add list subcommand Meet Soni
                             ` (4 subsequent siblings)
  7 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-05  9:27 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni, Elijah Newren, Victoria Dye
The implementation of `git for-each-ref` is monolithic within
`cmd_for_each_ref()`, making it impossible to share its logic with other
commands. To enable code reuse for the upcoming `git refs list`
subcommand, refactor the core logic into a shared helper function.
Introduce a new `for-each-ref.h` header to define the public interface
for this shared logic. It contains the declaration for a new helper
function, `for_each_ref_core()`, and a macro for the common usage
options.
Move the option parsing, filtering, and formatting logic from
`cmd_for_each_ref()` into a new helper function named
`for_each_ref_core()`. This helper is made generic by accepting the
command's usage string as a parameter.
The original `cmd_for_each_ref()` is simplified to a thin wrapper that
is only responsible for defining its specific usage array and calling
the shared helper.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 builtin/for-each-ref.c | 41 +++++++++++++++++++----------------------
 for-each-ref.h         | 26 ++++++++++++++++++++++++++
 2 files changed, 45 insertions(+), 22 deletions(-)
 create mode 100644 for-each-ref.h
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index fe62f07861..4af33de576 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -2,6 +2,7 @@
 #include "commit.h"
 #include "config.h"
 #include "environment.h"
+#include "for-each-ref.h"
 #include "gettext.h"
 #include "object.h"
 #include "parse-options.h"
@@ -9,24 +10,7 @@
 #include "strbuf.h"
 #include "strvec.h"
 
-#define COMMON_USAGE_FOR_EACH_REF \
-	"[--count=<count>] [--shell|--perl|--python|--tcl]\n" \
-	"                         [(--sort=<key>)...] [--format=<format>]\n" \
-	"                         [--include-root-refs] [--points-at=<object>]\n" \
-	"                         [--merged[=<object>]] [--no-merged[=<object>]]\n" \
-	"                         [--contains[=<object>]] [--no-contains[=<object>]]\n" \
-	"                         [(--exclude=<pattern>)...] [--start-after=<marker>]\n" \
-	"                         [ --stdin | <pattern>... ]"
-
-static char const * const for_each_ref_usage[] = {
-	"git for-each-ref " COMMON_USAGE_FOR_EACH_REF,
-	NULL
-};
-
-int cmd_for_each_ref(int argc,
-		     const char **argv,
-		     const char *prefix,
-		     struct repository *repo)
+int for_each_ref_core(int argc, const char **argv, const char *prefix, struct repository *repo, const char *const *usage)
 {
 	struct ref_sorting *sorting;
 	struct string_list sorting_options = STRING_LIST_INIT_DUP;
@@ -75,17 +59,17 @@ int cmd_for_each_ref(int argc,
 	/* Set default (refname) sorting */
 	string_list_append(&sorting_options, "refname");
 
-	parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0);
+	parse_options(argc, argv, prefix, opts, usage, 0);
 	if (format.array_opts.max_count < 0) {
 		error("invalid --count argument: `%d'", format.array_opts.max_count);
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(usage, opts);
 	}
 	if (HAS_MULTI_BITS(format.quote_style)) {
 		error("more than one quoting style?");
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(usage, opts);
 	}
 	if (verify_ref_format(&format))
-		usage_with_options(for_each_ref_usage, opts);
+		usage_with_options(usage, opts);
 
 	if (filter.start_after && sorting_options.nr > 1)
 		die(_("cannot use --start-after with custom sort options"));
@@ -125,3 +109,16 @@ int cmd_for_each_ref(int argc,
 	strvec_clear(&vec);
 	return 0;
 }
+
+int cmd_for_each_ref(int argc,
+		     const char **argv,
+		     const char *prefix,
+		     struct repository *repo)
+{
+	static char const * const for_each_ref_usage[] = {
+		N_("git for-each-ref " COMMON_USAGE_FOR_EACH_REF),
+		NULL
+	};
+
+	return for_each_ref_core(argc, argv, prefix, repo, for_each_ref_usage);
+}
diff --git a/for-each-ref.h b/for-each-ref.h
new file mode 100644
index 0000000000..a5e0b6d17a
--- /dev/null
+++ b/for-each-ref.h
@@ -0,0 +1,26 @@
+#ifndef FOR_EACH_REF_H
+#define FOR_EACH_REF_H
+
+struct repository;
+
+/*
+ * Shared usage string for options common to git-for-each-ref(1)
+ * and git-refs-list(1). The command-specific part (e.g., "git refs list ")
+ * must be prepended by the caller.
+ */
+#define COMMON_USAGE_FOR_EACH_REF \
+	"[--count=<count>] [--shell|--perl|--python|--tcl]\n" \
+	"                         [(--sort=<key>)...] [--format=<format>]\n" \
+	"                         [--include-root-refs] [--points-at=<object>]\n" \
+	"                         [--merged[=<object>]] [--no-merged[=<object>]]\n" \
+	"                         [--contains[=<object>]] [--no-contains[=<object>]]\n" \
+	"                         [(--exclude=<pattern>)...] [--start-after=<marker>]\n" \
+	"                         [ --stdin | <pattern>... ]"
+
+/*
+ * The core logic for for-each-ref and its clones.
+ */
+int for_each_ref_core(int argc, const char **argv, const char *prefix,
+		      struct repository *repo, const char *const *usage);
+
+#endif /* FOR_EACH_REF_H */
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][PATCH v6 4/6] builtin/refs: add list subcommand
  2025-08-05  9:27         ` [GSoC][PATCH v6 0/6] Add " Meet Soni
                             ` (2 preceding siblings ...)
  2025-08-05  9:27           ` [GSoC][PATCH v6 3/6] builtin/for-each-ref: factor out core logic into a helper Meet Soni
@ 2025-08-05  9:27           ` Meet Soni
  2025-08-05  9:27           ` [GSoC][PATCH v6 5/6] t6300: refactor tests to be shareable Meet Soni
                             ` (3 subsequent siblings)
  7 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-05  9:27 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni, John Cai
Git's reference management is distributed across multiple commands. As
part of an ongoing effort to consolidate and modernize reference
handling, introduce a `list` subcommand under the `git refs` umbrella as
a replacement for `git for-each-ref`.
Implement `cmd_refs_list` by having it call the `for_each_ref_core()`
helper function. This helper was factored out of the original
`cmd_for_each_ref` in a preceding commit, allowing both commands to
share the same core logic as independent peers.
Add documentation for the new command. The man page leverages the shared
options file, created in a previous commit, by using the AsciiDoc
`include::` macro to ensure consistency with git-for-each-ref(1).
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 Documentation/git-refs.adoc | 16 ++++++++++++++++
 builtin/refs.c              | 14 ++++++++++++++
 2 files changed, 30 insertions(+)
diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
index 4d6dc994f9..e608980711 100644
--- a/Documentation/git-refs.adoc
+++ b/Documentation/git-refs.adoc
@@ -11,6 +11,13 @@ SYNOPSIS
 [synopsis]
 git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
 git refs verify [--strict] [--verbose]
+git refs list [--count=<count>] [--shell|--perl|--python|--tcl]
+		   [(--sort=<key>)...] [--format=<format>]
+		   [--include-root-refs] [--points-at=<object>]
+		   [--merged[=<object>]] [--no-merged[=<object>]]
+		   [--contains[=<object>]] [--no-contains[=<object>]]
+		   [(--exclude=<pattern>)...] [--start-after=<marker>]
+		   [ --stdin | <pattern>... ]
 
 DESCRIPTION
 -----------
@@ -26,6 +33,11 @@ migrate::
 verify::
 	Verify reference database consistency.
 
+list::
+	List references in the repository with support for filtering,
+	formatting, and sorting. This subcommand is an alias for
+	linkgit:git-for-each-ref[1] and offers identical functionality.
+
 OPTIONS
 -------
 
@@ -57,6 +69,10 @@ The following options are specific to 'git refs verify':
 --verbose::
 	When verifying the reference database consistency, be chatty.
 
+The following options are specific to 'git refs list':
+
+include::for-each-ref-options.adoc[]
+
 KNOWN LIMITATIONS
 -----------------
 
diff --git a/builtin/refs.c b/builtin/refs.c
index c7ad0a2963..76224feba4 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -6,6 +6,7 @@
 #include "refs.h"
 #include "strbuf.h"
 #include "worktree.h"
+#include "for-each-ref.h"
 
 #define REFS_MIGRATE_USAGE \
 	N_("git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]")
@@ -101,6 +102,17 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
 	return ret;
 }
 
+static int cmd_refs_list(int argc, const char **argv, const char *prefix,
+			   struct repository *repo)
+{
+	static char const * const refs_list_usage[] = {
+		N_("git refs list " COMMON_USAGE_FOR_EACH_REF),
+		NULL
+	};
+
+	return for_each_ref_core(argc, argv, prefix, repo, refs_list_usage);
+}
+
 int cmd_refs(int argc,
 	     const char **argv,
 	     const char *prefix,
@@ -109,12 +121,14 @@ int cmd_refs(int argc,
 	const char * const refs_usage[] = {
 		REFS_MIGRATE_USAGE,
 		REFS_VERIFY_USAGE,
+		"git refs list " COMMON_USAGE_FOR_EACH_REF,
 		NULL,
 	};
 	parse_opt_subcommand_fn *fn = NULL;
 	struct option opts[] = {
 		OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate),
 		OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify),
+		OPT_SUBCOMMAND("list", &fn, cmd_refs_list),
 		OPT_END(),
 	};
 
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][PATCH v6 5/6] t6300: refactor tests to be shareable
  2025-08-05  9:27         ` [GSoC][PATCH v6 0/6] Add " Meet Soni
                             ` (3 preceding siblings ...)
  2025-08-05  9:27           ` [GSoC][PATCH v6 4/6] builtin/refs: add list subcommand Meet Soni
@ 2025-08-05  9:27           ` Meet Soni
  2025-08-05  9:27           ` [GSoC][PATCH v6 6/6] t: add test for git refs list subcommand Meet Soni
                             ` (2 subsequent siblings)
  7 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-05  9:27 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni, Taylor Blau, Kousik Sanagavarapu, Hariom Verma,
	Jeff King
In preparation for adding tests for the new `git refs list` command,
refactor the existing t6300 test suite to make its logic shareable.
Move the core test logic from `t6300-for-each-ref.sh` into a new
`for-each-ref-tests.sh` file. Inside this new script, replace hardcoded
calls to "git for-each-ref" with the `$git_for_each_ref` variable.
The original `t6300-for-each-ref.sh` script now becomes a simple
"driver". It is responsible for setting the default value of the
variable and then sourcing the test library.
This new structure follows the established pattern used for sharing
tests between `git-blame` and `git-annotate` and prepares the test suite
for the `refs list` tests to be added in a subsequent commit.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 t/for-each-ref-tests.sh | 2141 +++++++++++++++++++++++++++++++++++++++
 t/t6300-for-each-ref.sh | 2140 +-------------------------------------
 2 files changed, 2143 insertions(+), 2138 deletions(-)
 create mode 100644 t/for-each-ref-tests.sh
diff --git a/t/for-each-ref-tests.sh b/t/for-each-ref-tests.sh
new file mode 100644
index 0000000000..e3ad19298a
--- /dev/null
+++ b/t/for-each-ref-tests.sh
@@ -0,0 +1,2141 @@
+git_for_each_ref=${git_for_each_ref:-git for-each-ref}
+GNUPGHOME_NOT_USED=$GNUPGHOME
+. "$TEST_DIRECTORY"/lib-gpg.sh
+. "$TEST_DIRECTORY"/lib-terminal.sh
+
+# Mon Jul 3 23:18:43 2006 +0000
+datestamp=1151968723
+setdate_and_increment () {
+    GIT_COMMITTER_DATE="$datestamp +0200"
+    datestamp=$(expr "$datestamp" + 1)
+    GIT_AUTHOR_DATE="$datestamp +0200"
+    datestamp=$(expr "$datestamp" + 1)
+    export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+}
+
+test_object_file_size () {
+	oid=$(git rev-parse "$1")
+	path=".git/objects/$(test_oid_to_path $oid)"
+	test_file_size "$path"
+}
+
+test_expect_success setup '
+	# setup .mailmap
+	cat >.mailmap <<-EOF &&
+	A Thor <athor@example.com> A U Thor <author@example.com>
+	C Mitter <cmitter@example.com> C O Mitter <committer@example.com>
+	EOF
+
+	setdate_and_increment &&
+	echo "Using $datestamp" > one &&
+	git add one &&
+	git commit -m "Initial" &&
+	git branch -M main &&
+	setdate_and_increment &&
+	git tag -a -m "Tagging at $datestamp" testtag &&
+	git update-ref refs/remotes/origin/main main &&
+	git remote add origin nowhere &&
+	git config branch.main.remote origin &&
+	git config branch.main.merge refs/heads/main &&
+	git remote add myfork elsewhere &&
+	git config remote.pushdefault myfork &&
+	git config push.default current
+'
+
+test_atom () {
+	case "$1" in
+		head) ref=refs/heads/main ;;
+		 tag) ref=refs/tags/testtag ;;
+		 sym) ref=refs/heads/sym ;;
+		   *) ref=$1 ;;
+	esac
+	format=$2
+	test_do=test_expect_${4:-success}
+
+	printf '%s\n' "$3" >expected
+	$test_do $PREREQ "basic atom: $ref $format" '
+		${git_for_each_ref} --format="%($format)" "$ref" >actual &&
+		sanitize_pgp <actual >actual.clean &&
+		test_cmp expected actual.clean
+	'
+
+	# Automatically test "contents:size" atom after testing "contents"
+	if test "$format" = "contents"
+	then
+		# for commit leg, $3 is changed there
+		expect=$(printf '%s' "$3" | wc -c)
+		$test_do $PREREQ "basic atom: $ref contents:size" '
+			type=$(git cat-file -t "$ref") &&
+			case $type in
+			tag)
+				# We cannot use $3 as it expects sanitize_pgp to run
+				git cat-file tag $ref >out &&
+				expect=$(tail -n +6 out | wc -c) &&
+				rm -f out ;;
+			tree | blob)
+				expect="" ;;
+			commit)
+				: "use the calculated expect" ;;
+			*)
+				BUG "unknown object type" ;;
+			esac &&
+			# Leave $expect unquoted to lose possible leading whitespaces
+			echo $expect >expected &&
+			${git_for_each_ref} --format="%(contents:size)" "$ref" >actual &&
+			test_cmp expected actual
+		'
+	fi
+}
+
+hexlen=$(test_oid hexsz)
+
+test_atom head refname refs/heads/main
+test_atom head refname: refs/heads/main
+test_atom head refname:short main
+test_atom head refname:lstrip=1 heads/main
+test_atom head refname:lstrip=2 main
+test_atom head refname:lstrip=-1 main
+test_atom head refname:lstrip=-2 heads/main
+test_atom head refname:rstrip=1 refs/heads
+test_atom head refname:rstrip=2 refs
+test_atom head refname:rstrip=-1 refs
+test_atom head refname:rstrip=-2 refs/heads
+test_atom head refname:strip=1 heads/main
+test_atom head refname:strip=2 main
+test_atom head refname:strip=-1 main
+test_atom head refname:strip=-2 heads/main
+test_atom head upstream refs/remotes/origin/main
+test_atom head upstream:short origin/main
+test_atom head upstream:lstrip=2 origin/main
+test_atom head upstream:lstrip=-2 origin/main
+test_atom head upstream:rstrip=2 refs/remotes
+test_atom head upstream:rstrip=-2 refs/remotes
+test_atom head upstream:strip=2 origin/main
+test_atom head upstream:strip=-2 origin/main
+test_atom head push refs/remotes/myfork/main
+test_atom head push:short myfork/main
+test_atom head push:lstrip=1 remotes/myfork/main
+test_atom head push:lstrip=-1 main
+test_atom head push:rstrip=1 refs/remotes/myfork
+test_atom head push:rstrip=-1 refs
+test_atom head push:strip=1 remotes/myfork/main
+test_atom head push:strip=-1 main
+test_atom head objecttype commit
+test_atom head objectsize $((131 + hexlen))
+test_atom head objectsize:disk $(test_object_file_size refs/heads/main)
+test_atom head deltabase $ZERO_OID
+test_atom head objectname $(git rev-parse refs/heads/main)
+test_atom head objectname:short $(git rev-parse --short refs/heads/main)
+test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
+test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
+test_atom head tree $(git rev-parse refs/heads/main^{tree})
+test_atom head tree:short $(git rev-parse --short refs/heads/main^{tree})
+test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/main^{tree})
+test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/main^{tree})
+test_atom head parent ''
+test_atom head parent:short ''
+test_atom head parent:short=1 ''
+test_atom head parent:short=10 ''
+test_atom head numparent 0
+test_atom head object ''
+test_atom head type ''
+test_atom head raw "$(git cat-file commit refs/heads/main)
+"
+test_atom head '*objectname' ''
+test_atom head '*objecttype' ''
+test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
+test_atom head authorname 'A U Thor'
+test_atom head authorname:mailmap 'A Thor'
+test_atom head authoremail '<author@example.com>'
+test_atom head authoremail:trim 'author@example.com'
+test_atom head authoremail:localpart 'author'
+test_atom head authoremail:trim,localpart 'author'
+test_atom head authoremail:mailmap '<athor@example.com>'
+test_atom head authoremail:mailmap,trim 'athor@example.com'
+test_atom head authoremail:trim,mailmap 'athor@example.com'
+test_atom head authoremail:mailmap,localpart 'athor'
+test_atom head authoremail:localpart,mailmap 'athor'
+test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor'
+test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
+test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
+test_atom head committername 'C O Mitter'
+test_atom head committername:mailmap 'C Mitter'
+test_atom head committeremail '<committer@example.com>'
+test_atom head committeremail:trim 'committer@example.com'
+test_atom head committeremail:localpart 'committer'
+test_atom head committeremail:localpart,trim 'committer'
+test_atom head committeremail:mailmap '<cmitter@example.com>'
+test_atom head committeremail:mailmap,trim 'cmitter@example.com'
+test_atom head committeremail:trim,mailmap 'cmitter@example.com'
+test_atom head committeremail:mailmap,localpart 'cmitter'
+test_atom head committeremail:localpart,mailmap 'cmitter'
+test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter'
+test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
+test_atom head tag ''
+test_atom head tagger ''
+test_atom head taggername ''
+test_atom head taggeremail ''
+test_atom head taggeremail:trim ''
+test_atom head taggeremail:localpart ''
+test_atom head taggerdate ''
+test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
+test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
+test_atom head subject 'Initial'
+test_atom head subject:sanitize 'Initial'
+test_atom head contents:subject 'Initial'
+test_atom head body ''
+test_atom head contents:body ''
+test_atom head contents:signature ''
+test_atom head contents 'Initial
+'
+test_atom head HEAD '*'
+
+test_atom tag refname refs/tags/testtag
+test_atom tag refname:short testtag
+test_atom tag upstream ''
+test_atom tag push ''
+test_atom tag objecttype tag
+test_atom tag objectsize $((114 + hexlen))
+test_atom tag objectsize:disk $(test_object_file_size refs/tags/testtag)
+test_atom tag '*objectsize:disk' $(test_object_file_size refs/heads/main)
+test_atom tag deltabase $ZERO_OID
+test_atom tag '*deltabase' $ZERO_OID
+test_atom tag objectname $(git rev-parse refs/tags/testtag)
+test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
+test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
+test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
+test_atom tag tree ''
+test_atom tag tree:short ''
+test_atom tag tree:short=1 ''
+test_atom tag tree:short=10 ''
+test_atom tag parent ''
+test_atom tag parent:short ''
+test_atom tag parent:short=1 ''
+test_atom tag parent:short=10 ''
+test_atom tag numparent ''
+test_atom tag object $(git rev-parse refs/tags/testtag^0)
+test_atom tag type 'commit'
+test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{})
+test_atom tag '*objecttype' 'commit'
+test_atom tag author ''
+test_atom tag authorname ''
+test_atom tag authorname:mailmap ''
+test_atom tag authoremail ''
+test_atom tag authoremail:trim ''
+test_atom tag authoremail:localpart ''
+test_atom tag authoremail:trim,localpart ''
+test_atom tag authoremail:mailmap ''
+test_atom tag authoremail:mailmap,trim ''
+test_atom tag authoremail:trim,mailmap ''
+test_atom tag authoremail:mailmap,localpart ''
+test_atom tag authoremail:localpart,mailmap ''
+test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim ''
+test_atom tag authordate ''
+test_atom tag committer ''
+test_atom tag committername ''
+test_atom tag committername:mailmap ''
+test_atom tag committeremail ''
+test_atom tag committeremail:trim ''
+test_atom tag committeremail:localpart ''
+test_atom tag committeremail:localpart,trim ''
+test_atom tag committeremail:mailmap ''
+test_atom tag committeremail:mailmap,trim ''
+test_atom tag committeremail:trim,mailmap ''
+test_atom tag committeremail:mailmap,localpart ''
+test_atom tag committeremail:localpart,mailmap ''
+test_atom tag committeremail:trim,mailmap,trim,trim,localpart ''
+test_atom tag committerdate ''
+test_atom tag tag 'testtag'
+test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
+test_atom tag taggername 'C O Mitter'
+test_atom tag taggername:mailmap 'C Mitter'
+test_atom tag taggeremail '<committer@example.com>'
+test_atom tag taggeremail:trim 'committer@example.com'
+test_atom tag taggeremail:localpart 'committer'
+test_atom tag taggeremail:trim,localpart 'committer'
+test_atom tag taggeremail:mailmap '<cmitter@example.com>'
+test_atom tag taggeremail:mailmap,trim 'cmitter@example.com'
+test_atom tag taggeremail:trim,mailmap 'cmitter@example.com'
+test_atom tag taggeremail:mailmap,localpart 'cmitter'
+test_atom tag taggeremail:localpart,mailmap 'cmitter'
+test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter'
+test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
+test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
+test_atom tag subject 'Tagging at 1151968727'
+test_atom tag subject:sanitize 'Tagging-at-1151968727'
+test_atom tag contents:subject 'Tagging at 1151968727'
+test_atom tag body ''
+test_atom tag contents:body ''
+test_atom tag contents:signature ''
+test_atom tag contents 'Tagging at 1151968727
+'
+test_atom tag HEAD ' '
+
+test_expect_success 'basic atom: refs/tags/testtag *raw' '
+	git cat-file commit refs/tags/testtag^{} >expected &&
+	${git_for_each_ref} --format="%(*raw)" refs/tags/testtag >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_expect_success 'Check invalid atoms names are errors' '
+	test_must_fail ${git_for_each_ref} --format="%(INVALID)" refs/heads
+'
+
+test_expect_success 'Check format specifiers are ignored in naming date atoms' '
+	${git_for_each_ref} --format="%(authordate)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:default) %(authordate)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate) %(authordate:default)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:default) %(authordate:default)" refs/heads
+'
+
+test_expect_success 'Check valid format specifiers for date fields' '
+	${git_for_each_ref} --format="%(authordate:default)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:relative)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:short)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:local)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:iso8601)" refs/heads &&
+	${git_for_each_ref} --format="%(authordate:rfc2822)" refs/heads
+'
+
+test_expect_success 'Check invalid format specifiers are errors' '
+	test_must_fail ${git_for_each_ref} --format="%(authordate:INVALID)" refs/heads
+'
+
+test_expect_success 'arguments to %(objectname:short=) must be positive integers' '
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=0)" &&
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=-1)" &&
+	test_must_fail ${git_for_each_ref} --format="%(objectname:short=foo)"
+'
+
+test_bad_atom () {
+	case "$1" in
+	head) ref=refs/heads/main ;;
+	 tag) ref=refs/tags/testtag ;;
+	 sym) ref=refs/heads/sym ;;
+	   *) ref=$1 ;;
+	esac
+	format=$2
+	test_do=test_expect_${4:-success}
+
+	printf '%s\n' "$3" >expect
+	$test_do $PREREQ "err basic atom: $ref $format" '
+		test_must_fail ${git_for_each_ref} \
+			--format="%($format)" "$ref" 2>error &&
+		test_cmp expect error
+	'
+}
+
+test_bad_atom head 'authoremail:foo' \
+	'fatal: unrecognized %(authoremail) argument: foo'
+
+test_bad_atom head 'authoremail:mailmap,trim,bar' \
+	'fatal: unrecognized %(authoremail) argument: bar'
+
+test_bad_atom head 'authoremail:trim,' \
+	'fatal: unrecognized %(authoremail) argument: '
+
+test_bad_atom head 'authoremail:mailmaptrim' \
+	'fatal: unrecognized %(authoremail) argument: trim'
+
+test_bad_atom head 'committeremail: ' \
+	'fatal: unrecognized %(committeremail) argument:  '
+
+test_bad_atom head 'committeremail: trim,foo' \
+	'fatal: unrecognized %(committeremail) argument:  trim,foo'
+
+test_bad_atom head 'committeremail:mailmap,localpart ' \
+	'fatal: unrecognized %(committeremail) argument:  '
+
+test_bad_atom head 'committeremail:trim_localpart' \
+	'fatal: unrecognized %(committeremail) argument: _localpart'
+
+test_bad_atom head 'committeremail:localpart,,,trim' \
+	'fatal: unrecognized %(committeremail) argument: ,,trim'
+
+test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \
+	'fatal: unrecognized %(taggeremail) argument:  foo '
+
+test_bad_atom tag 'taggeremail:trim,localpart,' \
+	'fatal: unrecognized %(taggeremail) argument: '
+
+test_bad_atom tag 'taggeremail:mailmap;localpart trim' \
+	'fatal: unrecognized %(taggeremail) argument: ;localpart trim'
+
+test_bad_atom tag 'taggeremail:localpart trim' \
+	'fatal: unrecognized %(taggeremail) argument:  trim'
+
+test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \
+	'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim'
+
+test_date () {
+	f=$1 &&
+	committer_date=$2 &&
+	author_date=$3 &&
+	tagger_date=$4 &&
+	cat >expected <<-EOF &&
+	'refs/heads/main' '$committer_date' '$author_date'
+	'refs/tags/testtag' '$tagger_date'
+	EOF
+	(
+		${git_for_each_ref} --shell \
+			--format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
+			refs/heads &&
+		${git_for_each_ref} --shell \
+			--format="%(refname) %(taggerdate${f:+:$f})" \
+			refs/tags
+	) >actual &&
+	test_cmp expected actual
+}
+
+test_expect_success 'Check unformatted date fields output' '
+	test_date "" \
+		"Tue Jul 4 01:18:43 2006 +0200" \
+		"Tue Jul 4 01:18:44 2006 +0200" \
+		"Tue Jul 4 01:18:45 2006 +0200"
+'
+
+test_expect_success 'Check format "default" formatted date fields output' '
+	test_date default \
+		"Tue Jul 4 01:18:43 2006 +0200" \
+		"Tue Jul 4 01:18:44 2006 +0200" \
+		"Tue Jul 4 01:18:45 2006 +0200"
+'
+
+test_expect_success 'Check format "default-local" date fields output' '
+	test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006"
+'
+
+# Don't know how to do relative check because I can't know when this script
+# is going to be run and can't fake the current time to git, and hence can't
+# provide expected output.  Instead, I'll just make sure that "relative"
+# doesn't exit in error
+test_expect_success 'Check format "relative" date fields output' '
+	f=relative &&
+	(${git_for_each_ref} --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
+	${git_for_each_ref} --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
+'
+
+# We just check that this is the same as "relative" for now.
+test_expect_success 'Check format "relative-local" date fields output' '
+	test_date relative-local \
+		"$(${git_for_each_ref} --format="%(committerdate:relative)" refs/heads)" \
+		"$(${git_for_each_ref} --format="%(authordate:relative)" refs/heads)" \
+		"$(${git_for_each_ref} --format="%(taggerdate:relative)" refs/tags)"
+'
+
+test_expect_success 'Check format "short" date fields output' '
+	test_date short 2006-07-04 2006-07-04 2006-07-04
+'
+
+test_expect_success 'Check format "short-local" date fields output' '
+	test_date short-local 2006-07-03 2006-07-03 2006-07-03
+'
+
+test_expect_success 'Check format "local" date fields output' '
+	test_date local \
+		"Mon Jul 3 23:18:43 2006" \
+		"Mon Jul 3 23:18:44 2006" \
+		"Mon Jul 3 23:18:45 2006"
+'
+
+test_expect_success 'Check format "iso8601" date fields output' '
+	test_date iso8601 \
+		"2006-07-04 01:18:43 +0200" \
+		"2006-07-04 01:18:44 +0200" \
+		"2006-07-04 01:18:45 +0200"
+'
+
+test_expect_success 'Check format "iso8601-local" date fields output' '
+	test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000"
+'
+
+test_expect_success 'Check format "rfc2822" date fields output' '
+	test_date rfc2822 \
+		"Tue, 4 Jul 2006 01:18:43 +0200" \
+		"Tue, 4 Jul 2006 01:18:44 +0200" \
+		"Tue, 4 Jul 2006 01:18:45 +0200"
+'
+
+test_expect_success 'Check format "rfc2822-local" date fields output' '
+	test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000"
+'
+
+test_expect_success 'Check format "raw" date fields output' '
+	test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200"
+'
+
+test_expect_success 'Check format "raw-local" date fields output' '
+	test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000"
+'
+
+test_expect_success 'Check format of strftime date fields' '
+	echo "my date is 2006-07-04" >expected &&
+	${git_for_each_ref} \
+	  --format="%(authordate:format:my date is %Y-%m-%d)" \
+	  refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Check format of strftime-local date fields' '
+	echo "my date is 2006-07-03" >expected &&
+	${git_for_each_ref} \
+	  --format="%(authordate:format-local:my date is %Y-%m-%d)" \
+	  refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'exercise strftime with odd fields' '
+	echo >expected &&
+	${git_for_each_ref} --format="%(authordate:format:)" refs/heads >actual &&
+	test_cmp expected actual &&
+	long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" &&
+	echo $long >expected &&
+	${git_for_each_ref} --format="%(authordate:format:$long)" refs/heads >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/heads/main
+refs/remotes/origin/main
+refs/tags/testtag
+EOF
+
+test_expect_success 'Verify ascending sort' '
+	${git_for_each_ref} --format="%(refname)" --sort=refname >actual &&
+	test_cmp expected actual
+'
+
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/remotes/origin/main
+refs/heads/main
+EOF
+
+test_expect_success 'Verify descending sort' '
+	${git_for_each_ref} --format="%(refname)" --sort=-refname >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Give help even with invalid sort atoms' '
+	test_expect_code 129 ${git_for_each_ref} --sort=bogus -h >actual 2>&1 &&
+	grep "^usage: ${git_for_each_ref}" actual
+'
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/tags/testtag-2
+EOF
+
+test_expect_success 'exercise patterns with prefixes' '
+	git tag testtag-2 &&
+	test_when_finished "git tag -d testtag-2" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/testtag refs/tags/testtag-2 >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/testtag
+refs/tags/testtag-2
+EOF
+
+test_expect_success 'exercise glob patterns with prefixes' '
+	git tag testtag-2 &&
+	test_when_finished "git tag -d testtag-2" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/testtag "refs/tags/testtag-*" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/bar
+refs/tags/baz
+refs/tags/testtag
+EOF
+
+test_expect_success 'exercise patterns with prefix exclusions' '
+	for tag in foo/one foo/two foo/three bar baz
+	do
+		git tag "$tag" || return 1
+	done &&
+	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/ --exclude=refs/tags/foo >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+refs/tags/bar
+refs/tags/baz
+refs/tags/foo/one
+refs/tags/testtag
+EOF
+
+test_expect_success 'exercise patterns with pattern exclusions' '
+	for tag in foo/one foo/two foo/three bar baz
+	do
+		git tag "$tag" || return 1
+	done &&
+	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
+	${git_for_each_ref} --format="%(refname)" \
+		refs/tags/ --exclude="refs/tags/foo/t*" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+'refs/heads/main'
+'refs/remotes/origin/main'
+'refs/tags/testtag'
+EOF
+
+test_expect_success 'Quoting style: shell' '
+	${git_for_each_ref} --shell --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Quoting style: perl' '
+	${git_for_each_ref} --perl --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Quoting style: python' '
+	${git_for_each_ref} --python --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+"refs/heads/main"
+"refs/remotes/origin/main"
+"refs/tags/testtag"
+EOF
+
+test_expect_success 'Quoting style: tcl' '
+	${git_for_each_ref} --tcl --format="%(refname)" >actual &&
+	test_cmp expected actual
+'
+
+for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
+	test_expect_success "more than one quoting style: $i" "
+		test_must_fail ${git_for_each_ref} $i 2>err &&
+		grep '^error: more than one quoting style' err
+	"
+done
+
+test_expect_success 'setup for upstream:track[short]' '
+	test_commit two
+'
+
+test_atom head upstream:track '[ahead 1]'
+test_atom head upstream:trackshort '>'
+test_atom head upstream:track,nobracket 'ahead 1'
+test_atom head upstream:nobracket,track 'ahead 1'
+
+test_expect_success 'setup for push:track[short]' '
+	test_commit third &&
+	git update-ref refs/remotes/myfork/main main &&
+	git reset main~1
+'
+
+test_atom head push:track '[behind 1]'
+test_atom head push:trackshort '<'
+
+test_expect_success 'Check that :track[short] cannot be used with other atoms' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:track)" 2>/dev/null &&
+	test_must_fail ${git_for_each_ref} --format="%(refname:trackshort)" 2>/dev/null
+'
+
+test_expect_success 'Check that :track[short] works when upstream is invalid' '
+	cat >expected <<-\EOF &&
+	[gone]
+
+	EOF
+	test_when_finished "git config branch.main.merge refs/heads/main" &&
+	git config branch.main.merge refs/heads/does-not-exist &&
+	${git_for_each_ref} \
+		--format="%(upstream:track)$LF%(upstream:trackshort)" \
+		refs/heads >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Check for invalid refname format' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:INVALID)"
+'
+
+test_expect_success 'set up color tests' '
+	cat >expected.color <<-EOF &&
+	$(git rev-parse --short refs/heads/main) <GREEN>main<RESET>
+	$(git rev-parse --short refs/remotes/myfork/main) <GREEN>myfork/main<RESET>
+	$(git rev-parse --short refs/remotes/origin/main) <GREEN>origin/main<RESET>
+	$(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET>
+	$(git rev-parse --short refs/tags/third) <GREEN>third<RESET>
+	$(git rev-parse --short refs/tags/two) <GREEN>two<RESET>
+	EOF
+	sed "s/<[^>]*>//g" <expected.color >expected.bare &&
+	color_format="%(objectname:short) %(color:green)%(refname:short)"
+'
+
+test_expect_success TTY '%(color) shows color with a tty' '
+	test_terminal ${git_for_each_ref} --format="$color_format" >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	test_cmp expected.color actual
+'
+
+test_expect_success '%(color) does not show color without tty' '
+	TERM=vt100 ${git_for_each_ref} --format="$color_format" >actual &&
+	test_cmp expected.bare actual
+'
+
+test_expect_success '--color can override tty check' '
+	${git_for_each_ref} --color --format="$color_format" >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	test_cmp expected.color actual
+'
+
+test_expect_success 'color.ui=always does not override tty check' '
+	git -c color.ui=always ${git_for_each_ref#git} --format="$color_format" >actual &&
+	test_cmp expected.bare actual
+'
+
+test_expect_success 'setup for describe atom tests' '
+	git init -b master describe-repo &&
+	(
+		cd describe-repo &&
+
+		test_commit --no-tag one &&
+		git tag tagone &&
+
+		test_commit --no-tag two &&
+		git tag -a -m "tag two" tagtwo
+	)
+'
+
+test_expect_success 'describe atom vs git describe' '
+	(
+		cd describe-repo &&
+
+		${git_for_each_ref} --format="%(objectname)" \
+			refs/tags/ >obj &&
+		while read hash
+		do
+			if desc=$(git describe $hash)
+			then
+				: >expect-contains-good
+			else
+				: >expect-contains-bad
+			fi &&
+			echo "$hash $desc" || return 1
+		done <obj >expect &&
+		test_path_exists expect-contains-good &&
+		test_path_exists expect-contains-bad &&
+
+		${git_for_each_ref} --format="%(objectname) %(describe)" \
+			refs/tags/ >actual 2>err &&
+		test_cmp expect actual &&
+		test_must_be_empty err
+	)
+'
+
+test_expect_success 'describe:tags vs describe --tags' '
+	(
+		cd describe-repo &&
+		git describe --tags >expect &&
+		${git_for_each_ref} --format="%(describe:tags)" \
+				refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
+	(
+		cd describe-repo &&
+
+		# Case 1: We have commits between HEAD and the most
+		#	  recent tag reachable from it
+		test_commit --no-tag file &&
+		git describe --abbrev=14 >expect &&
+		${git_for_each_ref} --format="%(describe:abbrev=14)" \
+			refs/heads/master >actual &&
+		test_cmp expect actual &&
+
+		# Make sure the hash used is at least 14 digits long
+		sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart &&
+		test 15 -le $(wc -c <hexpart) &&
+
+		# Case 2: We have a tag at HEAD, describe directly gives
+		#	  the name of the tag
+		git tag -a -m tagged tagname &&
+		git describe --abbrev=14 >expect &&
+		${git_for_each_ref} --format="%(describe:abbrev=14)" \
+			refs/heads/master >actual &&
+		test_cmp expect actual &&
+		test tagname = $(cat actual)
+	)
+'
+
+test_expect_success 'describe:match=... vs describe --match ...' '
+	(
+		cd describe-repo &&
+		git tag -a -m "tag foo" tag-foo &&
+		git describe --match "*-foo" >expect &&
+		${git_for_each_ref} --format="%(describe:match="*-foo")" \
+			refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'describe:exclude:... vs describe --exclude ...' '
+	(
+		cd describe-repo &&
+		git tag -a -m "tag bar" tag-bar &&
+		git describe --exclude "*-bar" >expect &&
+		${git_for_each_ref} --format="%(describe:exclude="*-bar")" \
+			refs/heads/master >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'deref with describe atom' '
+	(
+		cd describe-repo &&
+		cat >expect <<-\EOF &&
+
+		tagname
+		tagname
+		tagname
+
+		tagtwo
+		EOF
+		${git_for_each_ref} --format="%(*describe)" >actual &&
+		test_cmp expect actual
+	)
+'
+
+test_expect_success 'err on bad describe atom arg' '
+	(
+		cd describe-repo &&
+
+		# The bad arg is the only arg passed to describe atom
+		cat >expect <<-\EOF &&
+		fatal: unrecognized %(describe) argument: baz
+		EOF
+		test_must_fail ${git_for_each_ref} --format="%(describe:baz)" \
+			refs/heads/master 2>actual &&
+		test_cmp expect actual &&
+
+		# The bad arg is in the middle of the option string
+		# passed to the describe atom
+		cat >expect <<-\EOF &&
+		fatal: unrecognized %(describe) argument: qux=1,abbrev=14
+		EOF
+		test_must_fail ${git_for_each_ref} \
+			--format="%(describe:tags,qux=1,abbrev=14)" \
+			ref/heads/master 2>actual &&
+		test_cmp expect actual
+	)
+'
+
+cat >expected <<\EOF
+heads/main
+tags/main
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs (strict)' '
+	git config --bool core.warnambiguousrefs true &&
+	git checkout -b newtag &&
+	echo "Using $datestamp" > one &&
+	git add one &&
+	git commit -m "Branch" &&
+	setdate_and_increment &&
+	git tag -m "Tagging at $datestamp" main &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+heads/main
+main
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs (loose)' '
+	git config --bool core.warnambiguousrefs false &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<\EOF
+heads/ambiguous
+ambiguous
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs II (loose)' '
+	git checkout main &&
+	git tag ambiguous testtag^0 &&
+	git branch ambiguous testtag^0 &&
+	${git_for_each_ref} --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'create tag without tagger' '
+	git tag -a -m "Broken tag" taggerless &&
+	git tag -f taggerless $(git cat-file tag taggerless |
+		sed -e "/^tagger /d" |
+		git hash-object --literally --stdin -w -t tag)
+'
+
+test_atom refs/tags/taggerless type 'commit'
+test_atom refs/tags/taggerless tag 'taggerless'
+test_atom refs/tags/taggerless tagger ''
+test_atom refs/tags/taggerless taggername ''
+test_atom refs/tags/taggerless taggeremail ''
+test_atom refs/tags/taggerless taggeremail:trim ''
+test_atom refs/tags/taggerless taggeremail:localpart ''
+test_atom refs/tags/taggerless taggerdate ''
+test_atom refs/tags/taggerless committer ''
+test_atom refs/tags/taggerless committername ''
+test_atom refs/tags/taggerless committeremail ''
+test_atom refs/tags/taggerless committeremail:trim ''
+test_atom refs/tags/taggerless committeremail:localpart ''
+test_atom refs/tags/taggerless committerdate ''
+test_atom refs/tags/taggerless subject 'Broken tag'
+
+test_expect_success 'an unusual tag with an incomplete line' '
+
+	git tag -m "bogo" bogo &&
+	bogo=$(git cat-file tag bogo) &&
+	bogo=$(printf "%s" "$bogo" | git mktag) &&
+	git tag -f bogo "$bogo" &&
+	${git_for_each_ref} --format "%(body)" refs/tags/bogo
+
+'
+
+test_expect_success 'create tag with subject and body content' '
+	cat >>msg <<-\EOF &&
+		the subject line
+
+		first body line
+		second body line
+	EOF
+	git tag -F msg subject-body
+'
+test_atom refs/tags/subject-body subject 'the subject line'
+test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
+test_atom refs/tags/subject-body body 'first body line
+second body line
+'
+test_atom refs/tags/subject-body contents 'the subject line
+
+first body line
+second body line
+'
+
+test_expect_success 'create tag with multiline subject' '
+	cat >msg <<-\EOF &&
+		first subject line
+		second subject line
+
+		first body line
+		second body line
+	EOF
+	git tag -F msg multiline
+'
+test_atom refs/tags/multiline subject 'first subject line second subject line'
+test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
+test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
+test_atom refs/tags/multiline body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:body 'first body line
+second body line
+'
+test_atom refs/tags/multiline contents:signature ''
+test_atom refs/tags/multiline contents 'first subject line
+second subject line
+
+first body line
+second body line
+'
+
+test_expect_success GPG 'create signed tags' '
+	git tag -s -m "" signed-empty &&
+	git tag -s -m "subject line" signed-short &&
+	cat >msg <<-\EOF &&
+	subject line
+
+	body contents
+	EOF
+	git tag -s -F msg signed-long
+'
+
+sig='-----BEGIN PGP SIGNATURE-----
+-----END PGP SIGNATURE-----
+'
+
+PREREQ=GPG
+test_atom refs/tags/signed-empty subject ''
+test_atom refs/tags/signed-empty subject:sanitize ''
+test_atom refs/tags/signed-empty contents:subject ''
+test_atom refs/tags/signed-empty body "$sig"
+test_atom refs/tags/signed-empty contents:body ''
+test_atom refs/tags/signed-empty contents:signature "$sig"
+test_atom refs/tags/signed-empty contents "$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
+	git cat-file tag refs/tags/signed-empty >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-empty >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_atom refs/tags/signed-short subject 'subject line'
+test_atom refs/tags/signed-short subject:sanitize 'subject-line'
+test_atom refs/tags/signed-short contents:subject 'subject line'
+test_atom refs/tags/signed-short body "$sig"
+test_atom refs/tags/signed-short contents:body ''
+test_atom refs/tags/signed-short contents:signature "$sig"
+test_atom refs/tags/signed-short contents "subject line
+$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
+	git cat-file tag refs/tags/signed-short >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-short >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_atom refs/tags/signed-long subject 'subject line'
+test_atom refs/tags/signed-long subject:sanitize 'subject-line'
+test_atom refs/tags/signed-long contents:subject 'subject line'
+test_atom refs/tags/signed-long body "body contents
+$sig"
+test_atom refs/tags/signed-long contents:body 'body contents
+'
+test_atom refs/tags/signed-long contents:signature "$sig"
+test_atom refs/tags/signed-long contents "subject line
+
+body contents
+$sig"
+
+test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
+	git cat-file tag refs/tags/signed-long >expected &&
+	${git_for_each_ref} --format="%(raw)" refs/tags/signed-long >actual &&
+	sanitize_pgp <expected >expected.clean &&
+	echo >>expected.clean &&
+	sanitize_pgp <actual >actual.clean &&
+	test_cmp expected.clean actual.clean
+'
+
+test_expect_success 'set up refs pointing to tree and blob' '
+	git update-ref refs/mytrees/first refs/heads/main^{tree} &&
+	git update-ref refs/myblobs/first refs/heads/main:one
+'
+
+test_atom refs/mytrees/first subject ""
+test_atom refs/mytrees/first contents:subject ""
+test_atom refs/mytrees/first body ""
+test_atom refs/mytrees/first contents:body ""
+test_atom refs/mytrees/first contents:signature ""
+test_atom refs/mytrees/first contents ""
+
+test_expect_success 'basic atom: refs/mytrees/first raw' '
+	git cat-file tree refs/mytrees/first >expected &&
+	echo >>expected &&
+	${git_for_each_ref} --format="%(raw)" refs/mytrees/first >actual &&
+	test_cmp expected actual &&
+	git cat-file -s refs/mytrees/first >expected &&
+	${git_for_each_ref} --format="%(raw:size)" refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_atom refs/myblobs/first subject ""
+test_atom refs/myblobs/first contents:subject ""
+test_atom refs/myblobs/first body ""
+test_atom refs/myblobs/first contents:body ""
+test_atom refs/myblobs/first contents:signature ""
+test_atom refs/myblobs/first contents ""
+
+test_expect_success 'basic atom: refs/myblobs/first raw' '
+	git cat-file blob refs/myblobs/first >expected &&
+	echo >>expected &&
+	${git_for_each_ref} --format="%(raw)" refs/myblobs/first >actual &&
+	test_cmp expected actual &&
+	git cat-file -s refs/myblobs/first >expected &&
+	${git_for_each_ref} --format="%(raw:size)" refs/myblobs/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'set up refs pointing to binary blob' '
+	printf "a\0b\0c" >blob1 &&
+	printf "a\0c\0b" >blob2 &&
+	printf "\0a\0b\0c" >blob3 &&
+	printf "abc" >blob4 &&
+	printf "\0 \0 \0 " >blob5 &&
+	printf "\0 \0a\0 " >blob6 &&
+	printf "  " >blob7 &&
+	>blob8 &&
+	obj=$(git hash-object -w blob1) &&
+	git update-ref refs/myblobs/blob1 "$obj" &&
+	obj=$(git hash-object -w blob2) &&
+	git update-ref refs/myblobs/blob2 "$obj" &&
+	obj=$(git hash-object -w blob3) &&
+	git update-ref refs/myblobs/blob3 "$obj" &&
+	obj=$(git hash-object -w blob4) &&
+	git update-ref refs/myblobs/blob4 "$obj" &&
+	obj=$(git hash-object -w blob5) &&
+	git update-ref refs/myblobs/blob5 "$obj" &&
+	obj=$(git hash-object -w blob6) &&
+	git update-ref refs/myblobs/blob6 "$obj" &&
+	obj=$(git hash-object -w blob7) &&
+	git update-ref refs/myblobs/blob7 "$obj" &&
+	obj=$(git hash-object -w blob8) &&
+	git update-ref refs/myblobs/blob8 "$obj"
+'
+
+test_expect_success 'Verify sorts with raw' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob8
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/blob3
+	refs/myblobs/blob7
+	refs/mytrees/first
+	refs/myblobs/first
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob4
+	refs/heads/main
+	EOF
+	${git_for_each_ref} --format="%(refname)" --sort=raw \
+		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'Verify sorts with raw:size' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob8
+	refs/myblobs/blob7
+	refs/myblobs/blob4
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob3
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/first
+	refs/mytrees/first
+	refs/heads/main
+	EOF
+	${git_for_each_ref} --format="%(refname)" --sort=raw:size \
+		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:equals)' '
+	cat >expected <<-EOF &&
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	refs/myblobs/blob4
+	not equals
+	not equals
+	not equals
+	not equals
+	not equals
+	EOF
+	${git_for_each_ref} --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
+		refs/myblobs/ refs/heads/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'validate raw atom with %(if:notequals)' '
+	cat >expected <<-EOF &&
+	refs/heads/ambiguous
+	refs/heads/main
+	refs/heads/newtag
+	refs/myblobs/blob1
+	refs/myblobs/blob2
+	refs/myblobs/blob3
+	equals
+	refs/myblobs/blob5
+	refs/myblobs/blob6
+	refs/myblobs/blob7
+	refs/myblobs/blob8
+	refs/myblobs/first
+	EOF
+	${git_for_each_ref} --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
+		refs/myblobs/ refs/heads/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'empty raw refs with %(if)' '
+	cat >expected <<-EOF &&
+	refs/myblobs/blob1 not empty
+	refs/myblobs/blob2 not empty
+	refs/myblobs/blob3 not empty
+	refs/myblobs/blob4 not empty
+	refs/myblobs/blob5 not empty
+	refs/myblobs/blob6 not empty
+	refs/myblobs/blob7 empty
+	refs/myblobs/blob8 empty
+	refs/myblobs/first not empty
+	EOF
+	${git_for_each_ref} --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
+		refs/myblobs/ >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '%(raw) with --python must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --python
+'
+
+test_expect_success '%(raw) with --tcl must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --tcl
+'
+
+test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' '
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
+	cmp blob1 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
+	cmp blob3 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
+	cmp blob8 actual &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
+	cmp one actual &&
+	git cat-file tree refs/mytrees/first > expected &&
+	${git_for_each_ref} --format="\$name= %(raw);
+print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
+	cmp expected actual
+'
+
+test_expect_success '%(raw) with --shell must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --shell
+'
+
+test_expect_success '%(raw) with --shell and --sort=raw must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(raw)" --sort=raw --shell
+'
+
+test_expect_success '%(raw:size) with --shell' '
+	${git_for_each_ref} --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
+	${git_for_each_ref} --format="%(raw:size)" --shell >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --format compare with cat-file --batch" '
+	git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
+	${git_for_each_ref} --format="%(objectname) %(objecttype) %(objectsize)
+%(raw)" refs/mytrees/first >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'verify sorts with contents:size' '
+	cat >expect <<-\EOF &&
+	refs/heads/main
+	refs/heads/newtag
+	refs/heads/ambiguous
+	EOF
+	${git_for_each_ref} --format="%(refname)" \
+		--sort=contents:size refs/heads/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'set up multiple-sort tags' '
+	for when in 100000 200000
+	do
+		for email in user1 user2
+		do
+			for ref in ref1 ref2
+			do
+				GIT_COMMITTER_DATE="@$when +0000" \
+				GIT_COMMITTER_EMAIL="$email@example.com" \
+				git tag -m "tag $ref-$when-$email" \
+				multi-$ref-$when-$email || return 1
+			done
+		done
+	done
+'
+
+test_expect_success 'Verify sort with multiple keys' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=-refname \
+		--sort=taggeremail \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'equivalent sorts fall back on refname' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '--no-sort cancels the previous sort keys' '
+	cat >expected <<-\EOF &&
+	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
+	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
+	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
+	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
+	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
+	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
+	EOF
+	${git_for_each_ref} \
+		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
+		--sort=-refname \
+		--sort=taggeremail \
+		--no-sort \
+		--sort=taggerdate \
+		"refs/tags/multi-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success '--no-sort without subsequent --sort prints expected refs' '
+	cat >expected <<-\EOF &&
+	refs/tags/multi-ref1-100000-user1
+	refs/tags/multi-ref1-100000-user2
+	refs/tags/multi-ref1-200000-user1
+	refs/tags/multi-ref1-200000-user2
+	refs/tags/multi-ref2-100000-user1
+	refs/tags/multi-ref2-100000-user2
+	refs/tags/multi-ref2-200000-user1
+	refs/tags/multi-ref2-200000-user2
+	EOF
+
+	# Sort the results with `sort` for a consistent comparison against
+	# expected
+	${git_for_each_ref} \
+		--format="%(refname)" \
+		--no-sort \
+		"refs/tags/multi-*" | sort >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'set up custom date sorting' '
+	# Dates:
+	# - Wed Feb 07 2024 21:34:20 +0000
+	# - Tue Dec 14 1999 00:05:22 +0000
+	# - Fri Jun 04 2021 11:26:51 +0000
+	# - Mon Jan 22 2007 16:44:01 GMT+0000
+	i=1 &&
+	for when in 1707341660 945129922 1622806011 1169484241
+	do
+		GIT_COMMITTER_DATE="@$when +0000" \
+		GIT_COMMITTER_EMAIL="user@example.com" \
+		git tag -m "tag $when" custom-dates-$i &&
+		i=$(($i+1)) || return 1
+	done
+'
+
+test_expect_success 'sort by date defaults to full timestamp' '
+	cat >expected <<-\EOF &&
+	945129922 refs/tags/custom-dates-2
+	1169484241 refs/tags/custom-dates-4
+	1622806011 refs/tags/custom-dates-3
+	1707341660 refs/tags/custom-dates-1
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(creatordate:unix) %(refname)" \
+		--sort=creatordate \
+		"refs/tags/custom-dates-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'sort by custom date format' '
+	cat >expected <<-\EOF &&
+	00:05:22 refs/tags/custom-dates-2
+	11:26:51 refs/tags/custom-dates-3
+	16:44:01 refs/tags/custom-dates-4
+	21:34:20 refs/tags/custom-dates-1
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(creatordate:format:%H:%M:%S) %(refname)" \
+		--sort="creatordate:format:%H:%M:%S" \
+		"refs/tags/custom-dates-*" >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
+	test_when_finished "git checkout main" &&
+	${git_for_each_ref} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	sed -e "s/^\* /  /" actual >expect &&
+	git checkout --orphan orphaned-branch &&
+	${git_for_each_ref} --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
+	test_cmp expect actual
+'
+
+cat >trailers <<EOF
+Reviewed-by: A U Thor <author@example.com>
+Signed-off-by: A U Thor <author@example.com>
+[ v2 updated patch description ]
+Acked-by: A U Thor
+  <author@example.com>
+EOF
+
+unfold () {
+	perl -0pe 's/\n\s+/ /g'
+}
+
+test_expect_success 'set up trailers for next test' '
+	echo "Some contents" > two &&
+	git add two &&
+	git commit -F - <<-EOF
+	trailers: this commit message has trailers
+
+	Some message contents
+
+	$(cat trailers)
+	EOF
+'
+
+test_trailer_option () {
+	if test "$#" -eq 3
+	then
+		prereq="$1"
+		shift
+	fi &&
+	title=$1 option=$2
+	cat >expect
+	test_expect_success $prereq "$title" '
+		${git_for_each_ref} --format="%($option)" refs/heads/main >actual &&
+		test_cmp expect actual &&
+		${git_for_each_ref} --format="%(contents:$option)" refs/heads/main >actual &&
+		test_cmp expect actual
+	'
+}
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' \
+	'trailers:unfold' <<-EOF
+	$(unfold <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only) shows only "key: value" trailers' \
+	'trailers:only' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=no,only=true) shows only "key: value" trailers' \
+	'trailers:only=no,only=true' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=yes) shows only "key: value" trailers' \
+	'trailers:only=yes' <<-EOF
+	$(grep -v patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:only=no) shows all trailers' \
+	'trailers:only=no' <<-EOF
+	$(cat trailers)
+
+	EOF
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:only) and %(trailers:unfold) work together' \
+	'trailers:only,unfold' <<-EOF
+	$(grep -v patch.description <trailers | unfold)
+
+	EOF
+
+test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) and %(trailers:only) work together' \
+	'trailers:unfold,only' <<-EOF
+	$(grep -v patch.description <trailers | unfold)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) shows that trailer' \
+	'trailers:key=Signed-off-by' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) is case insensitive' \
+	'trailers:key=SiGned-oFf-bY' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo:) trailing colon also works' \
+	'trailers:key=Signed-off-by:' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) multiple keys' \
+	'trailers:key=Reviewed-by:,key=Signed-off-by' <<-EOF
+	Reviewed-by: A U Thor <author@example.com>
+	Signed-off-by: A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:key=nonexistent) becomes empty' \
+	'trailers:key=Shined-off-by:' <<-EOF
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo) handles multiple lines even if folded' \
+	'trailers:key=Acked-by' <<-EOF
+	$(grep -v patch.description <trailers | grep -v Signed-off-by | grep -v Reviewed-by)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,unfold) properly unfolds' \
+	'trailers:key=Signed-Off-by,unfold' <<-EOF
+	$(unfold <trailers | grep Signed-off-by)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,only=no) also includes nontrailer lines' \
+	'trailers:key=Signed-off-by,only=no' <<-EOF
+	Signed-off-by: A U Thor <author@example.com>
+	$(grep patch.description <trailers)
+
+	EOF
+
+test_trailer_option '%(trailers:key=foo,valueonly) shows only value' \
+	'trailers:key=Signed-off-by,valueonly' <<-EOF
+	A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:separator) changes separator' \
+	'trailers:separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by: A U Thor <author@example.com>,Signed-off-by: A U Thor <author@example.com>
+	EOF
+
+test_trailer_option '%(trailers:key_value_separator) changes key-value separator' \
+	'trailers:key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by,A U Thor <author@example.com>
+	Signed-off-by,A U Thor <author@example.com>
+
+	EOF
+
+test_trailer_option '%(trailers:separator,key_value_separator) changes both separators' \
+	'trailers:separator=%x2C,key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
+	Reviewed-by,A U Thor <author@example.com>,Signed-off-by,A U Thor <author@example.com>
+	EOF
+
+test_expect_success 'multiple %(trailers) use their own options' '
+	git tag -F - tag-with-trailers <<-\EOF &&
+	body
+
+	one: foo
+	one: bar
+	two: baz
+	two: qux
+	EOF
+	t1="%(trailers:key=one,key_value_separator=W,separator=X)" &&
+	t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" &&
+	${git_for_each_ref} --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
+	cat >expect <<-\EOF &&
+	oneWfooXoneWbar
+	twoYbazZtwoYqux
+	EOF
+	test_cmp expect actual
+'
+
+test_failing_trailer_option () {
+	title=$1 option=$2
+	cat >expect
+	test_expect_success "$title" '
+		# error message cannot be checked under i18n
+		test_must_fail ${git_for_each_ref} --format="%($option)" refs/heads/main 2>actual &&
+		test_cmp expect actual &&
+		test_must_fail ${git_for_each_ref} --format="%(contents:$option)" refs/heads/main 2>actual &&
+		test_cmp expect actual
+	'
+}
+
+test_failing_trailer_option '%(trailers) rejects unknown trailers arguments' \
+	'trailers:unsupported' <<-\EOF
+	fatal: unknown %(trailers) argument: unsupported
+	EOF
+
+test_failing_trailer_option '%(trailers:key) without value is error' \
+	'trailers:key' <<-\EOF
+	fatal: expected %(trailers:key=<value>)
+	EOF
+
+test_expect_success 'if arguments, %(contents:trailers) shows error if colon is missing' '
+	cat >expect <<-EOF &&
+	fatal: unrecognized %(contents) argument: trailersonly
+	EOF
+	test_must_fail ${git_for_each_ref} --format="%(contents:trailersonly)" 2>actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'basic atom: head contents:trailers' '
+	${git_for_each_ref} --format="%(contents:trailers)" refs/heads/main >actual &&
+	sanitize_pgp <actual >actual.clean &&
+	# ${git_for_each_ref} ends with a blank line
+	cat >expect <<-EOF &&
+	$(cat trailers)
+
+	EOF
+	test_cmp expect actual.clean
+'
+
+test_expect_success 'basic atom: rest must fail' '
+	test_must_fail ${git_for_each_ref} --format="%(rest)" refs/heads/main
+'
+
+test_expect_success 'HEAD atom does not take arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(HEAD:foo)" 2>err &&
+	echo "fatal: %(HEAD) does not take arguments" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'subject atom rejects unknown arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(subject:foo)" 2>err &&
+	echo "fatal: unrecognized %(subject) argument: foo" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'refname atom rejects unknown arguments' '
+	test_must_fail ${git_for_each_ref} --format="%(refname:foo)" 2>err &&
+	echo "fatal: unrecognized %(refname) argument: foo" >expect &&
+	test_cmp expect err
+'
+
+test_expect_success 'trailer parsing not fooled by --- line' '
+	git commit --allow-empty -F - <<-\EOF &&
+	this is the subject
+
+	This is the body. The message has a "---" line which would confuse a
+	message+patch parser. But here we know we have only a commit message,
+	so we get it right.
+
+	trailer: wrong
+	---
+	This is more body.
+
+	trailer: right
+	EOF
+
+	{
+		echo "trailer: right" &&
+		echo
+	} >expect &&
+	${git_for_each_ref} --format="%(trailers)" refs/heads/main >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'Add symbolic ref for the following tests' '
+	git symbolic-ref refs/heads/sym refs/heads/main
+'
+
+cat >expected <<EOF
+refs/heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref) atom' '
+	${git_for_each_ref} --format="%(symref)" refs/heads/sym >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref:short) atom' '
+	${git_for_each_ref} --format="%(symref:short)" refs/heads/sym >actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+main
+heads/main
+EOF
+
+test_expect_success 'Verify usage of %(symref:lstrip) atom' '
+	${git_for_each_ref} --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual &&
+
+	${git_for_each_ref} --format="%(symref:strip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual
+'
+
+cat >expected <<EOF
+refs
+refs/heads
+EOF
+
+test_expect_success 'Verify usage of %(symref:rstrip) atom' '
+	${git_for_each_ref} --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
+	${git_for_each_ref} --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
+	test_cmp expected actual
+'
+
+test_expect_success ':remotename and :remoteref' '
+	git init remote-tests &&
+	(
+		cd remote-tests &&
+		test_commit initial &&
+		git branch -M main &&
+		git remote add from fifth.coffee:blub &&
+		git config branch.main.remote from &&
+		git config branch.main.merge refs/heads/stable &&
+		git remote add to southridge.audio:repo &&
+		git config remote.to.push "refs/heads/*:refs/heads/pushed/*" &&
+		git config branch.main.pushRemote to &&
+		for pair in "%(upstream)=refs/remotes/from/stable" \
+			"%(upstream:remotename)=from" \
+			"%(upstream:remoteref)=refs/heads/stable" \
+			"%(push)=refs/remotes/to/pushed/main" \
+			"%(push:remotename)=to" \
+			"%(push:remoteref)=refs/heads/pushed/main"
+		do
+			echo "${pair#*=}" >expect &&
+			${git_for_each_ref} --format="${pair%=*}" \
+				refs/heads/main >actual &&
+			test_cmp expect actual || exit 1
+		done &&
+		git branch push-simple &&
+		git config branch.push-simple.pushRemote from &&
+		actual="$(${git_for_each_ref} \
+			--format="%(push:remotename),%(push:remoteref)" \
+			refs/heads/push-simple)" &&
+		test from, = "$actual"
+	)
+'
+
+test_expect_success "${git_for_each_ref} --ignore-case ignores case" '
+	${git_for_each_ref} --format="%(refname)" refs/heads/MAIN >actual &&
+	test_must_be_empty actual &&
+
+	echo refs/heads/main >expect &&
+	${git_for_each_ref} --format="%(refname)" --ignore-case \
+		refs/heads/MAIN >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --omit-empty works" '
+	${git_for_each_ref} --format="%(refname)" >actual &&
+	test_line_count -gt 1 actual &&
+	${git_for_each_ref} --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
+	echo refs/heads/main >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --ignore-case works on multiple sort keys" '
+	# name refs numerically to avoid case-insensitive filesystem conflicts
+	nr=0 &&
+	for email in a A b B
+	do
+		for subject in a A b B
+		do
+			GIT_COMMITTER_EMAIL="$email@example.com" \
+			git tag -m "tag $subject" icase-$(printf %02d $nr) &&
+			nr=$((nr+1))||
+			return 1
+		done
+	done &&
+	${git_for_each_ref} --ignore-case \
+		--format="%(taggeremail) %(subject) %(refname)" \
+		--sort=refname \
+		--sort=subject \
+		--sort=taggeremail \
+		refs/tags/icase-* >actual &&
+	cat >expect <<-\EOF &&
+	<a@example.com> tag a refs/tags/icase-00
+	<a@example.com> tag A refs/tags/icase-01
+	<A@example.com> tag a refs/tags/icase-04
+	<A@example.com> tag A refs/tags/icase-05
+	<a@example.com> tag b refs/tags/icase-02
+	<a@example.com> tag B refs/tags/icase-03
+	<A@example.com> tag b refs/tags/icase-06
+	<A@example.com> tag B refs/tags/icase-07
+	<b@example.com> tag a refs/tags/icase-08
+	<b@example.com> tag A refs/tags/icase-09
+	<B@example.com> tag a refs/tags/icase-12
+	<B@example.com> tag A refs/tags/icase-13
+	<b@example.com> tag b refs/tags/icase-10
+	<b@example.com> tag B refs/tags/icase-11
+	<B@example.com> tag b refs/tags/icase-14
+	<B@example.com> tag B refs/tags/icase-15
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} reports broken tags" '
+	git tag -m "good tag" broken-tag-good HEAD &&
+	git cat-file tag broken-tag-good >good &&
+	sed s/commit/blob/ <good >bad &&
+	bad=$(git hash-object -w -t tag bad) &&
+	git update-ref refs/tags/broken-tag-bad $bad &&
+	test_must_fail ${git_for_each_ref} --format="%(*objectname)" \
+		refs/tags/broken-tag-*
+'
+
+test_expect_success 'set up tag with signature and no blank lines' '
+	git tag -F - fake-sig-no-blanks <<-\EOF
+	this is the subject
+	-----BEGIN PGP SIGNATURE-----
+	not a real signature, but we just care about the
+	subject/body parsing. It is important here that
+	there are no blank lines in the signature.
+	-----END PGP SIGNATURE-----
+	EOF
+'
+
+test_atom refs/tags/fake-sig-no-blanks contents:subject 'this is the subject'
+test_atom refs/tags/fake-sig-no-blanks contents:body ''
+test_atom refs/tags/fake-sig-no-blanks contents:signature "$sig"
+
+test_expect_success 'set up tag with CRLF signature' '
+	append_cr <<-\EOF |
+	this is the subject
+	-----BEGIN PGP SIGNATURE-----
+
+	not a real signature, but we just care about
+	the subject/body parsing. It is important here
+	that there is a blank line separating this
+	from the signature header.
+	-----END PGP SIGNATURE-----
+	EOF
+	git tag -F - --cleanup=verbatim fake-sig-crlf
+'
+
+test_atom refs/tags/fake-sig-crlf contents:subject 'this is the subject'
+test_atom refs/tags/fake-sig-crlf contents:body ''
+
+# CRLF is retained in the signature, so we have to pass our expected value
+# through append_cr. But test_atom requires a shell string, which means command
+# substitution, and the shell will strip trailing newlines from the output of
+# the substitution. Hack around it by adding and then removing a dummy line.
+sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
+sig_crlf=${sig_crlf%dummy}
+test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
+
+test_expect_success 'set up tag with signature and trailers' '
+	git tag -F - fake-sig-trailer <<-\EOF
+	this is the subject
+
+	this is the body
+
+	My-Trailer: foo
+	-----BEGIN PGP SIGNATURE-----
+
+	not a real signature, but we just care about the
+	subject/body/trailer parsing.
+	-----END PGP SIGNATURE-----
+	EOF
+'
+
+# use "separator=" here to suppress the terminating newline
+test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo'
+
+test_expect_success "${git_for_each_ref} --stdin: empty" '
+	>in &&
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	${git_for_each_ref} --format="%(refname)" >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} --stdin: fails if extra args" '
+	>in &&
+	test_must_fail ${git_for_each_ref} --format="%(refname)" \
+		--stdin refs/heads/extra <in 2>err &&
+	grep "unknown arguments supplied with --stdin" err
+'
+
+test_expect_success "${git_for_each_ref} --stdin: matches" '
+	cat >in <<-EOF &&
+	refs/tags/multi*
+	refs/heads/amb*
+	EOF
+
+	cat >expect <<-EOF &&
+	refs/heads/ambiguous
+	refs/tags/multi-ref1-100000-user1
+	refs/tags/multi-ref1-100000-user2
+	refs/tags/multi-ref1-200000-user1
+	refs/tags/multi-ref1-200000-user2
+	refs/tags/multi-ref2-100000-user1
+	refs/tags/multi-ref2-100000-user2
+	refs/tags/multi-ref2-200000-user1
+	refs/tags/multi-ref2-200000-user2
+	refs/tags/multiline
+	EOF
+
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success "${git_for_each_ref} with non-existing refs" '
+	cat >in <<-EOF &&
+	refs/heads/this-ref-does-not-exist
+	refs/tags/bogus
+	EOF
+
+	${git_for_each_ref} --format="%(refname)" --stdin <in >actual &&
+	test_must_be_empty actual &&
+
+	xargs ${git_for_each_ref} --format="%(refname)" <in >actual &&
+	test_must_be_empty actual
+'
+
+test_expect_success "${git_for_each_ref} with nested tags" '
+	git tag -am "Normal tag" nested/base HEAD &&
+	git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
+	git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
+
+	head_oid="$(git rev-parse HEAD)" &&
+	base_tag_oid="$(git rev-parse refs/tags/nested/base)" &&
+	nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" &&
+	nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" &&
+
+	cat >expect <<-EOF &&
+	refs/tags/nested/base $base_tag_oid tag $head_oid commit
+	refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit
+	refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
+	EOF
+
+	${git_for_each_ref} \
+		--format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
+		refs/tags/nested/ >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'is-base atom with non-commits' '
+	${git_for_each_ref} --format="%(is-base:HEAD) %(refname)" >out 2>err &&
+	grep "(HEAD) refs/heads/main" out &&
+
+	test_line_count = 2 err &&
+	grep "error: object .* is a commit, not a blob" err &&
+	grep "error: bad tag pointer to" err
+'
+
+GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
+TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
+
+test_expect_success GPG 'setup for signature atom using gpg' '
+	git checkout -b signed &&
+
+	test_when_finished "test_unconfig commit.gpgSign" &&
+
+	echo "1" >file &&
+	git add file &&
+	test_tick &&
+	git commit -S -m "file: 1" &&
+	git tag first-signed &&
+
+	echo "2" >file &&
+	test_tick &&
+	git commit -a -m "file: 2" &&
+	git tag second-unsigned &&
+
+	git config commit.gpgSign 1 &&
+	echo "3" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 3" &&
+	git tag third-unsigned &&
+
+	test_tick &&
+	git rebase -f HEAD^^ && git tag second-signed HEAD^ &&
+	git tag third-signed &&
+
+	echo "4" >file &&
+	test_tick &&
+	git commit -a -SB7227189 -m "file: 4" &&
+	git tag fourth-signed &&
+
+	echo "5" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 5" &&
+	git tag fifth-unsigned &&
+
+	echo "6" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 6" &&
+
+	test_tick &&
+	git rebase -f HEAD^^ &&
+	git tag fifth-signed HEAD^ &&
+	git tag sixth-signed &&
+
+	echo "7" >file &&
+	test_tick &&
+	git commit -a --no-gpg-sign -m "file: 7" &&
+	git tag seventh-unsigned
+'
+
+test_expect_success GPGSSH 'setup for signature atom using ssh' '
+	test_when_finished "test_unconfig gpg.format user.signingkey" &&
+
+	test_config gpg.format ssh &&
+	test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
+	echo "8" >file &&
+	test_tick &&
+	git add file &&
+	git commit -S -m "file: 8" &&
+	git tag eighth-signed-ssh
+'
+
+test_expect_success GPG2 'bare signature atom' '
+	git verify-commit first-signed 2>expect &&
+	echo  >>expect &&
+	${git_for_each_ref} refs/tags/first-signed \
+		--format="%(signature)" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show good signature with custom format' '
+	git verify-commit first-signed &&
+	cat >expect <<-\EOF &&
+	G
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	EOF
+	${git_for_each_ref} refs/tags/first-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+test_expect_success GPGSSH 'show good signature with custom format with ssh' '
+	test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
+	FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
+	cat >expect.tmpl <<-\EOF &&
+	G
+	FINGERPRINT
+	principal with number 1
+	FINGERPRINT
+
+	EOF
+	sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
+	${git_for_each_ref} refs/tags/eighth-signed-ssh \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'signature atom with grade option and bad signature' '
+	git cat-file commit third-signed >raw &&
+	sed -e "s/^file: 3/file: 3 forged/" raw >forged1 &&
+	FORGED1=$(git hash-object -w -t commit forged1) &&
+	git update-ref refs/tags/third-signed "$FORGED1" &&
+	test_must_fail git verify-commit "$FORGED1" &&
+
+	cat >expect <<-\EOF &&
+	B
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+
+
+	EOF
+	${git_for_each_ref} refs/tags/third-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with custom format' '
+	cat >expect <<-\EOF &&
+	U
+	65A0EEA02E30CAD7
+	Eris Discordia <discord@example.net>
+	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+	D4BE22311AD3131E5EDA29A461092E85B7227189
+	EOF
+	${git_for_each_ref} refs/tags/fourth-signed \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with undefined trust level' '
+	cat >expect <<-\EOF &&
+	undefined
+	65A0EEA02E30CAD7
+	Eris Discordia <discord@example.net>
+	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
+	D4BE22311AD3131E5EDA29A461092E85B7227189
+	EOF
+	${git_for_each_ref} refs/tags/fourth-signed \
+		--format="$TRUSTLEVEL_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show untrusted signature with ultimate trust level' '
+	cat >expect <<-\EOF &&
+	ultimate
+	13B6F51ECDDE430D
+	C O Mitter <committer@example.com>
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	73D758744BE721698EC54E8713B6F51ECDDE430D
+	EOF
+	${git_for_each_ref} refs/tags/sixth-signed \
+		--format="$TRUSTLEVEL_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show unknown signature with custom format' '
+	cat >expect <<-\EOF &&
+	E
+	13B6F51ECDDE430D
+
+
+
+	EOF
+	GNUPGHOME="$GNUPGHOME_NOT_USED" ${git_for_each_ref} \
+		refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success GPG 'show lack of signature with custom format' '
+	cat >expect <<-\EOF &&
+	N
+
+
+
+
+	EOF
+	${git_for_each_ref} refs/tags/seventh-unsigned \
+		--format="$GRADE_FORMAT" >actual &&
+	test_cmp expect actual
+'
+
+test_done
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index ce9af79ab1..1d9809114d 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -6,2150 +6,14 @@
 test_description='for-each-ref test'
 
 . ./test-lib.sh
-GNUPGHOME_NOT_USED=$GNUPGHOME
-. "$TEST_DIRECTORY"/lib-gpg.sh
-. "$TEST_DIRECTORY"/lib-terminal.sh
 
-# Mon Jul 3 23:18:43 2006 +0000
-datestamp=1151968723
-setdate_and_increment () {
-    GIT_COMMITTER_DATE="$datestamp +0200"
-    datestamp=$(expr "$datestamp" + 1)
-    GIT_AUTHOR_DATE="$datestamp +0200"
-    datestamp=$(expr "$datestamp" + 1)
-    export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
-}
-
-test_object_file_size () {
-	oid=$(git rev-parse "$1")
-	path=".git/objects/$(test_oid_to_path $oid)"
-	test_file_size "$path"
-}
-
-test_expect_success setup '
-	# setup .mailmap
-	cat >.mailmap <<-EOF &&
-	A Thor <athor@example.com> A U Thor <author@example.com>
-	C Mitter <cmitter@example.com> C O Mitter <committer@example.com>
-	EOF
-
-	setdate_and_increment &&
-	echo "Using $datestamp" > one &&
-	git add one &&
-	git commit -m "Initial" &&
-	git branch -M main &&
-	setdate_and_increment &&
-	git tag -a -m "Tagging at $datestamp" testtag &&
-	git update-ref refs/remotes/origin/main main &&
-	git remote add origin nowhere &&
-	git config branch.main.remote origin &&
-	git config branch.main.merge refs/heads/main &&
-	git remote add myfork elsewhere &&
-	git config remote.pushdefault myfork &&
-	git config push.default current
-'
-
-test_atom () {
-	case "$1" in
-		head) ref=refs/heads/main ;;
-		 tag) ref=refs/tags/testtag ;;
-		 sym) ref=refs/heads/sym ;;
-		   *) ref=$1 ;;
-	esac
-	format=$2
-	test_do=test_expect_${4:-success}
-
-	printf '%s\n' "$3" >expected
-	$test_do $PREREQ "basic atom: $ref $format" '
-		git for-each-ref --format="%($format)" "$ref" >actual &&
-		sanitize_pgp <actual >actual.clean &&
-		test_cmp expected actual.clean
-	'
-
-	# Automatically test "contents:size" atom after testing "contents"
-	if test "$format" = "contents"
-	then
-		# for commit leg, $3 is changed there
-		expect=$(printf '%s' "$3" | wc -c)
-		$test_do $PREREQ "basic atom: $ref contents:size" '
-			type=$(git cat-file -t "$ref") &&
-			case $type in
-			tag)
-				# We cannot use $3 as it expects sanitize_pgp to run
-				git cat-file tag $ref >out &&
-				expect=$(tail -n +6 out | wc -c) &&
-				rm -f out ;;
-			tree | blob)
-				expect="" ;;
-			commit)
-				: "use the calculated expect" ;;
-			*)
-				BUG "unknown object type" ;;
-			esac &&
-			# Leave $expect unquoted to lose possible leading whitespaces
-			echo $expect >expected &&
-			git for-each-ref --format="%(contents:size)" "$ref" >actual &&
-			test_cmp expected actual
-		'
-	fi
-}
-
-hexlen=$(test_oid hexsz)
-
-test_atom head refname refs/heads/main
-test_atom head refname: refs/heads/main
-test_atom head refname:short main
-test_atom head refname:lstrip=1 heads/main
-test_atom head refname:lstrip=2 main
-test_atom head refname:lstrip=-1 main
-test_atom head refname:lstrip=-2 heads/main
-test_atom head refname:rstrip=1 refs/heads
-test_atom head refname:rstrip=2 refs
-test_atom head refname:rstrip=-1 refs
-test_atom head refname:rstrip=-2 refs/heads
-test_atom head refname:strip=1 heads/main
-test_atom head refname:strip=2 main
-test_atom head refname:strip=-1 main
-test_atom head refname:strip=-2 heads/main
-test_atom head upstream refs/remotes/origin/main
-test_atom head upstream:short origin/main
-test_atom head upstream:lstrip=2 origin/main
-test_atom head upstream:lstrip=-2 origin/main
-test_atom head upstream:rstrip=2 refs/remotes
-test_atom head upstream:rstrip=-2 refs/remotes
-test_atom head upstream:strip=2 origin/main
-test_atom head upstream:strip=-2 origin/main
-test_atom head push refs/remotes/myfork/main
-test_atom head push:short myfork/main
-test_atom head push:lstrip=1 remotes/myfork/main
-test_atom head push:lstrip=-1 main
-test_atom head push:rstrip=1 refs/remotes/myfork
-test_atom head push:rstrip=-1 refs
-test_atom head push:strip=1 remotes/myfork/main
-test_atom head push:strip=-1 main
-test_atom head objecttype commit
-test_atom head objectsize $((131 + hexlen))
-test_atom head objectsize:disk $(test_object_file_size refs/heads/main)
-test_atom head deltabase $ZERO_OID
-test_atom head objectname $(git rev-parse refs/heads/main)
-test_atom head objectname:short $(git rev-parse --short refs/heads/main)
-test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
-test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
-test_atom head tree $(git rev-parse refs/heads/main^{tree})
-test_atom head tree:short $(git rev-parse --short refs/heads/main^{tree})
-test_atom head tree:short=1 $(git rev-parse --short=1 refs/heads/main^{tree})
-test_atom head tree:short=10 $(git rev-parse --short=10 refs/heads/main^{tree})
-test_atom head parent ''
-test_atom head parent:short ''
-test_atom head parent:short=1 ''
-test_atom head parent:short=10 ''
-test_atom head numparent 0
-test_atom head object ''
-test_atom head type ''
-test_atom head raw "$(git cat-file commit refs/heads/main)
-"
-test_atom head '*objectname' ''
-test_atom head '*objecttype' ''
-test_atom head author 'A U Thor <author@example.com> 1151968724 +0200'
-test_atom head authorname 'A U Thor'
-test_atom head authorname:mailmap 'A Thor'
-test_atom head authoremail '<author@example.com>'
-test_atom head authoremail:trim 'author@example.com'
-test_atom head authoremail:localpart 'author'
-test_atom head authoremail:trim,localpart 'author'
-test_atom head authoremail:mailmap '<athor@example.com>'
-test_atom head authoremail:mailmap,trim 'athor@example.com'
-test_atom head authoremail:trim,mailmap 'athor@example.com'
-test_atom head authoremail:mailmap,localpart 'athor'
-test_atom head authoremail:localpart,mailmap 'athor'
-test_atom head authoremail:mailmap,trim,localpart,mailmap,trim 'athor'
-test_atom head authordate 'Tue Jul 4 01:18:44 2006 +0200'
-test_atom head committer 'C O Mitter <committer@example.com> 1151968723 +0200'
-test_atom head committername 'C O Mitter'
-test_atom head committername:mailmap 'C Mitter'
-test_atom head committeremail '<committer@example.com>'
-test_atom head committeremail:trim 'committer@example.com'
-test_atom head committeremail:localpart 'committer'
-test_atom head committeremail:localpart,trim 'committer'
-test_atom head committeremail:mailmap '<cmitter@example.com>'
-test_atom head committeremail:mailmap,trim 'cmitter@example.com'
-test_atom head committeremail:trim,mailmap 'cmitter@example.com'
-test_atom head committeremail:mailmap,localpart 'cmitter'
-test_atom head committeremail:localpart,mailmap 'cmitter'
-test_atom head committeremail:trim,mailmap,trim,trim,localpart 'cmitter'
-test_atom head committerdate 'Tue Jul 4 01:18:43 2006 +0200'
-test_atom head tag ''
-test_atom head tagger ''
-test_atom head taggername ''
-test_atom head taggeremail ''
-test_atom head taggeremail:trim ''
-test_atom head taggeremail:localpart ''
-test_atom head taggerdate ''
-test_atom head creator 'C O Mitter <committer@example.com> 1151968723 +0200'
-test_atom head creatordate 'Tue Jul 4 01:18:43 2006 +0200'
-test_atom head subject 'Initial'
-test_atom head subject:sanitize 'Initial'
-test_atom head contents:subject 'Initial'
-test_atom head body ''
-test_atom head contents:body ''
-test_atom head contents:signature ''
-test_atom head contents 'Initial
-'
-test_atom head HEAD '*'
-
-test_atom tag refname refs/tags/testtag
-test_atom tag refname:short testtag
-test_atom tag upstream ''
-test_atom tag push ''
-test_atom tag objecttype tag
-test_atom tag objectsize $((114 + hexlen))
-test_atom tag objectsize:disk $(test_object_file_size refs/tags/testtag)
-test_atom tag '*objectsize:disk' $(test_object_file_size refs/heads/main)
-test_atom tag deltabase $ZERO_OID
-test_atom tag '*deltabase' $ZERO_OID
-test_atom tag objectname $(git rev-parse refs/tags/testtag)
-test_atom tag objectname:short $(git rev-parse --short refs/tags/testtag)
-test_atom head objectname:short=1 $(git rev-parse --short=1 refs/heads/main)
-test_atom head objectname:short=10 $(git rev-parse --short=10 refs/heads/main)
-test_atom tag tree ''
-test_atom tag tree:short ''
-test_atom tag tree:short=1 ''
-test_atom tag tree:short=10 ''
-test_atom tag parent ''
-test_atom tag parent:short ''
-test_atom tag parent:short=1 ''
-test_atom tag parent:short=10 ''
-test_atom tag numparent ''
-test_atom tag object $(git rev-parse refs/tags/testtag^0)
-test_atom tag type 'commit'
-test_atom tag '*objectname' $(git rev-parse refs/tags/testtag^{})
-test_atom tag '*objecttype' 'commit'
-test_atom tag author ''
-test_atom tag authorname ''
-test_atom tag authorname:mailmap ''
-test_atom tag authoremail ''
-test_atom tag authoremail:trim ''
-test_atom tag authoremail:localpart ''
-test_atom tag authoremail:trim,localpart ''
-test_atom tag authoremail:mailmap ''
-test_atom tag authoremail:mailmap,trim ''
-test_atom tag authoremail:trim,mailmap ''
-test_atom tag authoremail:mailmap,localpart ''
-test_atom tag authoremail:localpart,mailmap ''
-test_atom tag authoremail:mailmap,trim,localpart,mailmap,trim ''
-test_atom tag authordate ''
-test_atom tag committer ''
-test_atom tag committername ''
-test_atom tag committername:mailmap ''
-test_atom tag committeremail ''
-test_atom tag committeremail:trim ''
-test_atom tag committeremail:localpart ''
-test_atom tag committeremail:localpart,trim ''
-test_atom tag committeremail:mailmap ''
-test_atom tag committeremail:mailmap,trim ''
-test_atom tag committeremail:trim,mailmap ''
-test_atom tag committeremail:mailmap,localpart ''
-test_atom tag committeremail:localpart,mailmap ''
-test_atom tag committeremail:trim,mailmap,trim,trim,localpart ''
-test_atom tag committerdate ''
-test_atom tag tag 'testtag'
-test_atom tag tagger 'C O Mitter <committer@example.com> 1151968725 +0200'
-test_atom tag taggername 'C O Mitter'
-test_atom tag taggername:mailmap 'C Mitter'
-test_atom tag taggeremail '<committer@example.com>'
-test_atom tag taggeremail:trim 'committer@example.com'
-test_atom tag taggeremail:localpart 'committer'
-test_atom tag taggeremail:trim,localpart 'committer'
-test_atom tag taggeremail:mailmap '<cmitter@example.com>'
-test_atom tag taggeremail:mailmap,trim 'cmitter@example.com'
-test_atom tag taggeremail:trim,mailmap 'cmitter@example.com'
-test_atom tag taggeremail:mailmap,localpart 'cmitter'
-test_atom tag taggeremail:localpart,mailmap 'cmitter'
-test_atom tag taggeremail:trim,mailmap,trim,localpart,localpart 'cmitter'
-test_atom tag taggerdate 'Tue Jul 4 01:18:45 2006 +0200'
-test_atom tag creator 'C O Mitter <committer@example.com> 1151968725 +0200'
-test_atom tag creatordate 'Tue Jul 4 01:18:45 2006 +0200'
-test_atom tag subject 'Tagging at 1151968727'
-test_atom tag subject:sanitize 'Tagging-at-1151968727'
-test_atom tag contents:subject 'Tagging at 1151968727'
-test_atom tag body ''
-test_atom tag contents:body ''
-test_atom tag contents:signature ''
-test_atom tag contents 'Tagging at 1151968727
-'
-test_atom tag HEAD ' '
-
-test_expect_success 'basic atom: refs/tags/testtag *raw' '
-	git cat-file commit refs/tags/testtag^{} >expected &&
-	git for-each-ref --format="%(*raw)" refs/tags/testtag >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_expect_success 'Check invalid atoms names are errors' '
-	test_must_fail git for-each-ref --format="%(INVALID)" refs/heads
-'
-
-test_expect_success 'for-each-ref does not crash with -h' '
+test_expect_success "for-each-ref does not crash with -h" '
 	test_expect_code 129 git for-each-ref -h >usage &&
 	test_grep "[Uu]sage: git for-each-ref " usage &&
 	test_expect_code 129 nongit git for-each-ref -h >usage &&
 	test_grep "[Uu]sage: git for-each-ref " usage
 '
 
-test_expect_success 'Check format specifiers are ignored in naming date atoms' '
-	git for-each-ref --format="%(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate)" refs/heads &&
-	git for-each-ref --format="%(authordate) %(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:default) %(authordate:default)" refs/heads
-'
-
-test_expect_success 'Check valid format specifiers for date fields' '
-	git for-each-ref --format="%(authordate:default)" refs/heads &&
-	git for-each-ref --format="%(authordate:relative)" refs/heads &&
-	git for-each-ref --format="%(authordate:short)" refs/heads &&
-	git for-each-ref --format="%(authordate:local)" refs/heads &&
-	git for-each-ref --format="%(authordate:iso8601)" refs/heads &&
-	git for-each-ref --format="%(authordate:rfc2822)" refs/heads
-'
-
-test_expect_success 'Check invalid format specifiers are errors' '
-	test_must_fail git for-each-ref --format="%(authordate:INVALID)" refs/heads
-'
-
-test_expect_success 'arguments to %(objectname:short=) must be positive integers' '
-	test_must_fail git for-each-ref --format="%(objectname:short=0)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=-1)" &&
-	test_must_fail git for-each-ref --format="%(objectname:short=foo)"
-'
-
-test_bad_atom () {
-	case "$1" in
-	head) ref=refs/heads/main ;;
-	 tag) ref=refs/tags/testtag ;;
-	 sym) ref=refs/heads/sym ;;
-	   *) ref=$1 ;;
-	esac
-	format=$2
-	test_do=test_expect_${4:-success}
-
-	printf '%s\n' "$3" >expect
-	$test_do $PREREQ "err basic atom: $ref $format" '
-		test_must_fail git for-each-ref \
-			--format="%($format)" "$ref" 2>error &&
-		test_cmp expect error
-	'
-}
-
-test_bad_atom head 'authoremail:foo' \
-	'fatal: unrecognized %(authoremail) argument: foo'
-
-test_bad_atom head 'authoremail:mailmap,trim,bar' \
-	'fatal: unrecognized %(authoremail) argument: bar'
-
-test_bad_atom head 'authoremail:trim,' \
-	'fatal: unrecognized %(authoremail) argument: '
-
-test_bad_atom head 'authoremail:mailmaptrim' \
-	'fatal: unrecognized %(authoremail) argument: trim'
-
-test_bad_atom head 'committeremail: ' \
-	'fatal: unrecognized %(committeremail) argument:  '
-
-test_bad_atom head 'committeremail: trim,foo' \
-	'fatal: unrecognized %(committeremail) argument:  trim,foo'
-
-test_bad_atom head 'committeremail:mailmap,localpart ' \
-	'fatal: unrecognized %(committeremail) argument:  '
-
-test_bad_atom head 'committeremail:trim_localpart' \
-	'fatal: unrecognized %(committeremail) argument: _localpart'
-
-test_bad_atom head 'committeremail:localpart,,,trim' \
-	'fatal: unrecognized %(committeremail) argument: ,,trim'
-
-test_bad_atom tag 'taggeremail:mailmap,trim, foo ' \
-	'fatal: unrecognized %(taggeremail) argument:  foo '
-
-test_bad_atom tag 'taggeremail:trim,localpart,' \
-	'fatal: unrecognized %(taggeremail) argument: '
-
-test_bad_atom tag 'taggeremail:mailmap;localpart trim' \
-	'fatal: unrecognized %(taggeremail) argument: ;localpart trim'
-
-test_bad_atom tag 'taggeremail:localpart trim' \
-	'fatal: unrecognized %(taggeremail) argument:  trim'
-
-test_bad_atom tag 'taggeremail:mailmap,mailmap,trim,qux,localpart,trim' \
-	'fatal: unrecognized %(taggeremail) argument: qux,localpart,trim'
-
-test_date () {
-	f=$1 &&
-	committer_date=$2 &&
-	author_date=$3 &&
-	tagger_date=$4 &&
-	cat >expected <<-EOF &&
-	'refs/heads/main' '$committer_date' '$author_date'
-	'refs/tags/testtag' '$tagger_date'
-	EOF
-	(
-		git for-each-ref --shell \
-			--format="%(refname) %(committerdate${f:+:$f}) %(authordate${f:+:$f})" \
-			refs/heads &&
-		git for-each-ref --shell \
-			--format="%(refname) %(taggerdate${f:+:$f})" \
-			refs/tags
-	) >actual &&
-	test_cmp expected actual
-}
-
-test_expect_success 'Check unformatted date fields output' '
-	test_date "" \
-		"Tue Jul 4 01:18:43 2006 +0200" \
-		"Tue Jul 4 01:18:44 2006 +0200" \
-		"Tue Jul 4 01:18:45 2006 +0200"
-'
-
-test_expect_success 'Check format "default" formatted date fields output' '
-	test_date default \
-		"Tue Jul 4 01:18:43 2006 +0200" \
-		"Tue Jul 4 01:18:44 2006 +0200" \
-		"Tue Jul 4 01:18:45 2006 +0200"
-'
-
-test_expect_success 'Check format "default-local" date fields output' '
-	test_date default-local "Mon Jul 3 23:18:43 2006" "Mon Jul 3 23:18:44 2006" "Mon Jul 3 23:18:45 2006"
-'
-
-# Don't know how to do relative check because I can't know when this script
-# is going to be run and can't fake the current time to git, and hence can't
-# provide expected output.  Instead, I'll just make sure that "relative"
-# doesn't exit in error
-test_expect_success 'Check format "relative" date fields output' '
-	f=relative &&
-	(git for-each-ref --shell --format="%(refname) %(committerdate:$f) %(authordate:$f)" refs/heads &&
-	git for-each-ref --shell --format="%(refname) %(taggerdate:$f)" refs/tags) >actual
-'
-
-# We just check that this is the same as "relative" for now.
-test_expect_success 'Check format "relative-local" date fields output' '
-	test_date relative-local \
-		"$(git for-each-ref --format="%(committerdate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(authordate:relative)" refs/heads)" \
-		"$(git for-each-ref --format="%(taggerdate:relative)" refs/tags)"
-'
-
-test_expect_success 'Check format "short" date fields output' '
-	test_date short 2006-07-04 2006-07-04 2006-07-04
-'
-
-test_expect_success 'Check format "short-local" date fields output' '
-	test_date short-local 2006-07-03 2006-07-03 2006-07-03
-'
-
-test_expect_success 'Check format "local" date fields output' '
-	test_date local \
-		"Mon Jul 3 23:18:43 2006" \
-		"Mon Jul 3 23:18:44 2006" \
-		"Mon Jul 3 23:18:45 2006"
-'
-
-test_expect_success 'Check format "iso8601" date fields output' '
-	test_date iso8601 \
-		"2006-07-04 01:18:43 +0200" \
-		"2006-07-04 01:18:44 +0200" \
-		"2006-07-04 01:18:45 +0200"
-'
-
-test_expect_success 'Check format "iso8601-local" date fields output' '
-	test_date iso8601-local "2006-07-03 23:18:43 +0000" "2006-07-03 23:18:44 +0000" "2006-07-03 23:18:45 +0000"
-'
-
-test_expect_success 'Check format "rfc2822" date fields output' '
-	test_date rfc2822 \
-		"Tue, 4 Jul 2006 01:18:43 +0200" \
-		"Tue, 4 Jul 2006 01:18:44 +0200" \
-		"Tue, 4 Jul 2006 01:18:45 +0200"
-'
-
-test_expect_success 'Check format "rfc2822-local" date fields output' '
-	test_date rfc2822-local "Mon, 3 Jul 2006 23:18:43 +0000" "Mon, 3 Jul 2006 23:18:44 +0000" "Mon, 3 Jul 2006 23:18:45 +0000"
-'
-
-test_expect_success 'Check format "raw" date fields output' '
-	test_date raw "1151968723 +0200" "1151968724 +0200" "1151968725 +0200"
-'
-
-test_expect_success 'Check format "raw-local" date fields output' '
-	test_date raw-local "1151968723 +0000" "1151968724 +0000" "1151968725 +0000"
-'
-
-test_expect_success 'Check format of strftime date fields' '
-	echo "my date is 2006-07-04" >expected &&
-	git for-each-ref \
-	  --format="%(authordate:format:my date is %Y-%m-%d)" \
-	  refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Check format of strftime-local date fields' '
-	echo "my date is 2006-07-03" >expected &&
-	git for-each-ref \
-	  --format="%(authordate:format-local:my date is %Y-%m-%d)" \
-	  refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'exercise strftime with odd fields' '
-	echo >expected &&
-	git for-each-ref --format="%(authordate:format:)" refs/heads >actual &&
-	test_cmp expected actual &&
-	long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" &&
-	echo $long >expected &&
-	git for-each-ref --format="%(authordate:format:$long)" refs/heads >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/heads/main
-refs/remotes/origin/main
-refs/tags/testtag
-EOF
-
-test_expect_success 'Verify ascending sort' '
-	git for-each-ref --format="%(refname)" --sort=refname >actual &&
-	test_cmp expected actual
-'
-
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/remotes/origin/main
-refs/heads/main
-EOF
-
-test_expect_success 'Verify descending sort' '
-	git for-each-ref --format="%(refname)" --sort=-refname >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Give help even with invalid sort atoms' '
-	test_expect_code 129 git for-each-ref --sort=bogus -h >actual 2>&1 &&
-	grep "^usage: git for-each-ref" actual
-'
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/tags/testtag-2
-EOF
-
-test_expect_success 'exercise patterns with prefixes' '
-	git tag testtag-2 &&
-	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/testtag refs/tags/testtag-2 >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/testtag
-refs/tags/testtag-2
-EOF
-
-test_expect_success 'exercise glob patterns with prefixes' '
-	git tag testtag-2 &&
-	test_when_finished "git tag -d testtag-2" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/testtag "refs/tags/testtag-*" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/bar
-refs/tags/baz
-refs/tags/testtag
-EOF
-
-test_expect_success 'exercise patterns with prefix exclusions' '
-	for tag in foo/one foo/two foo/three bar baz
-	do
-		git tag "$tag" || return 1
-	done &&
-	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/ --exclude=refs/tags/foo >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-refs/tags/bar
-refs/tags/baz
-refs/tags/foo/one
-refs/tags/testtag
-EOF
-
-test_expect_success 'exercise patterns with pattern exclusions' '
-	for tag in foo/one foo/two foo/three bar baz
-	do
-		git tag "$tag" || return 1
-	done &&
-	test_when_finished "git tag -d foo/one foo/two foo/three bar baz" &&
-	git for-each-ref --format="%(refname)" \
-		refs/tags/ --exclude="refs/tags/foo/t*" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-'refs/heads/main'
-'refs/remotes/origin/main'
-'refs/tags/testtag'
-EOF
-
-test_expect_success 'Quoting style: shell' '
-	git for-each-ref --shell --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Quoting style: perl' '
-	git for-each-ref --perl --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Quoting style: python' '
-	git for-each-ref --python --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-"refs/heads/main"
-"refs/remotes/origin/main"
-"refs/tags/testtag"
-EOF
-
-test_expect_success 'Quoting style: tcl' '
-	git for-each-ref --tcl --format="%(refname)" >actual &&
-	test_cmp expected actual
-'
-
-for i in "--perl --shell" "-s --python" "--python --tcl" "--tcl --perl"; do
-	test_expect_success "more than one quoting style: $i" "
-		test_must_fail git for-each-ref $i 2>err &&
-		grep '^error: more than one quoting style' err
-	"
-done
-
-test_expect_success 'setup for upstream:track[short]' '
-	test_commit two
-'
-
-test_atom head upstream:track '[ahead 1]'
-test_atom head upstream:trackshort '>'
-test_atom head upstream:track,nobracket 'ahead 1'
-test_atom head upstream:nobracket,track 'ahead 1'
-
-test_expect_success 'setup for push:track[short]' '
-	test_commit third &&
-	git update-ref refs/remotes/myfork/main main &&
-	git reset main~1
-'
-
-test_atom head push:track '[behind 1]'
-test_atom head push:trackshort '<'
-
-test_expect_success 'Check that :track[short] cannot be used with other atoms' '
-	test_must_fail git for-each-ref --format="%(refname:track)" 2>/dev/null &&
-	test_must_fail git for-each-ref --format="%(refname:trackshort)" 2>/dev/null
-'
-
-test_expect_success 'Check that :track[short] works when upstream is invalid' '
-	cat >expected <<-\EOF &&
-	[gone]
-
-	EOF
-	test_when_finished "git config branch.main.merge refs/heads/main" &&
-	git config branch.main.merge refs/heads/does-not-exist &&
-	git for-each-ref \
-		--format="%(upstream:track)$LF%(upstream:trackshort)" \
-		refs/heads >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Check for invalid refname format' '
-	test_must_fail git for-each-ref --format="%(refname:INVALID)"
-'
-
-test_expect_success 'set up color tests' '
-	cat >expected.color <<-EOF &&
-	$(git rev-parse --short refs/heads/main) <GREEN>main<RESET>
-	$(git rev-parse --short refs/remotes/myfork/main) <GREEN>myfork/main<RESET>
-	$(git rev-parse --short refs/remotes/origin/main) <GREEN>origin/main<RESET>
-	$(git rev-parse --short refs/tags/testtag) <GREEN>testtag<RESET>
-	$(git rev-parse --short refs/tags/third) <GREEN>third<RESET>
-	$(git rev-parse --short refs/tags/two) <GREEN>two<RESET>
-	EOF
-	sed "s/<[^>]*>//g" <expected.color >expected.bare &&
-	color_format="%(objectname:short) %(color:green)%(refname:short)"
-'
-
-test_expect_success TTY '%(color) shows color with a tty' '
-	test_terminal git for-each-ref --format="$color_format" >actual.raw &&
-	test_decode_color <actual.raw >actual &&
-	test_cmp expected.color actual
-'
-
-test_expect_success '%(color) does not show color without tty' '
-	TERM=vt100 git for-each-ref --format="$color_format" >actual &&
-	test_cmp expected.bare actual
-'
-
-test_expect_success '--color can override tty check' '
-	git for-each-ref --color --format="$color_format" >actual.raw &&
-	test_decode_color <actual.raw >actual &&
-	test_cmp expected.color actual
-'
-
-test_expect_success 'color.ui=always does not override tty check' '
-	git -c color.ui=always for-each-ref --format="$color_format" >actual &&
-	test_cmp expected.bare actual
-'
-
-test_expect_success 'setup for describe atom tests' '
-	git init -b master describe-repo &&
-	(
-		cd describe-repo &&
-
-		test_commit --no-tag one &&
-		git tag tagone &&
-
-		test_commit --no-tag two &&
-		git tag -a -m "tag two" tagtwo
-	)
-'
-
-test_expect_success 'describe atom vs git describe' '
-	(
-		cd describe-repo &&
-
-		git for-each-ref --format="%(objectname)" \
-			refs/tags/ >obj &&
-		while read hash
-		do
-			if desc=$(git describe $hash)
-			then
-				: >expect-contains-good
-			else
-				: >expect-contains-bad
-			fi &&
-			echo "$hash $desc" || return 1
-		done <obj >expect &&
-		test_path_exists expect-contains-good &&
-		test_path_exists expect-contains-bad &&
-
-		git for-each-ref --format="%(objectname) %(describe)" \
-			refs/tags/ >actual 2>err &&
-		test_cmp expect actual &&
-		test_must_be_empty err
-	)
-'
-
-test_expect_success 'describe:tags vs describe --tags' '
-	(
-		cd describe-repo &&
-		git describe --tags >expect &&
-		git for-each-ref --format="%(describe:tags)" \
-				refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'describe:abbrev=... vs describe --abbrev=...' '
-	(
-		cd describe-repo &&
-
-		# Case 1: We have commits between HEAD and the most
-		#	  recent tag reachable from it
-		test_commit --no-tag file &&
-		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
-			refs/heads/master >actual &&
-		test_cmp expect actual &&
-
-		# Make sure the hash used is at least 14 digits long
-		sed -e "s/^.*-g\([0-9a-f]*\)$/\1/" <actual >hexpart &&
-		test 15 -le $(wc -c <hexpart) &&
-
-		# Case 2: We have a tag at HEAD, describe directly gives
-		#	  the name of the tag
-		git tag -a -m tagged tagname &&
-		git describe --abbrev=14 >expect &&
-		git for-each-ref --format="%(describe:abbrev=14)" \
-			refs/heads/master >actual &&
-		test_cmp expect actual &&
-		test tagname = $(cat actual)
-	)
-'
-
-test_expect_success 'describe:match=... vs describe --match ...' '
-	(
-		cd describe-repo &&
-		git tag -a -m "tag foo" tag-foo &&
-		git describe --match "*-foo" >expect &&
-		git for-each-ref --format="%(describe:match="*-foo")" \
-			refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'describe:exclude:... vs describe --exclude ...' '
-	(
-		cd describe-repo &&
-		git tag -a -m "tag bar" tag-bar &&
-		git describe --exclude "*-bar" >expect &&
-		git for-each-ref --format="%(describe:exclude="*-bar")" \
-			refs/heads/master >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'deref with describe atom' '
-	(
-		cd describe-repo &&
-		cat >expect <<-\EOF &&
-
-		tagname
-		tagname
-		tagname
-
-		tagtwo
-		EOF
-		git for-each-ref --format="%(*describe)" >actual &&
-		test_cmp expect actual
-	)
-'
-
-test_expect_success 'err on bad describe atom arg' '
-	(
-		cd describe-repo &&
-
-		# The bad arg is the only arg passed to describe atom
-		cat >expect <<-\EOF &&
-		fatal: unrecognized %(describe) argument: baz
-		EOF
-		test_must_fail git for-each-ref --format="%(describe:baz)" \
-			refs/heads/master 2>actual &&
-		test_cmp expect actual &&
-
-		# The bad arg is in the middle of the option string
-		# passed to the describe atom
-		cat >expect <<-\EOF &&
-		fatal: unrecognized %(describe) argument: qux=1,abbrev=14
-		EOF
-		test_must_fail git for-each-ref \
-			--format="%(describe:tags,qux=1,abbrev=14)" \
-			ref/heads/master 2>actual &&
-		test_cmp expect actual
-	)
-'
-
-cat >expected <<\EOF
-heads/main
-tags/main
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs (strict)' '
-	git config --bool core.warnambiguousrefs true &&
-	git checkout -b newtag &&
-	echo "Using $datestamp" > one &&
-	git add one &&
-	git commit -m "Branch" &&
-	setdate_and_increment &&
-	git tag -m "Tagging at $datestamp" main &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-heads/main
-main
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs (loose)' '
-	git config --bool core.warnambiguousrefs false &&
-	git for-each-ref --format "%(refname:short)" refs/heads/main refs/tags/main >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<\EOF
-heads/ambiguous
-ambiguous
-EOF
-
-test_expect_success 'Check ambiguous head and tag refs II (loose)' '
-	git checkout main &&
-	git tag ambiguous testtag^0 &&
-	git branch ambiguous testtag^0 &&
-	git for-each-ref --format "%(refname:short)" refs/heads/ambiguous refs/tags/ambiguous >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'create tag without tagger' '
-	git tag -a -m "Broken tag" taggerless &&
-	git tag -f taggerless $(git cat-file tag taggerless |
-		sed -e "/^tagger /d" |
-		git hash-object --literally --stdin -w -t tag)
-'
-
-test_atom refs/tags/taggerless type 'commit'
-test_atom refs/tags/taggerless tag 'taggerless'
-test_atom refs/tags/taggerless tagger ''
-test_atom refs/tags/taggerless taggername ''
-test_atom refs/tags/taggerless taggeremail ''
-test_atom refs/tags/taggerless taggeremail:trim ''
-test_atom refs/tags/taggerless taggeremail:localpart ''
-test_atom refs/tags/taggerless taggerdate ''
-test_atom refs/tags/taggerless committer ''
-test_atom refs/tags/taggerless committername ''
-test_atom refs/tags/taggerless committeremail ''
-test_atom refs/tags/taggerless committeremail:trim ''
-test_atom refs/tags/taggerless committeremail:localpart ''
-test_atom refs/tags/taggerless committerdate ''
-test_atom refs/tags/taggerless subject 'Broken tag'
-
-test_expect_success 'an unusual tag with an incomplete line' '
-
-	git tag -m "bogo" bogo &&
-	bogo=$(git cat-file tag bogo) &&
-	bogo=$(printf "%s" "$bogo" | git mktag) &&
-	git tag -f bogo "$bogo" &&
-	git for-each-ref --format "%(body)" refs/tags/bogo
-
-'
-
-test_expect_success 'create tag with subject and body content' '
-	cat >>msg <<-\EOF &&
-		the subject line
-
-		first body line
-		second body line
-	EOF
-	git tag -F msg subject-body
-'
-test_atom refs/tags/subject-body subject 'the subject line'
-test_atom refs/tags/subject-body subject:sanitize 'the-subject-line'
-test_atom refs/tags/subject-body body 'first body line
-second body line
-'
-test_atom refs/tags/subject-body contents 'the subject line
-
-first body line
-second body line
-'
-
-test_expect_success 'create tag with multiline subject' '
-	cat >msg <<-\EOF &&
-		first subject line
-		second subject line
-
-		first body line
-		second body line
-	EOF
-	git tag -F msg multiline
-'
-test_atom refs/tags/multiline subject 'first subject line second subject line'
-test_atom refs/tags/multiline subject:sanitize 'first-subject-line-second-subject-line'
-test_atom refs/tags/multiline contents:subject 'first subject line second subject line'
-test_atom refs/tags/multiline body 'first body line
-second body line
-'
-test_atom refs/tags/multiline contents:body 'first body line
-second body line
-'
-test_atom refs/tags/multiline contents:signature ''
-test_atom refs/tags/multiline contents 'first subject line
-second subject line
-
-first body line
-second body line
-'
-
-test_expect_success GPG 'create signed tags' '
-	git tag -s -m "" signed-empty &&
-	git tag -s -m "subject line" signed-short &&
-	cat >msg <<-\EOF &&
-	subject line
-
-	body contents
-	EOF
-	git tag -s -F msg signed-long
-'
-
-sig='-----BEGIN PGP SIGNATURE-----
------END PGP SIGNATURE-----
-'
-
-PREREQ=GPG
-test_atom refs/tags/signed-empty subject ''
-test_atom refs/tags/signed-empty subject:sanitize ''
-test_atom refs/tags/signed-empty contents:subject ''
-test_atom refs/tags/signed-empty body "$sig"
-test_atom refs/tags/signed-empty contents:body ''
-test_atom refs/tags/signed-empty contents:signature "$sig"
-test_atom refs/tags/signed-empty contents "$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-empty raw' '
-	git cat-file tag refs/tags/signed-empty >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-empty >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_atom refs/tags/signed-short subject 'subject line'
-test_atom refs/tags/signed-short subject:sanitize 'subject-line'
-test_atom refs/tags/signed-short contents:subject 'subject line'
-test_atom refs/tags/signed-short body "$sig"
-test_atom refs/tags/signed-short contents:body ''
-test_atom refs/tags/signed-short contents:signature "$sig"
-test_atom refs/tags/signed-short contents "subject line
-$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-short raw' '
-	git cat-file tag refs/tags/signed-short >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-short >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_atom refs/tags/signed-long subject 'subject line'
-test_atom refs/tags/signed-long subject:sanitize 'subject-line'
-test_atom refs/tags/signed-long contents:subject 'subject line'
-test_atom refs/tags/signed-long body "body contents
-$sig"
-test_atom refs/tags/signed-long contents:body 'body contents
-'
-test_atom refs/tags/signed-long contents:signature "$sig"
-test_atom refs/tags/signed-long contents "subject line
-
-body contents
-$sig"
-
-test_expect_success GPG 'basic atom: refs/tags/signed-long raw' '
-	git cat-file tag refs/tags/signed-long >expected &&
-	git for-each-ref --format="%(raw)" refs/tags/signed-long >actual &&
-	sanitize_pgp <expected >expected.clean &&
-	echo >>expected.clean &&
-	sanitize_pgp <actual >actual.clean &&
-	test_cmp expected.clean actual.clean
-'
-
-test_expect_success 'set up refs pointing to tree and blob' '
-	git update-ref refs/mytrees/first refs/heads/main^{tree} &&
-	git update-ref refs/myblobs/first refs/heads/main:one
-'
-
-test_atom refs/mytrees/first subject ""
-test_atom refs/mytrees/first contents:subject ""
-test_atom refs/mytrees/first body ""
-test_atom refs/mytrees/first contents:body ""
-test_atom refs/mytrees/first contents:signature ""
-test_atom refs/mytrees/first contents ""
-
-test_expect_success 'basic atom: refs/mytrees/first raw' '
-	git cat-file tree refs/mytrees/first >expected &&
-	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/mytrees/first >actual &&
-	test_cmp expected actual &&
-	git cat-file -s refs/mytrees/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_atom refs/myblobs/first subject ""
-test_atom refs/myblobs/first contents:subject ""
-test_atom refs/myblobs/first body ""
-test_atom refs/myblobs/first contents:body ""
-test_atom refs/myblobs/first contents:signature ""
-test_atom refs/myblobs/first contents ""
-
-test_expect_success 'basic atom: refs/myblobs/first raw' '
-	git cat-file blob refs/myblobs/first >expected &&
-	echo >>expected &&
-	git for-each-ref --format="%(raw)" refs/myblobs/first >actual &&
-	test_cmp expected actual &&
-	git cat-file -s refs/myblobs/first >expected &&
-	git for-each-ref --format="%(raw:size)" refs/myblobs/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'set up refs pointing to binary blob' '
-	printf "a\0b\0c" >blob1 &&
-	printf "a\0c\0b" >blob2 &&
-	printf "\0a\0b\0c" >blob3 &&
-	printf "abc" >blob4 &&
-	printf "\0 \0 \0 " >blob5 &&
-	printf "\0 \0a\0 " >blob6 &&
-	printf "  " >blob7 &&
-	>blob8 &&
-	obj=$(git hash-object -w blob1) &&
-	git update-ref refs/myblobs/blob1 "$obj" &&
-	obj=$(git hash-object -w blob2) &&
-	git update-ref refs/myblobs/blob2 "$obj" &&
-	obj=$(git hash-object -w blob3) &&
-	git update-ref refs/myblobs/blob3 "$obj" &&
-	obj=$(git hash-object -w blob4) &&
-	git update-ref refs/myblobs/blob4 "$obj" &&
-	obj=$(git hash-object -w blob5) &&
-	git update-ref refs/myblobs/blob5 "$obj" &&
-	obj=$(git hash-object -w blob6) &&
-	git update-ref refs/myblobs/blob6 "$obj" &&
-	obj=$(git hash-object -w blob7) &&
-	git update-ref refs/myblobs/blob7 "$obj" &&
-	obj=$(git hash-object -w blob8) &&
-	git update-ref refs/myblobs/blob8 "$obj"
-'
-
-test_expect_success 'Verify sorts with raw' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob8
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/blob3
-	refs/myblobs/blob7
-	refs/mytrees/first
-	refs/myblobs/first
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob4
-	refs/heads/main
-	EOF
-	git for-each-ref --format="%(refname)" --sort=raw \
-		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'Verify sorts with raw:size' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob8
-	refs/myblobs/blob7
-	refs/myblobs/blob4
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob3
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/first
-	refs/mytrees/first
-	refs/heads/main
-	EOF
-	git for-each-ref --format="%(refname)" --sort=raw:size \
-		refs/heads/main refs/myblobs/ refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'validate raw atom with %(if:equals)' '
-	cat >expected <<-EOF &&
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	refs/myblobs/blob4
-	not equals
-	not equals
-	not equals
-	not equals
-	not equals
-	EOF
-	git for-each-ref --format="%(if:equals=abc)%(raw)%(then)%(refname)%(else)not equals%(end)" \
-		refs/myblobs/ refs/heads/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'validate raw atom with %(if:notequals)' '
-	cat >expected <<-EOF &&
-	refs/heads/ambiguous
-	refs/heads/main
-	refs/heads/newtag
-	refs/myblobs/blob1
-	refs/myblobs/blob2
-	refs/myblobs/blob3
-	equals
-	refs/myblobs/blob5
-	refs/myblobs/blob6
-	refs/myblobs/blob7
-	refs/myblobs/blob8
-	refs/myblobs/first
-	EOF
-	git for-each-ref --format="%(if:notequals=abc)%(raw)%(then)%(refname)%(else)equals%(end)" \
-		refs/myblobs/ refs/heads/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'empty raw refs with %(if)' '
-	cat >expected <<-EOF &&
-	refs/myblobs/blob1 not empty
-	refs/myblobs/blob2 not empty
-	refs/myblobs/blob3 not empty
-	refs/myblobs/blob4 not empty
-	refs/myblobs/blob5 not empty
-	refs/myblobs/blob6 not empty
-	refs/myblobs/blob7 empty
-	refs/myblobs/blob8 empty
-	refs/myblobs/first not empty
-	EOF
-	git for-each-ref --format="%(refname) %(if)%(raw)%(then)not empty%(else)empty%(end)" \
-		refs/myblobs/ >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '%(raw) with --python must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --python
-'
-
-test_expect_success '%(raw) with --tcl must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --tcl
-'
-
-test_expect_success PERL_TEST_HELPERS '%(raw) with --perl' '
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob1 --perl | perl >actual &&
-	cmp blob1 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob3 --perl | perl >actual &&
-	cmp blob3 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/blob8 --perl | perl >actual &&
-	cmp blob8 actual &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/myblobs/first --perl | perl >actual &&
-	cmp one actual &&
-	git cat-file tree refs/mytrees/first > expected &&
-	git for-each-ref --format="\$name= %(raw);
-print \"\$name\"" refs/mytrees/first --perl | perl >actual &&
-	cmp expected actual
-'
-
-test_expect_success '%(raw) with --shell must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --shell
-'
-
-test_expect_success '%(raw) with --shell and --sort=raw must fail' '
-	test_must_fail git for-each-ref --format="%(raw)" --sort=raw --shell
-'
-
-test_expect_success '%(raw:size) with --shell' '
-	git for-each-ref --format="%(raw:size)" | sed "s/^/$SQ/;s/$/$SQ/" >expect &&
-	git for-each-ref --format="%(raw:size)" --shell >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --format compare with cat-file --batch' '
-	git rev-parse refs/mytrees/first | git cat-file --batch >expected &&
-	git for-each-ref --format="%(objectname) %(objecttype) %(objectsize)
-%(raw)" refs/mytrees/first >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'verify sorts with contents:size' '
-	cat >expect <<-\EOF &&
-	refs/heads/main
-	refs/heads/newtag
-	refs/heads/ambiguous
-	EOF
-	git for-each-ref --format="%(refname)" \
-		--sort=contents:size refs/heads/ >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'set up multiple-sort tags' '
-	for when in 100000 200000
-	do
-		for email in user1 user2
-		do
-			for ref in ref1 ref2
-			do
-				GIT_COMMITTER_DATE="@$when +0000" \
-				GIT_COMMITTER_EMAIL="$email@example.com" \
-				git tag -m "tag $ref-$when-$email" \
-				multi-$ref-$when-$email || return 1
-			done
-		done
-	done
-'
-
-test_expect_success 'Verify sort with multiple keys' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=-refname \
-		--sort=taggeremail \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'equivalent sorts fall back on refname' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '--no-sort cancels the previous sort keys' '
-	cat >expected <<-\EOF &&
-	100000 <user1@example.com> refs/tags/multi-ref1-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref1-100000-user2
-	100000 <user1@example.com> refs/tags/multi-ref2-100000-user1
-	100000 <user2@example.com> refs/tags/multi-ref2-100000-user2
-	200000 <user1@example.com> refs/tags/multi-ref1-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref1-200000-user2
-	200000 <user1@example.com> refs/tags/multi-ref2-200000-user1
-	200000 <user2@example.com> refs/tags/multi-ref2-200000-user2
-	EOF
-	git for-each-ref \
-		--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
-		--sort=-refname \
-		--sort=taggeremail \
-		--no-sort \
-		--sort=taggerdate \
-		"refs/tags/multi-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success '--no-sort without subsequent --sort prints expected refs' '
-	cat >expected <<-\EOF &&
-	refs/tags/multi-ref1-100000-user1
-	refs/tags/multi-ref1-100000-user2
-	refs/tags/multi-ref1-200000-user1
-	refs/tags/multi-ref1-200000-user2
-	refs/tags/multi-ref2-100000-user1
-	refs/tags/multi-ref2-100000-user2
-	refs/tags/multi-ref2-200000-user1
-	refs/tags/multi-ref2-200000-user2
-	EOF
-
-	# Sort the results with `sort` for a consistent comparison against
-	# expected
-	git for-each-ref \
-		--format="%(refname)" \
-		--no-sort \
-		"refs/tags/multi-*" | sort >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'set up custom date sorting' '
-	# Dates:
-	# - Wed Feb 07 2024 21:34:20 +0000
-	# - Tue Dec 14 1999 00:05:22 +0000
-	# - Fri Jun 04 2021 11:26:51 +0000
-	# - Mon Jan 22 2007 16:44:01 GMT+0000
-	i=1 &&
-	for when in 1707341660 945129922 1622806011 1169484241
-	do
-		GIT_COMMITTER_DATE="@$when +0000" \
-		GIT_COMMITTER_EMAIL="user@example.com" \
-		git tag -m "tag $when" custom-dates-$i &&
-		i=$(($i+1)) || return 1
-	done
-'
-
-test_expect_success 'sort by date defaults to full timestamp' '
-	cat >expected <<-\EOF &&
-	945129922 refs/tags/custom-dates-2
-	1169484241 refs/tags/custom-dates-4
-	1622806011 refs/tags/custom-dates-3
-	1707341660 refs/tags/custom-dates-1
-	EOF
-
-	git for-each-ref \
-		--format="%(creatordate:unix) %(refname)" \
-		--sort=creatordate \
-		"refs/tags/custom-dates-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'sort by custom date format' '
-	cat >expected <<-\EOF &&
-	00:05:22 refs/tags/custom-dates-2
-	11:26:51 refs/tags/custom-dates-3
-	16:44:01 refs/tags/custom-dates-4
-	21:34:20 refs/tags/custom-dates-1
-	EOF
-
-	git for-each-ref \
-		--format="%(creatordate:format:%H:%M:%S) %(refname)" \
-		--sort="creatordate:format:%H:%M:%S" \
-		"refs/tags/custom-dates-*" >actual &&
-	test_cmp expected actual
-'
-
-test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
-	test_when_finished "git checkout main" &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
-	sed -e "s/^\* /  /" actual >expect &&
-	git checkout --orphan orphaned-branch &&
-	git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual &&
-	test_cmp expect actual
-'
-
-cat >trailers <<EOF
-Reviewed-by: A U Thor <author@example.com>
-Signed-off-by: A U Thor <author@example.com>
-[ v2 updated patch description ]
-Acked-by: A U Thor
-  <author@example.com>
-EOF
-
-unfold () {
-	perl -0pe 's/\n\s+/ /g'
-}
-
-test_expect_success 'set up trailers for next test' '
-	echo "Some contents" > two &&
-	git add two &&
-	git commit -F - <<-EOF
-	trailers: this commit message has trailers
-
-	Some message contents
-
-	$(cat trailers)
-	EOF
-'
-
-test_trailer_option () {
-	if test "$#" -eq 3
-	then
-		prereq="$1"
-		shift
-	fi &&
-	title=$1 option=$2
-	cat >expect
-	test_expect_success $prereq "$title" '
-		git for-each-ref --format="%($option)" refs/heads/main >actual &&
-		test_cmp expect actual &&
-		git for-each-ref --format="%(contents:$option)" refs/heads/main >actual &&
-		test_cmp expect actual
-	'
-}
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) unfolds trailers' \
-	'trailers:unfold' <<-EOF
-	$(unfold <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only) shows only "key: value" trailers' \
-	'trailers:only' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=no,only=true) shows only "key: value" trailers' \
-	'trailers:only=no,only=true' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=yes) shows only "key: value" trailers' \
-	'trailers:only=yes' <<-EOF
-	$(grep -v patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:only=no) shows all trailers' \
-	'trailers:only=no' <<-EOF
-	$(cat trailers)
-
-	EOF
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:only) and %(trailers:unfold) work together' \
-	'trailers:only,unfold' <<-EOF
-	$(grep -v patch.description <trailers | unfold)
-
-	EOF
-
-test_trailer_option PERL_TEST_HELPERS '%(trailers:unfold) and %(trailers:only) work together' \
-	'trailers:unfold,only' <<-EOF
-	$(grep -v patch.description <trailers | unfold)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) shows that trailer' \
-	'trailers:key=Signed-off-by' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) is case insensitive' \
-	'trailers:key=SiGned-oFf-bY' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo:) trailing colon also works' \
-	'trailers:key=Signed-off-by:' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) multiple keys' \
-	'trailers:key=Reviewed-by:,key=Signed-off-by' <<-EOF
-	Reviewed-by: A U Thor <author@example.com>
-	Signed-off-by: A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:key=nonexistent) becomes empty' \
-	'trailers:key=Shined-off-by:' <<-EOF
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo) handles multiple lines even if folded' \
-	'trailers:key=Acked-by' <<-EOF
-	$(grep -v patch.description <trailers | grep -v Signed-off-by | grep -v Reviewed-by)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,unfold) properly unfolds' \
-	'trailers:key=Signed-Off-by,unfold' <<-EOF
-	$(unfold <trailers | grep Signed-off-by)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,only=no) also includes nontrailer lines' \
-	'trailers:key=Signed-off-by,only=no' <<-EOF
-	Signed-off-by: A U Thor <author@example.com>
-	$(grep patch.description <trailers)
-
-	EOF
-
-test_trailer_option '%(trailers:key=foo,valueonly) shows only value' \
-	'trailers:key=Signed-off-by,valueonly' <<-EOF
-	A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:separator) changes separator' \
-	'trailers:separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by: A U Thor <author@example.com>,Signed-off-by: A U Thor <author@example.com>
-	EOF
-
-test_trailer_option '%(trailers:key_value_separator) changes key-value separator' \
-	'trailers:key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by,A U Thor <author@example.com>
-	Signed-off-by,A U Thor <author@example.com>
-
-	EOF
-
-test_trailer_option '%(trailers:separator,key_value_separator) changes both separators' \
-	'trailers:separator=%x2C,key_value_separator=%x2C,key=Reviewed-by,key=Signed-off-by:' <<-EOF
-	Reviewed-by,A U Thor <author@example.com>,Signed-off-by,A U Thor <author@example.com>
-	EOF
-
-test_expect_success 'multiple %(trailers) use their own options' '
-	git tag -F - tag-with-trailers <<-\EOF &&
-	body
-
-	one: foo
-	one: bar
-	two: baz
-	two: qux
-	EOF
-	t1="%(trailers:key=one,key_value_separator=W,separator=X)" &&
-	t2="%(trailers:key=two,key_value_separator=Y,separator=Z)" &&
-	git for-each-ref --format="$t1%0a$t2" refs/tags/tag-with-trailers >actual &&
-	cat >expect <<-\EOF &&
-	oneWfooXoneWbar
-	twoYbazZtwoYqux
-	EOF
-	test_cmp expect actual
-'
-
-test_failing_trailer_option () {
-	title=$1 option=$2
-	cat >expect
-	test_expect_success "$title" '
-		# error message cannot be checked under i18n
-		test_must_fail git for-each-ref --format="%($option)" refs/heads/main 2>actual &&
-		test_cmp expect actual &&
-		test_must_fail git for-each-ref --format="%(contents:$option)" refs/heads/main 2>actual &&
-		test_cmp expect actual
-	'
-}
-
-test_failing_trailer_option '%(trailers) rejects unknown trailers arguments' \
-	'trailers:unsupported' <<-\EOF
-	fatal: unknown %(trailers) argument: unsupported
-	EOF
-
-test_failing_trailer_option '%(trailers:key) without value is error' \
-	'trailers:key' <<-\EOF
-	fatal: expected %(trailers:key=<value>)
-	EOF
-
-test_expect_success 'if arguments, %(contents:trailers) shows error if colon is missing' '
-	cat >expect <<-EOF &&
-	fatal: unrecognized %(contents) argument: trailersonly
-	EOF
-	test_must_fail git for-each-ref --format="%(contents:trailersonly)" 2>actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'basic atom: head contents:trailers' '
-	git for-each-ref --format="%(contents:trailers)" refs/heads/main >actual &&
-	sanitize_pgp <actual >actual.clean &&
-	# git for-each-ref ends with a blank line
-	cat >expect <<-EOF &&
-	$(cat trailers)
-
-	EOF
-	test_cmp expect actual.clean
-'
-
-test_expect_success 'basic atom: rest must fail' '
-	test_must_fail git for-each-ref --format="%(rest)" refs/heads/main
-'
-
-test_expect_success 'HEAD atom does not take arguments' '
-	test_must_fail git for-each-ref --format="%(HEAD:foo)" 2>err &&
-	echo "fatal: %(HEAD) does not take arguments" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'subject atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(subject:foo)" 2>err &&
-	echo "fatal: unrecognized %(subject) argument: foo" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'refname atom rejects unknown arguments' '
-	test_must_fail git for-each-ref --format="%(refname:foo)" 2>err &&
-	echo "fatal: unrecognized %(refname) argument: foo" >expect &&
-	test_cmp expect err
-'
-
-test_expect_success 'trailer parsing not fooled by --- line' '
-	git commit --allow-empty -F - <<-\EOF &&
-	this is the subject
-
-	This is the body. The message has a "---" line which would confuse a
-	message+patch parser. But here we know we have only a commit message,
-	so we get it right.
-
-	trailer: wrong
-	---
-	This is more body.
-
-	trailer: right
-	EOF
-
-	{
-		echo "trailer: right" &&
-		echo
-	} >expect &&
-	git for-each-ref --format="%(trailers)" refs/heads/main >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'Add symbolic ref for the following tests' '
-	git symbolic-ref refs/heads/sym refs/heads/main
-'
-
-cat >expected <<EOF
-refs/heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref) atom' '
-	git for-each-ref --format="%(symref)" refs/heads/sym >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref:short) atom' '
-	git for-each-ref --format="%(symref:short)" refs/heads/sym >actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-main
-heads/main
-EOF
-
-test_expect_success 'Verify usage of %(symref:lstrip) atom' '
-	git for-each-ref --format="%(symref:lstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:lstrip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual &&
-
-	git for-each-ref --format="%(symref:strip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:strip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual
-'
-
-cat >expected <<EOF
-refs
-refs/heads
-EOF
-
-test_expect_success 'Verify usage of %(symref:rstrip) atom' '
-	git for-each-ref --format="%(symref:rstrip=2)" refs/heads/sym > actual &&
-	git for-each-ref --format="%(symref:rstrip=-2)" refs/heads/sym >> actual &&
-	test_cmp expected actual
-'
-
-test_expect_success ':remotename and :remoteref' '
-	git init remote-tests &&
-	(
-		cd remote-tests &&
-		test_commit initial &&
-		git branch -M main &&
-		git remote add from fifth.coffee:blub &&
-		git config branch.main.remote from &&
-		git config branch.main.merge refs/heads/stable &&
-		git remote add to southridge.audio:repo &&
-		git config remote.to.push "refs/heads/*:refs/heads/pushed/*" &&
-		git config branch.main.pushRemote to &&
-		for pair in "%(upstream)=refs/remotes/from/stable" \
-			"%(upstream:remotename)=from" \
-			"%(upstream:remoteref)=refs/heads/stable" \
-			"%(push)=refs/remotes/to/pushed/main" \
-			"%(push:remotename)=to" \
-			"%(push:remoteref)=refs/heads/pushed/main"
-		do
-			echo "${pair#*=}" >expect &&
-			git for-each-ref --format="${pair%=*}" \
-				refs/heads/main >actual &&
-			test_cmp expect actual || exit 1
-		done &&
-		git branch push-simple &&
-		git config branch.push-simple.pushRemote from &&
-		actual="$(git for-each-ref \
-			--format="%(push:remotename),%(push:remoteref)" \
-			refs/heads/push-simple)" &&
-		test from, = "$actual"
-	)
-'
-
-test_expect_success 'for-each-ref --ignore-case ignores case' '
-	git for-each-ref --format="%(refname)" refs/heads/MAIN >actual &&
-	test_must_be_empty actual &&
-
-	echo refs/heads/main >expect &&
-	git for-each-ref --format="%(refname)" --ignore-case \
-		refs/heads/MAIN >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --omit-empty works' '
-	git for-each-ref --format="%(refname)" >actual &&
-	test_line_count -gt 1 actual &&
-	git for-each-ref --format="%(if:equals=refs/heads/main)%(refname)%(then)%(refname)%(end)" --omit-empty >actual &&
-	echo refs/heads/main >expect &&
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref --ignore-case works on multiple sort keys' '
-	# name refs numerically to avoid case-insensitive filesystem conflicts
-	nr=0 &&
-	for email in a A b B
-	do
-		for subject in a A b B
-		do
-			GIT_COMMITTER_EMAIL="$email@example.com" \
-			git tag -m "tag $subject" icase-$(printf %02d $nr) &&
-			nr=$((nr+1))||
-			return 1
-		done
-	done &&
-	git for-each-ref --ignore-case \
-		--format="%(taggeremail) %(subject) %(refname)" \
-		--sort=refname \
-		--sort=subject \
-		--sort=taggeremail \
-		refs/tags/icase-* >actual &&
-	cat >expect <<-\EOF &&
-	<a@example.com> tag a refs/tags/icase-00
-	<a@example.com> tag A refs/tags/icase-01
-	<A@example.com> tag a refs/tags/icase-04
-	<A@example.com> tag A refs/tags/icase-05
-	<a@example.com> tag b refs/tags/icase-02
-	<a@example.com> tag B refs/tags/icase-03
-	<A@example.com> tag b refs/tags/icase-06
-	<A@example.com> tag B refs/tags/icase-07
-	<b@example.com> tag a refs/tags/icase-08
-	<b@example.com> tag A refs/tags/icase-09
-	<B@example.com> tag a refs/tags/icase-12
-	<B@example.com> tag A refs/tags/icase-13
-	<b@example.com> tag b refs/tags/icase-10
-	<b@example.com> tag B refs/tags/icase-11
-	<B@example.com> tag b refs/tags/icase-14
-	<B@example.com> tag B refs/tags/icase-15
-	EOF
-	test_cmp expect actual
-'
-
-test_expect_success 'for-each-ref reports broken tags' '
-	git tag -m "good tag" broken-tag-good HEAD &&
-	git cat-file tag broken-tag-good >good &&
-	sed s/commit/blob/ <good >bad &&
-	bad=$(git hash-object -w -t tag bad) &&
-	git update-ref refs/tags/broken-tag-bad $bad &&
-	test_must_fail git for-each-ref --format="%(*objectname)" \
-		refs/tags/broken-tag-*
-'
-
-test_expect_success 'set up tag with signature and no blank lines' '
-	git tag -F - fake-sig-no-blanks <<-\EOF
-	this is the subject
-	-----BEGIN PGP SIGNATURE-----
-	not a real signature, but we just care about the
-	subject/body parsing. It is important here that
-	there are no blank lines in the signature.
-	-----END PGP SIGNATURE-----
-	EOF
-'
-
-test_atom refs/tags/fake-sig-no-blanks contents:subject 'this is the subject'
-test_atom refs/tags/fake-sig-no-blanks contents:body ''
-test_atom refs/tags/fake-sig-no-blanks contents:signature "$sig"
-
-test_expect_success 'set up tag with CRLF signature' '
-	append_cr <<-\EOF |
-	this is the subject
-	-----BEGIN PGP SIGNATURE-----
-
-	not a real signature, but we just care about
-	the subject/body parsing. It is important here
-	that there is a blank line separating this
-	from the signature header.
-	-----END PGP SIGNATURE-----
-	EOF
-	git tag -F - --cleanup=verbatim fake-sig-crlf
-'
-
-test_atom refs/tags/fake-sig-crlf contents:subject 'this is the subject'
-test_atom refs/tags/fake-sig-crlf contents:body ''
-
-# CRLF is retained in the signature, so we have to pass our expected value
-# through append_cr. But test_atom requires a shell string, which means command
-# substitution, and the shell will strip trailing newlines from the output of
-# the substitution. Hack around it by adding and then removing a dummy line.
-sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
-sig_crlf=${sig_crlf%dummy}
-test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
-
-test_expect_success 'set up tag with signature and trailers' '
-	git tag -F - fake-sig-trailer <<-\EOF
-	this is the subject
-
-	this is the body
-
-	My-Trailer: foo
-	-----BEGIN PGP SIGNATURE-----
-
-	not a real signature, but we just care about the
-	subject/body/trailer parsing.
-	-----END PGP SIGNATURE-----
-	EOF
-'
-
-# use "separator=" here to suppress the terminating newline
-test_atom refs/tags/fake-sig-trailer trailers:separator= 'My-Trailer: foo'
-
-test_expect_success 'git for-each-ref --stdin: empty' '
-	>in &&
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	git for-each-ref --format="%(refname)" >expect &&
-	test_cmp expect actual
-'
-
-test_expect_success 'git for-each-ref --stdin: fails if extra args' '
-	>in &&
-	test_must_fail git for-each-ref --format="%(refname)" \
-		--stdin refs/heads/extra <in 2>err &&
-	grep "unknown arguments supplied with --stdin" err
-'
-
-test_expect_success 'git for-each-ref --stdin: matches' '
-	cat >in <<-EOF &&
-	refs/tags/multi*
-	refs/heads/amb*
-	EOF
-
-	cat >expect <<-EOF &&
-	refs/heads/ambiguous
-	refs/tags/multi-ref1-100000-user1
-	refs/tags/multi-ref1-100000-user2
-	refs/tags/multi-ref1-200000-user1
-	refs/tags/multi-ref1-200000-user2
-	refs/tags/multi-ref2-100000-user1
-	refs/tags/multi-ref2-100000-user2
-	refs/tags/multi-ref2-200000-user1
-	refs/tags/multi-ref2-200000-user2
-	refs/tags/multiline
-	EOF
-
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'git for-each-ref with non-existing refs' '
-	cat >in <<-EOF &&
-	refs/heads/this-ref-does-not-exist
-	refs/tags/bogus
-	EOF
-
-	git for-each-ref --format="%(refname)" --stdin <in >actual &&
-	test_must_be_empty actual &&
-
-	xargs git for-each-ref --format="%(refname)" <in >actual &&
-	test_must_be_empty actual
-'
-
-test_expect_success 'git for-each-ref with nested tags' '
-	git tag -am "Normal tag" nested/base HEAD &&
-	git tag -am "Nested tag" nested/nest1 refs/tags/nested/base &&
-	git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 &&
-
-	head_oid="$(git rev-parse HEAD)" &&
-	base_tag_oid="$(git rev-parse refs/tags/nested/base)" &&
-	nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" &&
-	nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" &&
-
-	cat >expect <<-EOF &&
-	refs/tags/nested/base $base_tag_oid tag $head_oid commit
-	refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit
-	refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit
-	EOF
-
-	git for-each-ref \
-		--format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \
-		refs/tags/nested/ >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'is-base atom with non-commits' '
-	git for-each-ref --format="%(is-base:HEAD) %(refname)" >out 2>err &&
-	grep "(HEAD) refs/heads/main" out &&
-
-	test_line_count = 2 err &&
-	grep "error: object .* is a commit, not a blob" err &&
-	grep "error: bad tag pointer to" err
-'
-
-GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
-TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)"
-
-test_expect_success GPG 'setup for signature atom using gpg' '
-	git checkout -b signed &&
-
-	test_when_finished "test_unconfig commit.gpgSign" &&
-
-	echo "1" >file &&
-	git add file &&
-	test_tick &&
-	git commit -S -m "file: 1" &&
-	git tag first-signed &&
-
-	echo "2" >file &&
-	test_tick &&
-	git commit -a -m "file: 2" &&
-	git tag second-unsigned &&
-
-	git config commit.gpgSign 1 &&
-	echo "3" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 3" &&
-	git tag third-unsigned &&
-
-	test_tick &&
-	git rebase -f HEAD^^ && git tag second-signed HEAD^ &&
-	git tag third-signed &&
-
-	echo "4" >file &&
-	test_tick &&
-	git commit -a -SB7227189 -m "file: 4" &&
-	git tag fourth-signed &&
-
-	echo "5" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 5" &&
-	git tag fifth-unsigned &&
-
-	echo "6" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 6" &&
-
-	test_tick &&
-	git rebase -f HEAD^^ &&
-	git tag fifth-signed HEAD^ &&
-	git tag sixth-signed &&
-
-	echo "7" >file &&
-	test_tick &&
-	git commit -a --no-gpg-sign -m "file: 7" &&
-	git tag seventh-unsigned
-'
-
-test_expect_success GPGSSH 'setup for signature atom using ssh' '
-	test_when_finished "test_unconfig gpg.format user.signingkey" &&
-
-	test_config gpg.format ssh &&
-	test_config user.signingkey "${GPGSSH_KEY_PRIMARY}" &&
-	echo "8" >file &&
-	test_tick &&
-	git add file &&
-	git commit -S -m "file: 8" &&
-	git tag eighth-signed-ssh
-'
-
-test_expect_success GPG2 'bare signature atom' '
-	git verify-commit first-signed 2>expect &&
-	echo  >>expect &&
-	git for-each-ref refs/tags/first-signed \
-		--format="%(signature)" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show good signature with custom format' '
-	git verify-commit first-signed &&
-	cat >expect <<-\EOF &&
-	G
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	EOF
-	git for-each-ref refs/tags/first-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-test_expect_success GPGSSH 'show good signature with custom format with ssh' '
-	test_config gpg.ssh.allowedSignersFile "${GPGSSH_ALLOWED_SIGNERS}" &&
-	FINGERPRINT=$(ssh-keygen -lf "${GPGSSH_KEY_PRIMARY}" | awk "{print \$2;}") &&
-	cat >expect.tmpl <<-\EOF &&
-	G
-	FINGERPRINT
-	principal with number 1
-	FINGERPRINT
-
-	EOF
-	sed "s|FINGERPRINT|$FINGERPRINT|g" expect.tmpl >expect &&
-	git for-each-ref refs/tags/eighth-signed-ssh \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'signature atom with grade option and bad signature' '
-	git cat-file commit third-signed >raw &&
-	sed -e "s/^file: 3/file: 3 forged/" raw >forged1 &&
-	FORGED1=$(git hash-object -w -t commit forged1) &&
-	git update-ref refs/tags/third-signed "$FORGED1" &&
-	test_must_fail git verify-commit "$FORGED1" &&
-
-	cat >expect <<-\EOF &&
-	B
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-
-
-	EOF
-	git for-each-ref refs/tags/third-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with custom format' '
-	cat >expect <<-\EOF &&
-	U
-	65A0EEA02E30CAD7
-	Eris Discordia <discord@example.net>
-	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
-	D4BE22311AD3131E5EDA29A461092E85B7227189
-	EOF
-	git for-each-ref refs/tags/fourth-signed \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with undefined trust level' '
-	cat >expect <<-\EOF &&
-	undefined
-	65A0EEA02E30CAD7
-	Eris Discordia <discord@example.net>
-	F8364A59E07FFE9F4D63005A65A0EEA02E30CAD7
-	D4BE22311AD3131E5EDA29A461092E85B7227189
-	EOF
-	git for-each-ref refs/tags/fourth-signed \
-		--format="$TRUSTLEVEL_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show untrusted signature with ultimate trust level' '
-	cat >expect <<-\EOF &&
-	ultimate
-	13B6F51ECDDE430D
-	C O Mitter <committer@example.com>
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	73D758744BE721698EC54E8713B6F51ECDDE430D
-	EOF
-	git for-each-ref refs/tags/sixth-signed \
-		--format="$TRUSTLEVEL_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show unknown signature with custom format' '
-	cat >expect <<-\EOF &&
-	E
-	13B6F51ECDDE430D
-
-
-
-	EOF
-	GNUPGHOME="$GNUPGHOME_NOT_USED" git for-each-ref \
-		refs/tags/sixth-signed --format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success GPG 'show lack of signature with custom format' '
-	cat >expect <<-\EOF &&
-	N
-
-
-
-
-	EOF
-	git for-each-ref refs/tags/seventh-unsigned \
-		--format="$GRADE_FORMAT" >actual &&
-	test_cmp expect actual
-'
+. "$TEST_DIRECTORY"/for-each-ref-tests.sh
 
 test_done
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* [GSoC][PATCH v6 6/6] t: add test for git refs list subcommand
  2025-08-05  9:27         ` [GSoC][PATCH v6 0/6] Add " Meet Soni
                             ` (4 preceding siblings ...)
  2025-08-05  9:27           ` [GSoC][PATCH v6 5/6] t6300: refactor tests to be shareable Meet Soni
@ 2025-08-05  9:27           ` Meet Soni
  2025-08-05 13:07           ` [GSoC][PATCH v6 0/6] Add " Patrick Steinhardt
  2025-08-05 16:12           ` Junio C Hamano
  7 siblings, 0 replies; 65+ messages in thread
From: Meet Soni @ 2025-08-05  9:27 UTC (permalink / raw)
  To: git
  Cc: ps, shejialuo, karthik.188, gitster, sunshine, phillip.wood123,
	Meet Soni
Add a test script, `t/t1461-refs-list.sh`, for the new `git refs list`
command.
This script acts as a simple driver, leveraging the shared test library
created in the preceding commit. It works by overriding the
`$git_for_each_ref` variable to "git refs list" and then sourcing the
shared library (`t/for-each-ref-tests.sh`).
This approach ensures that `git refs list` is tested against the
entire comprehensive test suite of `git for-each-ref`, verifying
that it acts as a compatible drop-in replacement.
Mentored-by: Patrick Steinhardt <ps@pks.im>
Mentored-by: shejialuo <shejialuo@gmail.com>
Mentored-by: Karthik Nayak <karthik.188@gmail.com>
Signed-off-by: Meet Soni <meetsoni3017@gmail.com>
---
 t/meson.build        | 1 +
 t/t1461-refs-list.sh | 8 ++++++++
 2 files changed, 9 insertions(+)
 create mode 100755 t/t1461-refs-list.sh
diff --git a/t/meson.build b/t/meson.build
index bbeba1a8d5..23a3702958 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -208,6 +208,7 @@ integration_tests = [
   't1450-fsck.sh',
   't1451-fsck-buffer.sh',
   't1460-refs-migrate.sh',
+  't1461-refs-list.sh',
   't1500-rev-parse.sh',
   't1501-work-tree.sh',
   't1502-rev-parse-parseopt.sh',
diff --git a/t/t1461-refs-list.sh b/t/t1461-refs-list.sh
new file mode 100755
index 0000000000..36e3d81e59
--- /dev/null
+++ b/t/t1461-refs-list.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+test_description='git refs list tests'
+
+. ./test-lib.sh
+
+git_for_each_ref='git refs list'
+. "$TEST_DIRECTORY"/for-each-ref-tests.sh
-- 
2.34.1
^ permalink raw reply related	[flat|nested] 65+ messages in thread
* Re: [GSoC][PATCH v6 0/6] Add refs list subcommand
  2025-08-05  9:27         ` [GSoC][PATCH v6 0/6] Add " Meet Soni
                             ` (5 preceding siblings ...)
  2025-08-05  9:27           ` [GSoC][PATCH v6 6/6] t: add test for git refs list subcommand Meet Soni
@ 2025-08-05 13:07           ` Patrick Steinhardt
  2025-08-05 16:12           ` Junio C Hamano
  7 siblings, 0 replies; 65+ messages in thread
From: Patrick Steinhardt @ 2025-08-05 13:07 UTC (permalink / raw)
  To: Meet Soni; +Cc: git, shejialuo, karthik.188, gitster, sunshine, phillip.wood123
On Tue, Aug 05, 2025 at 02:57:52PM +0530, Meet Soni wrote:
> Hello everyone,
> 
> This is the sixth version of the patch series that introduces the git
> refs list subcommand.
> 
> changes in v6:
>   - rebased onto v2.51.0-rc0
I'm happy with this version, thanks!
Patrick
^ permalink raw reply	[flat|nested] 65+ messages in thread
* Re: [GSoC][PATCH v6 0/6] Add refs list subcommand
  2025-08-05  9:27         ` [GSoC][PATCH v6 0/6] Add " Meet Soni
                             ` (6 preceding siblings ...)
  2025-08-05 13:07           ` [GSoC][PATCH v6 0/6] Add " Patrick Steinhardt
@ 2025-08-05 16:12           ` Junio C Hamano
  7 siblings, 0 replies; 65+ messages in thread
From: Junio C Hamano @ 2025-08-05 16:12 UTC (permalink / raw)
  To: Meet Soni; +Cc: git, ps, shejialuo, karthik.188, sunshine, phillip.wood123
Meet Soni <meetsoni3017@gmail.com> writes:
> Hello everyone,
>
> This is the sixth version of the patch series that introduces the git
> refs list subcommand.
>
> changes in v6:
>   - rebased onto v2.51.0-rc0
Thanks.  Will queue.
^ permalink raw reply	[flat|nested] 65+ messages in thread
end of thread, other threads:[~2025-08-05 16:12 UTC | newest]
Thread overview: 65+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-06-27  7:49 [GSoC][RFC PATCH 0/2] Add refs list subcommand Meet Soni
2025-06-27  7:49 ` [GSoC][RFC PATCH 1/2] builtin/refs: add " Meet Soni
2025-06-27 16:27   ` Jean-Noël Avila
2025-06-27 18:13     ` Junio C Hamano
2025-06-30  4:28     ` Meet Soni
2025-06-29 11:05   ` [PATCH] doc:git-for-each-ref: fix styling and typos Jean-Noël Avila
2025-06-30 15:48     ` Junio C Hamano
2025-06-30 18:55       ` Jean-Noël AVILA
2025-06-27  7:49 ` [GSoC][RFC PATCH 2/2] t: add test for git refs list subcommand Meet Soni
2025-06-27 18:03 ` [GSoC][RFC PATCH 0/2] Add " Junio C Hamano
2025-06-28  8:05   ` shejialuo
2025-06-30 14:05     ` Junio C Hamano
2025-07-06 12:58       ` shejialuo
2025-06-30  3:53   ` Meet Soni
2025-06-30 20:10     ` Junio C Hamano
2025-07-09 13:36       ` Patrick Steinhardt
2025-07-17  7:50 ` [GSoC][RFC PATCH v2 " Meet Soni
2025-07-17  7:50   ` [GSoC][RFC PATCH v2 1/2] builtin/refs: add " Meet Soni
2025-07-17 16:48     ` Eric Sunshine
2025-07-23  5:01       ` Meet Soni
2025-07-17  7:50   ` [GSoC][RFC PATCH v2 2/2] t: add test for git refs " Meet Soni
2025-07-17 21:01     ` Junio C Hamano
2025-07-23  5:17       ` Meet Soni
2025-07-23 15:03         ` Junio C Hamano
2025-07-23  6:43   ` [GSoC][RFC PATCH v3 0/3] Add " Meet Soni
2025-07-23  6:43     ` [GSoC][RFC PATCH v3 1/3] builtin/refs: add " Meet Soni
2025-07-24  5:58       ` Patrick Steinhardt
2025-07-24 16:01         ` Junio C Hamano
2025-07-25 11:10         ` Meet Soni
2025-07-23  6:43     ` [GSoC][RFC PATCH v3 2/3] t6300: refactor tests to be shareable Meet Soni
2025-07-23  6:43     ` [GSoC][RFC PATCH v3 3/3] t: add test for git refs list subcommand Meet Soni
2025-07-31  9:00     ` [GSoC][RFC PATCH v4 0/5] Add " Meet Soni
2025-07-31  9:00       ` [GSoC][RFC PATCH v4 1/5] doc: factor out common option Meet Soni
2025-07-31  9:00       ` [GSoC][RFC PATCH v4 2/5] builtin/for-each-ref: factor out core logic into a helper Meet Soni
2025-08-01  5:54         ` Patrick Steinhardt
2025-08-04  6:34           ` Meet Soni
2025-07-31  9:00       ` [GSoC][RFC PATCH v4 3/5] builtin/refs: add list subcommand Meet Soni
2025-08-01 13:27         ` Phillip Wood
2025-08-01 14:43           ` Junio C Hamano
2025-08-01 15:49             ` Phillip Wood
2025-08-01 17:14               ` Junio C Hamano
2025-08-04  9:28                 ` Phillip Wood
2025-08-04  6:32               ` Meet Soni
2025-08-04  9:27               ` Phillip Wood
2025-08-04 15:35                 ` Junio C Hamano
2025-07-31  9:00       ` [GSoC][RFC PATCH v4 4/5] t6300: refactor tests to be shareable Meet Soni
2025-07-31  9:00       ` [GSoC][RFC PATCH v4 5/5] t: add test for git refs list subcommand Meet Soni
2025-08-01  5:54       ` [GSoC][RFC PATCH v4 0/5] Add " Patrick Steinhardt
2025-08-04  9:22       ` [GSoC][RFC PATCH v5 0/6] " Meet Soni
2025-08-04  9:22         ` [GSoC][RFC PATCH v5 1/6] doc: factor out common option Meet Soni
2025-08-04 18:34           ` Junio C Hamano
2025-08-04  9:22         ` [GSoC][RFC PATCH v5 2/6] builtin/for-each-ref: align usage string with the man page Meet Soni
2025-08-04  9:22         ` [GSoC][RFC PATCH v5 3/6] builtin/for-each-ref: factor out core logic into a helper Meet Soni
2025-08-04  9:22         ` [GSoC][RFC PATCH v5 4/6] builtin/refs: add list subcommand Meet Soni
2025-08-04  9:22         ` [GSoC][RFC PATCH v5 5/6] t6300: refactor tests to be shareable Meet Soni
2025-08-04  9:22         ` [GSoC][RFC PATCH v5 6/6] t: add test for git refs list subcommand Meet Soni
2025-08-05  9:27         ` [GSoC][PATCH v6 0/6] Add " Meet Soni
2025-08-05  9:27           ` [GSoC][PATCH v6 1/6] doc: factor out common option Meet Soni
2025-08-05  9:27           ` [GSoC][PATCH v6 2/6] builtin/for-each-ref: align usage string with the man page Meet Soni
2025-08-05  9:27           ` [GSoC][PATCH v6 3/6] builtin/for-each-ref: factor out core logic into a helper Meet Soni
2025-08-05  9:27           ` [GSoC][PATCH v6 4/6] builtin/refs: add list subcommand Meet Soni
2025-08-05  9:27           ` [GSoC][PATCH v6 5/6] t6300: refactor tests to be shareable Meet Soni
2025-08-05  9:27           ` [GSoC][PATCH v6 6/6] t: add test for git refs list subcommand Meet Soni
2025-08-05 13:07           ` [GSoC][PATCH v6 0/6] Add " Patrick Steinhardt
2025-08-05 16:12           ` Junio C Hamano
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).