Subject: [GSoC RFC PATCH v2 5/7] repo-info: add the field references.format

From: Lucas Seiki Oshiro <[email-masked]>
Date: Thu, 19 Jun 2025 19:57:49 -0300

Add the field references.format to the repo-info command. The data
retrieved in this field is the same that currently is obtained by
running `git rev-parse --show-ref-format`.

Mentored-by: Karthik Nayak <[email-masked]>
Mentored-by Patrick Steinhardt <[email-masked]>
Signed-off-by: Lucas Seiki Oshiro <[email-masked]>
---
 builtin/repo-info.c  | 108 +++++++++++++++++++++++++++++++++++++++++--
 t/t1900-repo-info.sh |  58 +++++++++++++++++++++++
 2 files changed, 162 insertions(+), 4 deletions(-)

diff --git a/builtin/repo-info.c b/builtin/repo-info.c
index 6499be0eae..6ce3e6134f 100644
--- a/builtin/repo-info.c
+++ b/builtin/repo-info.c
@@ -1,21 +1,56 @@
 #include "builtin.h"
 #include "json-writer.h"
 #include "parse-options.h"
+#include "quote.h"
+#include "refs.h"
 
 enum output_format {
 	FORMAT_JSON,
 	FORMAT_PLAINTEXT
 };
 
+enum repo_info_category {
+	CATEGORY_REFERENCES = 1 << 0
+};
+
+enum repo_info_references_field {
+	FIELD_REFERENCES_FORMAT = 1 << 0
+};
+
+struct repo_info_field {
+	enum repo_info_category category;
+	union {
+		enum repo_info_references_field references;
+	} field;
+};
+
 struct repo_info {
 	struct repository *repo;
 	enum output_format format;
+	int n_fields;
+	struct repo_info_field *fields;
 };
 
+static struct repo_info_field default_fields[] = {
+	{
+		.category = CATEGORY_REFERENCES,
+		.field.references = FIELD_REFERENCES_FORMAT
+	}
+};
+
+static void print_key_value(const char *key, const char *value) {
+	printf("%s=", key);
+	quote_c_style(value, NULL, stdout, 0);
+	putchar('\n');
+}
+
 static void repo_info_init(struct repo_info *repo_info,
 			   struct repository *repo,
-			   char *format)
+			   char *format,
+			   int allow_empty,
+			   int argc, const char **argv)
 {
+	int i;
 	repo_info->repo = repo;
 
 	if (format == NULL || !strcmp(format, "json"))
@@ -24,19 +59,83 @@ static void repo_info_init(struct repo_info *repo_info,
 		repo_info->format = FORMAT_PLAINTEXT;
 	else
 		die("invalid format %s", format);
+
+	if (argc == 0 && !allow_empty) {
+		repo_info->n_fields = ARRAY_SIZE(default_fields);
+		repo_info->fields = default_fields;
+	} else {
+		repo_info->n_fields = argc;
+		ALLOC_ARRAY(repo_info->fields, argc);
+
+		for (i = 0; i < argc; i++) {
+			const char *arg = argv[i];
+			struct repo_info_field *field = repo_info->fields + i;
+
+			if (!strcmp(arg, "references.format")) {
+				field->category = CATEGORY_REFERENCES;
+				field->field.references = FIELD_REFERENCES_FORMAT;
+			} else {
+				die("invalid field '%s'", arg);
+			}
+		}
+	}
 }
 
-static void repo_info_print_plaintext(struct repo_info *repo_info UNUSED)
+static void repo_info_release(struct repo_info *repo_info)
 {
+	if (repo_info->fields != default_fields) free(repo_info->fields);
 }
 
-static void repo_info_print_json(struct repo_info *repo_info UNUSED)
+static void repo_info_print_plaintext(struct repo_info *repo_info) {
+	struct repository *repo = repo_info->repo;
+	int i;
+	for (i = 0; i < repo_info->n_fields; i++) {
+		struct repo_info_field *field = &repo_info->fields[i];
+		switch (field->category) {
+		case CATEGORY_REFERENCES:
+			switch (field->field.references) {
+			case FIELD_REFERENCES_FORMAT:
+				print_key_value("references.format",
+						ref_storage_format_to_name(
+							repo->ref_storage_format));
+				break;
+			}
+			break;
+		}
+	}
+}
+
+static void repo_info_print_json(struct repo_info *repo_info)
 {
 	struct json_writer jw;
+	int i;
+	unsigned int categories = 0;
+	unsigned int references_fields = 0;
+	struct repository *repo = repo_info->repo;
+
+	for (i = 0; i < repo_info->n_fields; i++) {
+		struct repo_info_field *field = repo_info->fields + i;
+		categories |= field->category;
+		switch (field->category) {
+		case CATEGORY_REFERENCES:
+			references_fields |= field->field.references;
+			break;
+		}
+	}
 
 	jw_init(&jw);
 
 	jw_object_begin(&jw, 1);
+
+	if (categories & CATEGORY_REFERENCES) {
+		jw_object_inline_begin_object(&jw, "references");
+		if (references_fields & FIELD_REFERENCES_FORMAT) {
+			const char *format_name = ref_storage_format_to_name(
+				repo->ref_storage_format);
+			jw_object_string(&jw, "format", format_name);
+		}
+		jw_end(&jw);
+	}
 	jw_end(&jw);
 
 	puts(jw.json.buf);
@@ -79,8 +178,9 @@ int cmd_repo_info(int argc,
 
 	argc = parse_options(argc, argv, prefix, options, repo_info_usage,
 			     PARSE_OPT_KEEP_UNKNOWN_OPT);
-	repo_info_init(&repo_info, repo, format);
+	repo_info_init(&repo_info, repo, format, allow_empty, argc, argv);
 	repo_info_print(&repo_info);
+	repo_info_release(&repo_info);
 
 	return 0;
 }
diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh
index db4a6aad17..d6e6f6ed1d 100755
--- a/t/t1900-repo-info.sh
+++ b/t/t1900-repo-info.sh
@@ -6,6 +6,8 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
 
 . ./test-lib.sh
 
+DEFAULT_NUMBER_OF_FIELDS=1
+
 parse_json () {
 	tr '\n' ' ' | "$PERL_PATH" "$TEST_DIRECTORY/t0019/parse_json.perl"
 }
@@ -14,6 +16,45 @@ test_lazy_prereq PERLJSON '
 	perl -MJSON -e "exit 0"
 '
 
+# Test if a field is correctly returned in both plaintext and json formats.
+#
+# Usage: test_repo_info <label> <init command> <key> <expected value>
+#
+# Arguments:
+#   label: the label of the test
+#   init command: a command that creates a repository called 'repo', configured
+#      accordingly to what is being tested
+#   key: the key of the field that is being tested
+#   expected value: the value that the field should contain
+test_repo_info () {
+        label=$1
+        init_command=$2
+        key=$3
+        expected_value=$4
+
+        test_expect_success PERLJSON "json: $label" "
+                test_when_finished 'rm -rf repo' &&
+                '$SHELL_PATH' -c '$init_command' &&
+                cd repo &&
+                echo '$expected_value' >expect &&
+                git repo-info '$key' >output &&
+                cat output | parse_json >parsed &&
+                grep -F 'row[0].$key' parsed | cut -d ' ' -f 2 >value &&
+                cat value | sed 's/^0$/false/' | sed 's/^1$/true/' >actual &&
+                test_cmp expect actual
+        "
+
+        test_expect_success "plaintext: $label" "
+                test_when_finished 'rm -rf repo' &&
+                '$SHELL_PATH' -c '$init_command' &&
+                cd repo &&
+                echo '$expected_value' >expect &&
+                git repo-info --format=plaintext '$key' >output &&
+                cat output | cut -d '=' -f 2 >actual &&
+                test_cmp expect actual
+        "
+}
+
 test_expect_success PERLJSON 'json: returns empty output with allow-empty' '
 	git repo-info --allow-empty --format=json >output &&
 	test_line_count = 2 output
@@ -24,4 +65,21 @@ test_expect_success 'plaintext: returns empty output with allow-empty' '
 	test_line_count = 0 output
 '
 
+test_repo_info 'ref format files is retrieved correctly' '
+	git init --ref-format=files repo' 'references.format' 'files'
+
+test_repo_info 'ref format reftable is retrieved correctly' '
+	git init --ref-format=reftable repo' 'references.format' 'reftable'
+
+test_expect_success 'plaintext: output all default fields' "
+	git repo-info --format=plaintext >actual &&
+	test_line_count = $DEFAULT_NUMBER_OF_FIELDS actual
+"
+
+test_expect_success PERLJSON 'json: output all default fields' "
+	git repo-info --format=json > output &&
+	cat output | parse_json | grep '.*\..*\..*' >actual &&
+	test_line_count = $DEFAULT_NUMBER_OF_FIELDS actual
+"
+
 test_done
-- 
2.39.5 (Apple Git-154)