threads / rfc / 63644

RFC patch, 2 partsAdd refs list subcommand

Subject: [GSoC][RFC PATCH 0/2] Add refs list subcommand

## tl;dr

5 messages between Jun 14, 2025 and Jun 17, 2025. Diffs are folded; open one to read it.

replies: 4people: 2as markdown or json

Meet Soni· Jun 14, 2025, 07:05 UTC · lore

This patch series introduces the git refs list subcommand, as part of a longer-term goal to consolidate and modernize ref listing functionality currently split between git-show-ref(1) and git-for-each-ref(1).

The initial implementation focuses on mirroring the behavior of git-show-ref, providing support for filtering by --branches, --tags, and --head, and implementing pattern matching similar to the legacy command. This ensures backward compatibility.

That said, git-for-each-ref(1) offers more flexible pattern matching and we acknowledge that its style may be a better fit in the long run. As such, this RFC deliberately starts with the show-ref matching semantics to solicit feedback on whether to switch to for-each-ref style matching as the default, with a compatibility flag to preserve legacy behavior.

It's also worth highlighting that several options from git-show-ref are intended to be supported in the git refs list subcommand. These include flags such as '--abbrev', '--quiet', '--dereference', '--hash', and '--exclude-existing'. While this series focuses on core functionality and pattern matching, these additional options are within scope for future patches.

Additionally, the git-for-each-ref(1) command offers a rich set of features that would be valuable to incorporate into git refs list. At this point, all of its existing options appear to provide meaningful functionality, and my current thinking is to support them incrementally as part of expanding this subcommand. I'd appreciate feedback on whether there are any options that should be reconsidered or excluded in this consolidation effort.

This RFC is meant to start a broader discussion on:
  - The desired default behavior of pattern matching in git refs list
  - Which features from both git-show-ref and git-for-each-ref should be
    preserved, rethought, or dropped
  - How much backward compatibility we want to offer, and through what
    interface (e.g., compatibility flags)
Feedback and thoughts on these topics would be very welcome.
Meet Soni (2):
  builtin/refs: add list subcommand
  t: add tests for refs list subcommand
 Documentation/git-refs.adoc |  25 ++++++++
 builtin/refs.c              | 110 ++++++++++++++++++++++++++++++++++++
 t/meson.build               |   1 +
 t/t1461-refs-list.sh        |  95 +++++++++++++++++++++++++++++++
 4 files changed, 231 insertions(+)
 create mode 100755 t/t1461-refs-list.sh
-- 
2.34.1
Meet Soni· Jun 14, 2025, 07:05 UTC · re: Meet Soni · lore

[GSoC][RFC PATCH 1/2] builtin/refs: add list subcommand

Git's reference management is distributed across multiple commands and as part of an ongoing effort to streamline and modernize reference handling, we are beginning to consolidate these operations into a cohesive `git refs` command.

Add a `list` subcommand to `git refs` as a modern replacement for `git show-ref`, consolidating ref listing functionality under the unified `git refs` command.

The initial implementation supports the following options from `git show-ref`:

- --head
- --tags
- --branches
- patterns argument

For large changes, this patch limits itself to the basic ref listing and commonly used flags. Remaining options will be added incrementally in follow-up patches, guided by feedback from the mailing list.

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 |  25 ++++++++
 builtin/refs.c              | 110 ++++++++++++++++++++++++++++++++++++
 2 files changed, 135 insertions(+)
Show changes to 2 files +135 −0

Documentation/git-refs.adoc, builtin/refs.c

diff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc
index 4d6dc994f9..397c3ceb01 100644
--- a/Documentation/git-refs.adoc
+++ b/Documentation/git-refs.adoc
@@ -11,6 +11,7 @@ SYNOPSIS
 [synopsis]
 git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]
 git refs verify [--strict] [--verbose]
+git refs list [--head] [--branches] [--tag] [--] [<pattern>...]
 
 DESCRIPTION
 -----------
@@ -26,6 +27,12 @@ migrate::
 verify::
 	Verify reference database consistency.
 
+list::
+	Displays references available in a local repository along with the associated
+	commit IDs. Results can be filtered using a pattern.
+
+	By default, shows the tags, heads, and remote refs.
+
 OPTIONS
 -------
 
@@ -57,6 +64,24 @@ 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':
+
+--head::
+	Show the HEAD reference, even if it would normally be filtered out.
+
+--branches::
+--tags::
+	Limit to local branches and local tags, respectively.  These options
+	are not mutually exclusive; when given both, references stored in
+	"refs/heads" and "refs/tags" are displayed.
+
+<pattern>...::
+	Show references matching one or more patterns. Patterns are matched from
+	the end of the full name, and only complete parts are matched, e.g.
+	'master' matches 'refs/heads/master', 'refs/remotes/origin/master',
+	'refs/tags/jedi/master' but not 'refs/heads/mymaster' or
+	'refs/remotes/master/jedi'.
+
 KNOWN LIMITATIONS
 -----------------
 
diff --git a/builtin/refs.c b/builtin/refs.c
index 998d2a2c1c..c098132191 100644
--- a/builtin/refs.c
+++ b/builtin/refs.c
@@ -2,6 +2,9 @@
 #include "builtin.h"
 #include "config.h"
 #include "fsck.h"
+#include "hex.h"
+#include "object-name.h"
+#include "object-store.h"
 #include "parse-options.h"
 #include "refs.h"
 #include "strbuf.h"
@@ -13,6 +16,9 @@
 #define REFS_VERIFY_USAGE \
 	N_("git refs verify [--strict] [--verbose]")
 
+#define REFS_LIST_USAGE \
+	N_("git refs list [--head] [--branches] [--tag] [--] [<pattern>...]")
+
 static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
 			    struct repository *repo UNUSED)
 {
@@ -101,6 +107,108 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix,
 	return ret;
 }
 
+struct list_options {
+	unsigned int show_head;
+	unsigned int filter_branches;
+	unsigned int filter_tags;
+	unsigned int found_match;
+	const char **patterns;
+};
+
+static void print_ref(const char *refname, const struct object_id *oid)
+{
+	const char *hex;
+
+	hex = oid_to_hex(oid);
+	if (!has_object(the_repository, oid,
+			HAS_OBJECT_RECHECK_PACKED | HAS_OBJECT_FETCH_PROMISOR))
+		die("git refs list: bad ref %s (%s)", refname,
+		    hex);
+
+	printf("%s %s\n", hex, refname);
+}
+
+static int list_ref(const char *refname, const char *referent UNUSED,
+		    const struct object_id *oid, int flag UNUSED, void *cbdata)
+{
+	struct list_options *data = cbdata;
+
+	if (data->show_head && !strcmp(refname, "HEAD"))
+		goto match;
+
+	if (data->patterns) {
+		int reflen = strlen(refname);
+		const char **pattern_ptr = data->patterns, *pattern;
+		while ((pattern = *pattern_ptr++) != NULL) {
+			int pattern_len = strlen(pattern);
+			if (pattern_len > reflen)
+				continue;
+			if (memcmp(pattern, refname + reflen - pattern_len, pattern_len))
+				continue;
+			if (pattern_len == reflen)
+				goto match;
+			if (refname[reflen - pattern_len - 1] == '/')
+				goto match;
+		}
+		return 0;
+	}
+
+match:
+	data->found_match++;
+
+	print_ref(refname, oid);
+
+	return 0;
+}
+
+static int cmd_refs_list(int argc, const char **argv, const char *prefix,
+			 struct repository *repo UNUSED)
+{
+	struct list_options list_opts = {0};
+	const char * const list_usage[] = {
+		REFS_LIST_USAGE,
+		NULL,
+	};
+	struct option options[] = {
+		OPT_BOOL(0, "head", &list_opts.show_head,
+			 N_("show the HEAD reference, even if it would be filtered out")),
+		OPT_BOOL(0, "tags", &list_opts.filter_tags,
+			 N_("only show tags (can be combined with --branches)")),
+		OPT_BOOL(0, "branches", &list_opts.filter_branches,
+			 N_("only show branches (can be combined with --tags)")),
+		OPT_END(),
+	};
+
+	argc = parse_options(argc, argv, prefix, options, list_usage, 0);
+
+	if (argv && *argv)
+		list_opts.patterns = argv;
+
+	if (list_opts.show_head)
+		refs_head_ref(get_main_ref_store(the_repository), list_ref,
+			      &list_opts);
+
+	if (list_opts.filter_tags || list_opts.filter_branches) {
+		if (list_opts.filter_branches)
+			refs_for_each_fullref_in(get_main_ref_store(the_repository),
+						 "refs/heads/", NULL,
+						 list_ref, &list_opts);
+
+		if (list_opts.filter_tags)
+			refs_for_each_fullref_in(get_main_ref_store(the_repository),
+						 "refs/tags/", NULL,
+						 list_ref, &list_opts);
+	} else {
+		refs_for_each_ref(get_main_ref_store(the_repository),
+				  list_ref, &list_opts);
+	}
+
+	if (!list_opts.found_match)
+		return 1;
+
+	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
Meet Soni· Jun 14, 2025, 07:05 UTC · re: Meet Soni · lore

[GSoC][RFC PATCH 2/2] t: add tests for refs list subcommand

Test the implemented functionality of `git refs list` and verify backward compatibility with `git show-ref` for the supported flags and patterns.

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 | 95 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 96 insertions(+)
 create mode 100755 t/t1461-refs-list.sh
Show changes to 2 files +96 −0

t/meson.build, t/t1461-refs-list.sh

diff --git a/t/meson.build b/t/meson.build
index d052fc3e23..c9d0863490 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..d628a193fc
--- /dev/null
+++ b/t/t1461-refs-list.sh
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+test_description='Verify git refs list functionality and compatibility with git show-ref'
+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	test_commit --annotate A &&
+	git checkout -b side &&
+	test_commit --annotate B &&
+	git checkout main &&
+	test_commit C &&
+	git branch B A^0
+'
+
+test_expect_success 'refs list --branches, --tags, --head, pattern' '
+	for branch in B main side
+	do
+		echo $(git rev-parse refs/heads/$branch) refs/heads/$branch || return 1
+	done >expect.branches &&
+	git refs list --branches >actual &&
+	test_cmp expect.branches actual &&
+
+	for tag in A B C
+	do
+		echo $(git rev-parse refs/tags/$tag) refs/tags/$tag || return 1
+	done >expect.tags &&
+	git refs list --tags >actual &&
+	test_cmp expect.tags actual &&
+
+	cat expect.branches expect.tags >expect &&
+	git refs list --branches --tags >actual &&
+	test_cmp expect actual &&
+
+	{
+		echo $(git rev-parse HEAD) HEAD &&
+		cat expect.branches expect.tags
+	} >expect &&
+	git refs list --branches --tags --head >actual &&
+	test_cmp expect actual &&
+
+	{
+		echo $(git rev-parse HEAD) HEAD &&
+		echo $(git rev-parse refs/heads/B) refs/heads/B &&
+		echo $(git rev-parse refs/tags/B) refs/tags/B
+	} >expect &&
+	git refs list --head B >actual &&
+	test_cmp expect actual &&
+
+	{
+		echo $(git rev-parse refs/heads/B) refs/heads/B &&
+		echo $(git rev-parse refs/tags/A) refs/tags/A &&
+		echo $(git rev-parse refs/tags/B) refs/tags/B
+	} >expect &&
+	git refs list A B >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'Backward compatibility with show-ref' '
+	git show-ref >expect&&
+	git refs list >actual&&
+	test_cmp expect actual &&
+
+	git show-ref --branches >expect &&
+	git refs list --branches >actual &&
+	test_cmp expect actual &&
+
+	git show-ref --tags >expect &&
+	git refs list --tags >actual &&
+	test_cmp expect actual &&
+
+	git show-ref --head >expect &&
+	git refs list --head >actual &&
+	test_cmp expect actual &&
+
+	git show-ref --branches --tags --head >expect &&
+	git refs list --branches --tags --head >actual &&
+	test_cmp expect actual &&
+
+	git show-ref B >expect &&
+	git refs list B >actual &&
+	test_cmp expect actual &&
+
+	git show-ref --head B >expect &&
+	git refs list --head B >actual &&
+	test_cmp expect actual &&
+
+	git show-ref A B >expect &&
+	git refs list A B >actual &&
+	test_cmp expect actual
+'
+
+test_done
-- 
2.34.1
Junio C Hamano· Jun 14, 2025, 23:45 UTC · re: Meet Soni · lore

Re: [GSoC][RFC PATCH 0/2] Add refs list subcommand

Meet Soni <meetsoni3017@gmail.com> writes:
Show 11 quoted lines
> This RFC is meant to start a broader discussion on:
>
>   - The desired default behavior of pattern matching in git refs list
>
>   - Which features from both git-show-ref and git-for-each-ref should be
>     preserved, rethought, or dropped
>
>   - How much backward compatibility we want to offer, and through what
>     interface (e.g., compatibility flags)
>
> Feedback and thoughts on these topics would be very welcome.

In addition to these three points, 0th point perhaps is "Is it desirable to unify these two commands in the first place?"

I only use "show-ref" when I care about a single ref in a script, expecting to be able to switch on its exit status. Everything else I'd use for-each-ref.

But then that particular unique advantage of show-ref over for-each-ref can be done with "rev-parse --verify".

So, I'd rather not to see yet another command to do the same thing. Rather, is it insufficient to just use for-each-ref or rev-parse, let the sleeping show-ref alone, and be happy?

Show 10 quoted lines
> Meet Soni (2):
>   builtin/refs: add list subcommand
>   t: add tests for refs list subcommand
>
>  Documentation/git-refs.adoc |  25 ++++++++
>  builtin/refs.c              | 110 ++++++++++++++++++++++++++++++++++++
>  t/meson.build               |   1 +
>  t/t1461-refs-list.sh        |  95 +++++++++++++++++++++++++++++++
>  4 files changed, 231 insertions(+)
>  create mode 100755 t/t1461-refs-list.sh
Meet Soni· Jun 17, 2025, 11:51 UTC · re: Junio C Hamano · lore

Re: [GSoC][RFC PATCH 0/2] Add refs list subcommand

On Sun, 15 Jun 2025 at 05:15, Junio C Hamano <gitster@pobox.com> wrote:
Show 28 quoted lines
>
> Meet Soni <meetsoni3017@gmail.com> writes:
>
> > This RFC is meant to start a broader discussion on:
> >
> >   - The desired default behavior of pattern matching in git refs list
> >
> >   - Which features from both git-show-ref and git-for-each-ref should be
> >     preserved, rethought, or dropped
> >
> >   - How much backward compatibility we want to offer, and through what
> >     interface (e.g., compatibility flags)
> >
> > Feedback and thoughts on these topics would be very welcome.
>
> In addition to these three points, 0th point perhaps is "Is it
> desirable to unify these two commands in the first place?"
>
> I only use "show-ref" when I care about a single ref in a script,
> expecting to be able to switch on its exit status.  Everything else
> I'd use for-each-ref.
>
> But then that particular unique advantage of show-ref over for-each-ref
> can be done with "rev-parse --verify".
>
> So, I'd rather not to see yet another command to do the same thing.
> Rather, is it insufficient to just use for-each-ref or rev-parse,
> let the sleeping show-ref alone, and be happy?

The broader motivation behind this effort (and the overarching GSoC project) has been to reduce fragmentation in ref-related functionality by gradually consolidating it under the git refs umbrella, aiming for consistency and discoverability.

Your point about rev-parse --verify covering show-ref's main utility is well taken. If that truly makes show-ref redundant, then perhaps it makes more sense to focus consolidation efforts around for-each-ref instead.

One concern I have is that if we simply replicate for-each-ref under a new name, we may still face the same question: what does this new command add that the old one doesn't already handle?

I'd love to hear thoughts on what a modernized or simplified ref-listing interface could look like. Are there features or changes worth exploring that could justify the consolidation?

Thanks, Meet

← back to recent threads