diff --git a/.gitignore b/.gitignore index 04c444404e4ba835659089c0fdafc68bb9fccc93..b2f3fb0047c3311f2c6577521280e97723c88594 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 70d1543b6b8688bf348f03f5e9cc1690fe547249..50e3a3cbccfa3155bb4465ca37b3ac5512469b19 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 bff13e3069b4affc66f669d2d2c35c124fd86919..cc6bc959621258af6935cdefdcde009fc505110e 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 0000000000000000000000000000000000000000..4c265c05faa64c03cadda0a4fe178517786e53c9 --- /dev/null +++ b/builtin/repo-info.c @@ -0,0 +1,244 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "builtin.h" +#include "environment.h" +#include "hash.h" +#include "json-writer.h" +#include "parse-options.h" +#include "quote.h" +#include "refs.h" +#include "shallow.h" + +enum output_format { + FORMAT_JSON, + FORMAT_PLAINTEXT +}; + +enum repo_info_category { + CATEGORY_REFERENCES = 1 << 0, + CATEGORY_LAYOUT = 1 << 1 +}; + +enum repo_info_references_field { + FIELD_REFERENCES_FORMAT = 1 << 0 +}; + +enum repo_info_layout_field { + FIELD_LAYOUT_BARE = 1 << 0, + FIELD_LAYOUT_SHALLOW = 1 << 1 +}; + +struct repo_info_field { + enum repo_info_category category; + union { + enum repo_info_references_field references; + enum repo_info_layout_field layout; + } 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 + }, + { + .category = CATEGORY_LAYOUT, + .field.layout = FIELD_LAYOUT_BARE + }, + { + .category = CATEGORY_LAYOUT, + .field.layout = FIELD_LAYOUT_SHALLOW + } +}; + +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, + int allow_empty, + int argc, const char **argv) +{ + int i; + repo_info->repo = repo; + + 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); + + 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 if (!strcmp(arg, "layout.bare")) { + field->category = CATEGORY_LAYOUT; + field->field.layout = FIELD_LAYOUT_BARE; + } else if (!strcmp(arg, "layout.shallow")) { + field->category = CATEGORY_LAYOUT; + field->field.layout = FIELD_LAYOUT_SHALLOW; + } else { + die("invalid field '%s'", arg); + } + } + } +} + +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_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; + case CATEGORY_LAYOUT: + switch (field->field.layout) { + case FIELD_LAYOUT_BARE: + print_key_value("layout.bare", + is_bare_repository() ? + "true" : "false"); + break; + case FIELD_LAYOUT_SHALLOW: + print_key_value("layout.shallow", + is_repository_shallow(repo) ? + "true" : "false"); + 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; + unsigned int layout_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; + case CATEGORY_LAYOUT: + layout_fields |= field->field.layout; + 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); + } + + if (categories & CATEGORY_LAYOUT) { + jw_object_inline_begin_object(&jw, "layout"); + if (layout_fields & FIELD_LAYOUT_BARE) { + jw_object_bool(&jw, "bare", + is_bare_repository()); + } + + if (layout_fields & FIELD_LAYOUT_SHALLOW) { + jw_object_bool(&jw, "shallow", + is_repository_shallow(repo)); + } + jw_end(&jw); + } + 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; + case FORMAT_PLAINTEXT: + repo_info_print_plaintext(repo_info); + break; + } +} + +int cmd_repo_info(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + const char *const repo_info_usage[] = { + "git repo-info", + NULL + }; + 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() + }; + + argc = parse_options(argc, argv, prefix, options, repo_info_usage, + PARSE_OPT_KEEP_UNKNOWN_OPT); + 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/git.c b/git.c index 77c435952232f60435bfd96697e304ee1761f565..1f7b8c0da01d39fac3a741aebde118d3cadedf6f 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 7fea4a34d684c579d389d1d3be3a5c1aba021ac6..06f2f647baab9134091eaf980c46ae78ff72174e 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', diff --git a/t/meson.build b/t/meson.build index 50e89e764ac3b414ce49694270beb494d88ea22e..d9ecaba3b72ffca3aa791c8af203f91a8d78dc67 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 0000000000000000000000000000000000000000..27f0af4c53a4cd18230ce957492cd02db1c8298e --- /dev/null +++ b/t/t1900-repo-info.sh @@ -0,0 +1,105 @@ +#!/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 + +DEFAULT_NUMBER_OF_FIELDS=3 + +parse_json () { + tr '\n' ' ' | "$PERL_PATH" "$TEST_DIRECTORY/t0019/parse_json.perl" +} + +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