From 08c722b3f0401d9e3731c7c5cc9158b600ade964 Mon Sep 17 00:00:00 2001 From: Lucas Seiki Oshiro Date: Thu, 19 Jun 2025 19:57:45 -0300 Subject: [PATCH 1/7] repo-info: declare the repo-info command Create a new Git subcommand called repo-info. `git repo-info` will query metadata from the current repository and outputs it as JSON or plaintext. Also add entries for this new command in: - the build files (Makefile and meson.build) - builtin.h - git.c - .gitignore In option parsing, use PARSE_OPT_KEEP_UNKNOWN_OPT to allow the users specify after the flags the information that they want to retrieve. Mentored-by: Karthik Nayak Mentored-by Patrick Steinhardt Signed-off-by: Lucas Seiki Oshiro Signed-off-by: Junio C Hamano --- .gitignore | 1 + Makefile | 1 + builtin.h | 1 + builtin/repo-info.c | 21 +++++++++++++++++++++ git.c | 1 + meson.build | 1 + 6 files changed, 26 insertions(+) create mode 100644 builtin/repo-info.c diff --git a/.gitignore b/.gitignore index 04c444404e4..b2f3fb0047c 100644 --- a/.gitignore +++ b/.gitignore @@ -139,6 +139,7 @@ /git-repack /git-replace /git-replay +/git-repo-info /git-request-pull /git-rerere /git-reset diff --git a/Makefile b/Makefile index 70d1543b6b8..50e3a3cbccf 100644 --- a/Makefile +++ b/Makefile @@ -1308,6 +1308,7 @@ BUILTIN_OBJS += builtin/remote.o BUILTIN_OBJS += builtin/repack.o BUILTIN_OBJS += builtin/replace.o BUILTIN_OBJS += builtin/replay.o +BUILTIN_OBJS += builtin/repo-info.o BUILTIN_OBJS += builtin/rerere.o BUILTIN_OBJS += builtin/reset.o BUILTIN_OBJS += builtin/rev-list.o diff --git a/builtin.h b/builtin.h index bff13e3069b..cc6bc959621 100644 --- a/builtin.h +++ b/builtin.h @@ -216,6 +216,7 @@ int cmd_remote_ext(int argc, const char **argv, const char *prefix, struct repos int cmd_remote_fd(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_repack(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_replay(int argc, const char **argv, const char *prefix, struct repository *repo); +int cmd_repo_info(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_rerere(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_reset(int argc, const char **argv, const char *prefix, struct repository *repo); int cmd_restore(int argc, const char **argv, const char *prefix, struct repository *repo); diff --git a/builtin/repo-info.c b/builtin/repo-info.c new file mode 100644 index 00000000000..a5c43e253f6 --- /dev/null +++ b/builtin/repo-info.c @@ -0,0 +1,21 @@ +#include "builtin.h" +#include "parse-options.h" + +int cmd_repo_info(int argc, + const char **argv, + const char *prefix, + struct repository *repo UNUSED) +{ + const char *const repo_info_usage[] = { + "git repo-info", + NULL + }; + struct option options[] = { + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, repo_info_usage, + PARSE_OPT_KEEP_UNKNOWN_OPT); + + return 0; +} diff --git a/git.c b/git.c index 77c43595223..1f7b8c0da01 100644 --- a/git.c +++ b/git.c @@ -611,6 +611,7 @@ static struct cmd_struct commands[] = { { "repack", cmd_repack, RUN_SETUP }, { "replace", cmd_replace, RUN_SETUP }, { "replay", cmd_replay, RUN_SETUP }, + { "repo-info", cmd_repo_info, RUN_SETUP }, { "rerere", cmd_rerere, RUN_SETUP }, { "reset", cmd_reset, RUN_SETUP }, { "restore", cmd_restore, RUN_SETUP | NEED_WORK_TREE }, diff --git a/meson.build b/meson.build index 7fea4a34d68..06f2f647baa 100644 --- a/meson.build +++ b/meson.build @@ -645,6 +645,7 @@ builtin_sources = [ 'builtin/repack.c', 'builtin/replace.c', 'builtin/replay.c', + 'builtin/repo-info.c', 'builtin/rerere.c', 'builtin/reset.c', 'builtin/rev-list.c', -- GitLab From 84d1deab63eeab74b7fabc708968d8ca0f90c503 Mon Sep 17 00:00:00 2001 From: Lucas Seiki Oshiro Date: Thu, 19 Jun 2025 19:57:46 -0300 Subject: [PATCH 2/7] repo-info: add the --format flag Add the --format flag to the repo-info command, allowing the user to choose between output formats. Use 'json' by default. Mentored-by: Karthik Nayak Mentored-by Patrick Steinhardt Signed-off-by: Lucas Seiki Oshiro Signed-off-by: Junio C Hamano --- builtin/repo-info.c | 54 +++++++++++++++++++++++++++++++++++++++++++- t/meson.build | 1 + t/t1900-repo-info.sh | 22 ++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100755 t/t1900-repo-info.sh diff --git a/builtin/repo-info.c b/builtin/repo-info.c index a5c43e253f6..cbe1475e306 100644 --- a/builtin/repo-info.c +++ b/builtin/repo-info.c @@ -1,21 +1,73 @@ #include "builtin.h" +#include "json-writer.h" #include "parse-options.h" +enum output_format { + FORMAT_JSON +}; + +struct repo_info { + struct repository *repo; + enum output_format format; +}; + +static void repo_info_init(struct repo_info *repo_info, + struct repository *repo, + char *format) +{ + repo_info->repo = repo; + + if (format == NULL || !strcmp(format, "json")) + repo_info->format = FORMAT_JSON; + else + die("invalid format %s", format); +} + +static void repo_info_print_json(struct repo_info *repo_info UNUSED) +{ + struct json_writer jw; + + jw_init(&jw); + + jw_object_begin(&jw, 1); + jw_end(&jw); + + puts(jw.json.buf); + jw_release(&jw); +} + +static void repo_info_print(struct repo_info *repo_info) +{ + enum output_format format = repo_info->format; + + switch (format) { + case FORMAT_JSON: + repo_info_print_json(repo_info); + break; + } +} + int cmd_repo_info(int argc, const char **argv, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { const char *const repo_info_usage[] = { "git repo-info", NULL }; + struct repo_info repo_info; + char *format = NULL; struct option options[] = { + OPT_STRING(0, "format", &format, N_("format"), + N_("output format")), OPT_END() }; argc = parse_options(argc, argv, prefix, options, repo_info_usage, PARSE_OPT_KEEP_UNKNOWN_OPT); + repo_info_init(&repo_info, repo, format); + repo_info_print(&repo_info); return 0; } diff --git a/t/meson.build b/t/meson.build index 50e89e764ac..d9ecaba3b72 100644 --- a/t/meson.build +++ b/t/meson.build @@ -246,6 +246,7 @@ integration_tests = [ 't1700-split-index.sh', 't1701-racy-split-index.sh', 't1800-hook.sh', + 't1900-repo-info.sh', 't2000-conflict-when-checking-files-out.sh', 't2002-checkout-cache-u.sh', 't2003-checkout-cache-mkdir.sh', diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh new file mode 100755 index 00000000000..f634e1a2853 --- /dev/null +++ b/t/t1900-repo-info.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +test_description='test git repo-info' +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh + +parse_json () { + tr '\n' ' ' | "$PERL_PATH" "$TEST_DIRECTORY/t0019/parse_json.perl" +} + +test_lazy_prereq PERLJSON ' + perl -MJSON -e "exit 0" +' + +test_expect_success PERLJSON 'json: returns empty output with allow-empty' ' + git repo-info --format=json >output && + test_line_count = 2 output +' + +test_done -- GitLab From dbeec44bc3ea078523225f847749afc6164bf704 Mon Sep 17 00:00:00 2001 From: Lucas Seiki Oshiro Date: Thu, 19 Jun 2025 19:57:47 -0300 Subject: [PATCH 3/7] repo-info: add plaintext as an output format Add 'plaintext' as an output format of repo-info. This output format is composed zero or more key=value pairs, one per line. Mentored-by: Karthik Nayak Mentored-by Patrick Steinhardt Signed-off-by: Lucas Seiki Oshiro Signed-off-by: Junio C Hamano --- builtin/repo-info.c | 12 +++++++++++- t/t1900-repo-info.sh | 4 ++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/builtin/repo-info.c b/builtin/repo-info.c index cbe1475e306..cd7c110f474 100644 --- a/builtin/repo-info.c +++ b/builtin/repo-info.c @@ -3,7 +3,8 @@ #include "parse-options.h" enum output_format { - FORMAT_JSON + FORMAT_JSON, + FORMAT_PLAINTEXT }; struct repo_info { @@ -19,10 +20,16 @@ static void repo_info_init(struct repo_info *repo_info, if (format == NULL || !strcmp(format, "json")) repo_info->format = FORMAT_JSON; + else if (!strcmp(format, "plaintext")) + repo_info->format = FORMAT_PLAINTEXT; else die("invalid format %s", format); } +static void repo_info_print_plaintext(struct repo_info *repo_info UNUSED) +{ +} + static void repo_info_print_json(struct repo_info *repo_info UNUSED) { struct json_writer jw; @@ -44,6 +51,9 @@ static void repo_info_print(struct repo_info *repo_info) case FORMAT_JSON: repo_info_print_json(repo_info); break; + case FORMAT_PLAINTEXT: + repo_info_print_plaintext(repo_info); + break; } } diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh index f634e1a2853..998c835795f 100755 --- a/t/t1900-repo-info.sh +++ b/t/t1900-repo-info.sh @@ -18,5 +18,9 @@ test_expect_success PERLJSON 'json: returns empty output with allow-empty' ' git repo-info --format=json >output && test_line_count = 2 output ' +test_expect_success 'plaintext: returns empty output with allow-empty' ' + git repo-info --format=plaintext >output && + test_line_count = 0 output +' test_done -- GitLab From 4d04f20cc5dfb277d1b04cefd70a7ce6c03e24c4 Mon Sep 17 00:00:00 2001 From: Lucas Seiki Oshiro Date: Thu, 19 Jun 2025 19:57:48 -0300 Subject: [PATCH 4/7] repo-info: add the --allow-empty flag Add a flag --allow-empty, which will force the output data to be empty when no field is requested. Mentored-by: Karthik Nayak Mentored-by Patrick Steinhardt Signed-off-by: Lucas Seiki Oshiro Signed-off-by: Junio C Hamano --- builtin/repo-info.c | 3 +++ t/t1900-repo-info.sh | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/builtin/repo-info.c b/builtin/repo-info.c index cd7c110f474..6499be0eaea 100644 --- a/builtin/repo-info.c +++ b/builtin/repo-info.c @@ -68,9 +68,12 @@ int cmd_repo_info(int argc, }; struct repo_info repo_info; char *format = NULL; + int allow_empty = 0; struct option options[] = { OPT_STRING(0, "format", &format, N_("format"), N_("output format")), + OPT_BOOL(0, "allow-empty", &allow_empty, + "when set, it will use an empty set of fields if no field is requested"), OPT_END() }; diff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh index 998c835795f..db4a6aad17a 100755 --- a/t/t1900-repo-info.sh +++ b/t/t1900-repo-info.sh @@ -15,11 +15,12 @@ test_lazy_prereq PERLJSON ' ' test_expect_success PERLJSON 'json: returns empty output with allow-empty' ' - git repo-info --format=json >output && + git repo-info --allow-empty --format=json >output && test_line_count = 2 output ' + test_expect_success 'plaintext: returns empty output with allow-empty' ' - git repo-info --format=plaintext >output && + git repo-info --allow-empty --format=plaintext >output && test_line_count = 0 output ' -- GitLab From 3a3722e095cce5330de614fb08ad71461739d22e Mon Sep 17 00:00:00 2001 From: Lucas Seiki Oshiro Date: Thu, 19 Jun 2025 19:57:49 -0300 Subject: [PATCH 5/7] repo-info: add the field references.format 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 Mentored-by Patrick Steinhardt Signed-off-by: Lucas Seiki Oshiro Signed-off-by: Junio C Hamano --- 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 6499be0eaea..6ce3e6134f2 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 db4a6aad17a..d8625268825 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