summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2025-08-20 17:18:35 -0700
committerJunio C Hamano <gitster@pobox.com>2025-08-20 17:18:35 -0700
commitc8f660a7cab5ab3b9c57677e66cb41bb57ee0114 (patch)
tree3696adcee033cdd66d4b1a355b85c0bea10b887a
parentc44beea485f0f2feaf460e2ac87fdd5608d63cf0 (diff)
parenta81224d12818e94a2e3c257ee2e5b0f3169da12b (diff)
Merge branch 'lo/repo-info' into lo/repo-info-step-2
* lo/repo-info: repo: add the --format flag repo: add the field layout.shallow repo: add the field layout.bare repo: add the field references.format repo: declare the repo command
-rw-r--r--.gitignore1
-rw-r--r--Documentation/git-repo.adoc84
-rw-r--r--Documentation/meson.build1
-rw-r--r--Makefile1
-rw-r--r--builtin.h1
-rw-r--r--builtin/repo.c150
-rw-r--r--command-list.txt1
-rw-r--r--git.c1
-rw-r--r--meson.build1
-rw-r--r--t/meson.build1
-rwxr-xr-xt/t1900-repo.sh95
11 files changed, 337 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 04c444404e..1803023427 100644
--- a/.gitignore
+++ b/.gitignore
@@ -139,6 +139,7 @@
/git-repack
/git-replace
/git-replay
+/git-repo
/git-request-pull
/git-rerere
/git-reset
diff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc
new file mode 100644
index 0000000000..2870828d93
--- /dev/null
+++ b/Documentation/git-repo.adoc
@@ -0,0 +1,84 @@
+git-repo(1)
+===========
+
+NAME
+----
+git-repo - Retrieve information about the repository
+
+SYNOPSIS
+--------
+[synopsis]
+git repo info [--format=(keyvalue|nul)] [<key>...]
+
+DESCRIPTION
+-----------
+Retrieve information about the repository.
+
+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
+
+COMMANDS
+--------
+`info [--format=(keyvalue|nul)] [<key>...]`::
+ Retrieve metadata-related information about the current repository. Only
+ the requested data will be returned based on their keys (see "INFO KEYS"
+ section below).
++
+The values are returned in the same order in which their respective keys were
+requested.
++
+The output format can be chosen through the flag `--format`. Two formats are
+supported:
++
+`keyvalue`:::
+ output key-value pairs one per line using the `=` character as
+ the delimiter between the key and the value. Values containing "unusual"
+ characters are quoted as explained for the configuration variable
+ `core.quotePath` (see linkgit:git-config[1]). This is the default.
+
+`nul`:::
+ similar to `keyvalue`, but using a newline character as the delimiter
+ between the key and the value and using a NUL character after each value.
+ This format is better suited for being parsed by another applications than
+ `keyvalue`. Unlike in the `keyvalue` format, the values are never quoted.
+
+INFO KEYS
+---------
+In order to obtain a set of values from `git repo info`, you should provide
+the keys that identify them. Here's a list of the available keys and the
+values that they return:
+
+`layout.bare`::
+ `true` if this is a bare repository, otherwise `false`.
+
+`layout.shallow`::
+ `true` if this is a shallow repository, otherwise `false`.
+
+`references.format`::
+ The reference storage format. The valid values are:
++
+include::ref-storage-format.adoc[]
+
+EXAMPLES
+--------
+
+* Retrieves the reference format of the current repository:
++
+------------
+git repo info references.format
+------------
++
+
+* Retrieves whether the current repository is bare and whether it is shallow
+using the `nul` format:
++
+------------
+git repo info --format=nul layout.bare layout.shallow
+------------
+
+SEE ALSO
+--------
+linkgit:git-rev-parse[1]
+
+GIT
+---
+Part of the linkgit:git[1] suite
diff --git a/Documentation/meson.build b/Documentation/meson.build
index 4404c623f0..41f43e0336 100644
--- a/Documentation/meson.build
+++ b/Documentation/meson.build
@@ -116,6 +116,7 @@ manpages = {
'git-repack.adoc' : 1,
'git-replace.adoc' : 1,
'git-replay.adoc' : 1,
+ 'git-repo.adoc' : 1,
'git-request-pull.adoc' : 1,
'git-rerere.adoc' : 1,
'git-reset.adoc' : 1,
diff --git a/Makefile b/Makefile
index e11340c1ae..ec7ac58980 100644
--- a/Makefile
+++ b/Makefile
@@ -1306,6 +1306,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.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 bff13e3069..e6458e6fb9 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(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.c b/builtin/repo.c
new file mode 100644
index 0000000000..8c6e7f42ab
--- /dev/null
+++ b/builtin/repo.c
@@ -0,0 +1,150 @@
+#define USE_THE_REPOSITORY_VARIABLE
+
+#include "builtin.h"
+#include "environment.h"
+#include "parse-options.h"
+#include "quote.h"
+#include "refs.h"
+#include "strbuf.h"
+#include "shallow.h"
+
+static const char *const repo_usage[] = {
+ "git repo info [--format=(keyvalue|nul)] [<key>...]",
+ NULL
+};
+
+typedef int get_value_fn(struct repository *repo, struct strbuf *buf);
+
+enum output_format {
+ FORMAT_KEYVALUE,
+ FORMAT_NUL_TERMINATED,
+};
+
+struct field {
+ const char *key;
+ get_value_fn *get_value;
+};
+
+static int get_layout_bare(struct repository *repo UNUSED, struct strbuf *buf)
+{
+ strbuf_addstr(buf, is_bare_repository() ? "true" : "false");
+ return 0;
+}
+
+static int get_layout_shallow(struct repository *repo, struct strbuf *buf)
+{
+ strbuf_addstr(buf,
+ is_repository_shallow(repo) ? "true" : "false");
+ return 0;
+}
+
+static int get_references_format(struct repository *repo, struct strbuf *buf)
+{
+ strbuf_addstr(buf,
+ ref_storage_format_to_name(repo->ref_storage_format));
+ return 0;
+}
+
+/* repo_info_fields keys must be in lexicographical order */
+static const struct field repo_info_fields[] = {
+ { "layout.bare", get_layout_bare },
+ { "layout.shallow", get_layout_shallow },
+ { "references.format", get_references_format },
+};
+
+static int repo_info_fields_cmp(const void *va, const void *vb)
+{
+ const struct field *a = va;
+ const struct field *b = vb;
+
+ return strcmp(a->key, b->key);
+}
+
+static get_value_fn *get_value_fn_for_key(const char *key)
+{
+ const struct field search_key = { key, NULL };
+ const struct field *found = bsearch(&search_key, repo_info_fields,
+ ARRAY_SIZE(repo_info_fields),
+ sizeof(*found),
+ repo_info_fields_cmp);
+ return found ? found->get_value : NULL;
+}
+
+static int print_fields(int argc, const char **argv,
+ struct repository *repo,
+ enum output_format format)
+{
+ int ret = 0;
+ struct strbuf valbuf = STRBUF_INIT;
+ struct strbuf quotbuf = STRBUF_INIT;
+
+ for (int i = 0; i < argc; i++) {
+ get_value_fn *get_value;
+ const char *key = argv[i];
+
+ get_value = get_value_fn_for_key(key);
+
+ if (!get_value) {
+ ret = error(_("key '%s' not found"), key);
+ continue;
+ }
+
+ strbuf_reset(&valbuf);
+ strbuf_reset(&quotbuf);
+
+ get_value(repo, &valbuf);
+
+ switch (format) {
+ case FORMAT_KEYVALUE:
+ quote_c_style(valbuf.buf, &quotbuf, NULL, 0);
+ printf("%s=%s\n", key, quotbuf.buf);
+ break;
+ case FORMAT_NUL_TERMINATED:
+ printf("%s\n%s%c", key, valbuf.buf, '\0');
+ break;
+ default:
+ BUG("not a valid output format: %d", format);
+ }
+ }
+
+ strbuf_release(&valbuf);
+ strbuf_release(&quotbuf);
+ return ret;
+}
+
+static int repo_info(int argc, const char **argv, const char *prefix,
+ struct repository *repo)
+{
+ const char *format_str = "keyvalue";
+ enum output_format format;
+ struct option options[] = {
+ OPT_STRING(0, "format", &format_str, N_("format"),
+ N_("output format")),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options, repo_usage, 0);
+
+ if (!strcmp(format_str, "keyvalue"))
+ format = FORMAT_KEYVALUE;
+ else if (!strcmp(format_str, "nul"))
+ format = FORMAT_NUL_TERMINATED;
+ else
+ die(_("invalid format '%s'"), format_str);
+
+ return print_fields(argc, argv, repo, format);
+}
+
+int cmd_repo(int argc, const char **argv, const char *prefix,
+ struct repository *repo)
+{
+ parse_opt_subcommand_fn *fn = NULL;
+ struct option options[] = {
+ OPT_SUBCOMMAND("info", &fn, repo_info),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options, repo_usage, 0);
+
+ return fn(argc, argv, prefix, repo);
+}
diff --git a/command-list.txt b/command-list.txt
index b7ade3ab9f..1b0bdee00d 100644
--- a/command-list.txt
+++ b/command-list.txt
@@ -164,6 +164,7 @@ git-remote ancillarymanipulators complete
git-repack ancillarymanipulators complete
git-replace ancillarymanipulators complete
git-replay plumbingmanipulators
+git-repo plumbinginterrogators
git-request-pull foreignscminterface complete
git-rerere ancillaryinterrogators
git-reset mainporcelain history
diff --git a/git.c b/git.c
index 83eac0aeab..d4ff4d5517 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", cmd_repo, 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 5dd299b496..e8ec0eca16 100644
--- a/meson.build
+++ b/meson.build
@@ -645,6 +645,7 @@ builtin_sources = [
'builtin/repack.c',
'builtin/replace.c',
'builtin/replay.c',
+ 'builtin/repo.c',
'builtin/rerere.c',
'builtin/reset.c',
'builtin/rev-list.c',
diff --git a/t/meson.build b/t/meson.build
index bbeba1a8d5..252dbbc031 100644
--- a/t/meson.build
+++ b/t/meson.build
@@ -230,6 +230,7 @@ integration_tests = [
't1700-split-index.sh',
't1701-racy-split-index.sh',
't1800-hook.sh',
+ 't1900-repo.sh',
't2000-conflict-when-checking-files-out.sh',
't2002-checkout-cache-u.sh',
't2003-checkout-cache-mkdir.sh',
diff --git a/t/t1900-repo.sh b/t/t1900-repo.sh
new file mode 100755
index 0000000000..a69c715357
--- /dev/null
+++ b/t/t1900-repo.sh
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+test_description='test git repo-info'
+
+. ./test-lib.sh
+
+# Test whether a key-value pair is correctly returned
+#
+# Usage: test_repo_info <label> <init command> <repo_name> <key> <expected value>
+#
+# Arguments:
+# label: the label of the test
+# init_command: a command which creates a repository
+# repo_name: the name of the repository that will be created in init_command
+# 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
+ repo_name=$3
+ key=$4
+ expected_value=$5
+
+ test_expect_success "setup: $label" '
+ eval "$init_command $repo_name"
+ '
+
+ test_expect_success "keyvalue: $label" '
+ echo "$key=$expected_value" > expect &&
+ git -C "$repo_name" repo info "$key" >actual &&
+ test_cmp expect actual
+ '
+
+ test_expect_success "nul: $label" '
+ printf "%s\n%s\0" "$key" "$expected_value" >expect &&
+ git -C "$repo_name" repo info --format=nul "$key" >actual &&
+ test_cmp_bin expect actual
+ '
+}
+
+test_repo_info 'ref format files is retrieved correctly' \
+ 'git init --ref-format=files' 'format-files' 'references.format' 'files'
+
+test_repo_info 'ref format reftable is retrieved correctly' \
+ 'git init --ref-format=reftable' 'format-reftable' 'references.format' 'reftable'
+
+test_repo_info 'bare repository = false is retrieved correctly' \
+ 'git init' 'nonbare' 'layout.bare' 'false'
+
+test_repo_info 'bare repository = true is retrieved correctly' \
+ 'git init --bare' 'bare' 'layout.bare' 'true'
+
+test_repo_info 'shallow repository = false is retrieved correctly' \
+ 'git init' 'nonshallow' 'layout.shallow' 'false'
+
+test_expect_success 'setup remote' '
+ git init remote &&
+ echo x >remote/x &&
+ git -C remote add x &&
+ git -C remote commit -m x
+'
+
+test_repo_info 'shallow repository = true is retrieved correctly' \
+ 'git clone --depth 1 "file://$PWD/remote"' 'shallow' 'layout.shallow' 'true'
+
+test_expect_success 'values returned in order requested' '
+ cat >expect <<-\EOF &&
+ layout.bare=false
+ references.format=files
+ layout.bare=false
+ EOF
+ git init --ref-format=files ordered &&
+ git -C ordered repo info layout.bare references.format layout.bare >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git-repo-info fails if an invalid key is requested' '
+ echo "error: key ${SQ}foo${SQ} not found" >expect &&
+ test_must_fail git repo info foo 2>actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git-repo-info outputs data even if there is an invalid field' '
+ echo "references.format=$(test_detect_ref_format)" >expect &&
+ test_must_fail git repo info foo references.format bar >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'git-repo-info aborts when requesting an invalid format' '
+ echo "fatal: invalid format ${SQ}foo${SQ}" >expect &&
+ test_must_fail git repo info --format=foo 2>actual &&
+ test_cmp expect actual
+'
+
+test_done