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

From: Meet Soni <[email-masked]>
Date: Sat, 14 Jun 2025 12:35:35 +0530

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 <[email-masked]>
Mentored-by: shejialuo <[email-masked]>
Mentored-by: Karthik Nayak <[email-masked]>
Signed-off-by: Meet Soni <[email-masked]>
---
 Documentation/git-refs.adoc |  25 ++++++++
 builtin/refs.c              | 110 ++++++++++++++++++++++++++++++++++++
 2 files changed, 135 insertions(+)

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