Skip to content

Commit 0613476

Browse files
committed
Merge branch 'lo/repo-info' into seen
A new subcommand "git repo-info" gives users a way to grab various repository characteristics. * lo/repo-info: repo-info: add field layout.shallow repo-info: add field layout.bare repo-info: add the field references.format repo-info: add the --allow-empty flag repo-info: add plaintext as an output format repo-info: add the --format flag repo-info: declare the repo-info command
2 parents 34a8671 + 7b6f18a commit 0613476

File tree

8 files changed

+355
-0
lines changed

8 files changed

+355
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@
139139
/git-repack
140140
/git-replace
141141
/git-replay
142+
/git-repo-info
142143
/git-request-pull
143144
/git-rerere
144145
/git-reset

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1308,6 +1308,7 @@ BUILTIN_OBJS += builtin/remote.o
13081308
BUILTIN_OBJS += builtin/repack.o
13091309
BUILTIN_OBJS += builtin/replace.o
13101310
BUILTIN_OBJS += builtin/replay.o
1311+
BUILTIN_OBJS += builtin/repo-info.o
13111312
BUILTIN_OBJS += builtin/rerere.o
13121313
BUILTIN_OBJS += builtin/reset.o
13131314
BUILTIN_OBJS += builtin/rev-list.o

builtin.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ int cmd_remote_ext(int argc, const char **argv, const char *prefix, struct repos
216216
int cmd_remote_fd(int argc, const char **argv, const char *prefix, struct repository *repo);
217217
int cmd_repack(int argc, const char **argv, const char *prefix, struct repository *repo);
218218
int cmd_replay(int argc, const char **argv, const char *prefix, struct repository *repo);
219+
int cmd_repo_info(int argc, const char **argv, const char *prefix, struct repository *repo);
219220
int cmd_rerere(int argc, const char **argv, const char *prefix, struct repository *repo);
220221
int cmd_reset(int argc, const char **argv, const char *prefix, struct repository *repo);
221222
int cmd_restore(int argc, const char **argv, const char *prefix, struct repository *repo);

builtin/repo-info.c

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
#define USE_THE_REPOSITORY_VARIABLE
2+
3+
#include "builtin.h"
4+
#include "environment.h"
5+
#include "hash.h"
6+
#include "json-writer.h"
7+
#include "parse-options.h"
8+
#include "quote.h"
9+
#include "refs.h"
10+
#include "shallow.h"
11+
12+
enum output_format {
13+
FORMAT_JSON,
14+
FORMAT_PLAINTEXT
15+
};
16+
17+
enum repo_info_category {
18+
CATEGORY_REFERENCES = 1 << 0,
19+
CATEGORY_LAYOUT = 1 << 1
20+
};
21+
22+
enum repo_info_references_field {
23+
FIELD_REFERENCES_FORMAT = 1 << 0
24+
};
25+
26+
enum repo_info_layout_field {
27+
FIELD_LAYOUT_BARE = 1 << 0,
28+
FIELD_LAYOUT_SHALLOW = 1 << 1
29+
};
30+
31+
struct repo_info_field {
32+
enum repo_info_category category;
33+
union {
34+
enum repo_info_references_field references;
35+
enum repo_info_layout_field layout;
36+
} field;
37+
};
38+
39+
struct repo_info {
40+
struct repository *repo;
41+
enum output_format format;
42+
int n_fields;
43+
struct repo_info_field *fields;
44+
};
45+
46+
static struct repo_info_field default_fields[] = {
47+
{
48+
.category = CATEGORY_REFERENCES,
49+
.field.references = FIELD_REFERENCES_FORMAT
50+
},
51+
{
52+
.category = CATEGORY_LAYOUT,
53+
.field.layout = FIELD_LAYOUT_BARE
54+
},
55+
{
56+
.category = CATEGORY_LAYOUT,
57+
.field.layout = FIELD_LAYOUT_SHALLOW
58+
}
59+
};
60+
61+
static void print_key_value(const char *key, const char *value) {
62+
printf("%s=", key);
63+
quote_c_style(value, NULL, stdout, 0);
64+
putchar('\n');
65+
}
66+
67+
static void repo_info_init(struct repo_info *repo_info,
68+
struct repository *repo,
69+
char *format,
70+
int allow_empty,
71+
int argc, const char **argv)
72+
{
73+
int i;
74+
repo_info->repo = repo;
75+
76+
if (format == NULL || !strcmp(format, "json"))
77+
repo_info->format = FORMAT_JSON;
78+
else if (!strcmp(format, "plaintext"))
79+
repo_info->format = FORMAT_PLAINTEXT;
80+
else
81+
die("invalid format %s", format);
82+
83+
if (argc == 0 && !allow_empty) {
84+
repo_info->n_fields = ARRAY_SIZE(default_fields);
85+
repo_info->fields = default_fields;
86+
} else {
87+
repo_info->n_fields = argc;
88+
ALLOC_ARRAY(repo_info->fields, argc);
89+
90+
for (i = 0; i < argc; i++) {
91+
const char *arg = argv[i];
92+
struct repo_info_field *field = repo_info->fields + i;
93+
94+
if (!strcmp(arg, "references.format")) {
95+
field->category = CATEGORY_REFERENCES;
96+
field->field.references = FIELD_REFERENCES_FORMAT;
97+
} else if (!strcmp(arg, "layout.bare")) {
98+
field->category = CATEGORY_LAYOUT;
99+
field->field.layout = FIELD_LAYOUT_BARE;
100+
} else if (!strcmp(arg, "layout.shallow")) {
101+
field->category = CATEGORY_LAYOUT;
102+
field->field.layout = FIELD_LAYOUT_SHALLOW;
103+
} else {
104+
die("invalid field '%s'", arg);
105+
}
106+
}
107+
}
108+
}
109+
110+
static void repo_info_release(struct repo_info *repo_info)
111+
{
112+
if (repo_info->fields != default_fields) free(repo_info->fields);
113+
}
114+
115+
static void repo_info_print_plaintext(struct repo_info *repo_info) {
116+
struct repository *repo = repo_info->repo;
117+
int i;
118+
for (i = 0; i < repo_info->n_fields; i++) {
119+
struct repo_info_field *field = &repo_info->fields[i];
120+
switch (field->category) {
121+
case CATEGORY_REFERENCES:
122+
switch (field->field.references) {
123+
case FIELD_REFERENCES_FORMAT:
124+
print_key_value("references.format",
125+
ref_storage_format_to_name(
126+
repo->ref_storage_format));
127+
break;
128+
}
129+
break;
130+
case CATEGORY_LAYOUT:
131+
switch (field->field.layout) {
132+
case FIELD_LAYOUT_BARE:
133+
print_key_value("layout.bare",
134+
is_bare_repository() ?
135+
"true" : "false");
136+
break;
137+
case FIELD_LAYOUT_SHALLOW:
138+
print_key_value("layout.shallow",
139+
is_repository_shallow(repo) ?
140+
"true" : "false");
141+
break;
142+
}
143+
break;
144+
}
145+
}
146+
}
147+
148+
static void repo_info_print_json(struct repo_info *repo_info)
149+
{
150+
struct json_writer jw;
151+
int i;
152+
unsigned int categories = 0;
153+
unsigned int references_fields = 0;
154+
unsigned int layout_fields = 0;
155+
struct repository *repo = repo_info->repo;
156+
157+
for (i = 0; i < repo_info->n_fields; i++) {
158+
struct repo_info_field *field = repo_info->fields + i;
159+
categories |= field->category;
160+
switch (field->category) {
161+
case CATEGORY_REFERENCES:
162+
references_fields |= field->field.references;
163+
break;
164+
case CATEGORY_LAYOUT:
165+
layout_fields |= field->field.layout;
166+
break;
167+
}
168+
}
169+
170+
jw_init(&jw);
171+
172+
jw_object_begin(&jw, 1);
173+
174+
if (categories & CATEGORY_REFERENCES) {
175+
jw_object_inline_begin_object(&jw, "references");
176+
if (references_fields & FIELD_REFERENCES_FORMAT) {
177+
const char *format_name = ref_storage_format_to_name(
178+
repo->ref_storage_format);
179+
jw_object_string(&jw, "format", format_name);
180+
}
181+
jw_end(&jw);
182+
}
183+
184+
if (categories & CATEGORY_LAYOUT) {
185+
jw_object_inline_begin_object(&jw, "layout");
186+
if (layout_fields & FIELD_LAYOUT_BARE) {
187+
jw_object_bool(&jw, "bare",
188+
is_bare_repository());
189+
}
190+
191+
if (layout_fields & FIELD_LAYOUT_SHALLOW) {
192+
jw_object_bool(&jw, "shallow",
193+
is_repository_shallow(repo));
194+
}
195+
jw_end(&jw);
196+
}
197+
jw_end(&jw);
198+
199+
puts(jw.json.buf);
200+
jw_release(&jw);
201+
}
202+
203+
static void repo_info_print(struct repo_info *repo_info)
204+
{
205+
enum output_format format = repo_info->format;
206+
207+
switch (format) {
208+
case FORMAT_JSON:
209+
repo_info_print_json(repo_info);
210+
break;
211+
case FORMAT_PLAINTEXT:
212+
repo_info_print_plaintext(repo_info);
213+
break;
214+
}
215+
}
216+
217+
int cmd_repo_info(int argc,
218+
const char **argv,
219+
const char *prefix,
220+
struct repository *repo)
221+
{
222+
const char *const repo_info_usage[] = {
223+
"git repo-info",
224+
NULL
225+
};
226+
struct repo_info repo_info;
227+
char *format = NULL;
228+
int allow_empty = 0;
229+
struct option options[] = {
230+
OPT_STRING(0, "format", &format, N_("format"),
231+
N_("output format")),
232+
OPT_BOOL(0, "allow-empty", &allow_empty,
233+
"when set, it will use an empty set of fields if no field is requested"),
234+
OPT_END()
235+
};
236+
237+
argc = parse_options(argc, argv, prefix, options, repo_info_usage,
238+
PARSE_OPT_KEEP_UNKNOWN_OPT);
239+
repo_info_init(&repo_info, repo, format, allow_empty, argc, argv);
240+
repo_info_print(&repo_info);
241+
repo_info_release(&repo_info);
242+
243+
return 0;
244+
}

git.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -611,6 +611,7 @@ static struct cmd_struct commands[] = {
611611
{ "repack", cmd_repack, RUN_SETUP },
612612
{ "replace", cmd_replace, RUN_SETUP },
613613
{ "replay", cmd_replay, RUN_SETUP },
614+
{ "repo-info", cmd_repo_info, RUN_SETUP },
614615
{ "rerere", cmd_rerere, RUN_SETUP },
615616
{ "reset", cmd_reset, RUN_SETUP },
616617
{ "restore", cmd_restore, RUN_SETUP | NEED_WORK_TREE },

meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ builtin_sources = [
645645
'builtin/repack.c',
646646
'builtin/replace.c',
647647
'builtin/replay.c',
648+
'builtin/repo-info.c',
648649
'builtin/rerere.c',
649650
'builtin/reset.c',
650651
'builtin/rev-list.c',

t/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ integration_tests = [
227227
't1700-split-index.sh',
228228
't1701-racy-split-index.sh',
229229
't1800-hook.sh',
230+
't1900-repo-info.sh',
230231
't2000-conflict-when-checking-files-out.sh',
231232
't2002-checkout-cache-u.sh',
232233
't2003-checkout-cache-mkdir.sh',

t/t1900-repo-info.sh

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#!/bin/sh
2+
3+
test_description='test git repo-info'
4+
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
5+
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
6+
7+
. ./test-lib.sh
8+
9+
DEFAULT_NUMBER_OF_FIELDS=3
10+
11+
parse_json () {
12+
tr '\n' ' ' | "$PERL_PATH" "$TEST_DIRECTORY/t0019/parse_json.perl"
13+
}
14+
15+
test_lazy_prereq PERLJSON '
16+
perl -MJSON -e "exit 0"
17+
'
18+
19+
# Test if a field is correctly returned in both plaintext and json formats.
20+
#
21+
# Usage: test_repo_info <label> <init command> <key> <expected value>
22+
#
23+
# Arguments:
24+
# label: the label of the test
25+
# init command: a command that creates a repository called 'repo', configured
26+
# accordingly to what is being tested
27+
# key: the key of the field that is being tested
28+
# expected value: the value that the field should contain
29+
test_repo_info () {
30+
label=$1
31+
init_command=$2
32+
key=$3
33+
expected_value=$4
34+
35+
test_expect_success PERLJSON "json: $label" "
36+
test_when_finished 'rm -rf repo' &&
37+
'$SHELL_PATH' -c '$init_command' &&
38+
cd repo &&
39+
echo '$expected_value' >expect &&
40+
git repo-info '$key' >output &&
41+
cat output | parse_json >parsed &&
42+
grep -F 'row[0].$key' parsed | cut -d ' ' -f 2 >value &&
43+
cat value | sed 's/^0$/false/' | sed 's/^1$/true/' >actual &&
44+
test_cmp expect actual
45+
"
46+
47+
test_expect_success "plaintext: $label" "
48+
test_when_finished 'rm -rf repo' &&
49+
'$SHELL_PATH' -c '$init_command' &&
50+
cd repo &&
51+
echo '$expected_value' >expect &&
52+
git repo-info --format=plaintext '$key' >output &&
53+
cat output | cut -d '=' -f 2 >actual &&
54+
test_cmp expect actual
55+
"
56+
}
57+
58+
test_expect_success PERLJSON 'json: returns empty output with allow-empty' '
59+
git repo-info --allow-empty --format=json >output &&
60+
test_line_count = 2 output
61+
'
62+
63+
test_expect_success 'plaintext: returns empty output with allow-empty' '
64+
git repo-info --allow-empty --format=plaintext >output &&
65+
test_line_count = 0 output
66+
'
67+
68+
test_repo_info 'ref format files is retrieved correctly' '
69+
git init --ref-format=files repo' 'references.format' 'files'
70+
71+
test_repo_info 'ref format reftable is retrieved correctly' '
72+
git init --ref-format=reftable repo' 'references.format' 'reftable'
73+
74+
test_repo_info 'bare repository = false is retrieved correctly' '
75+
git init repo' 'layout.bare' 'false'
76+
77+
test_repo_info 'bare repository = true is retrieved correctly' '
78+
git init --bare repo' 'layout.bare' 'true'
79+
80+
test_repo_info 'shallow repository = false is retrieved correctly' '
81+
git init repo' 'layout.shallow' 'false'
82+
83+
test_repo_info 'shallow repository = true is retrieved correctly' '
84+
git init remote &&
85+
cd remote &&
86+
echo x >x &&
87+
git add x &&
88+
git commit -m x &&
89+
cd .. &&
90+
git clone --depth 1 "file://$PWD/remote" repo &&
91+
rm -rf remote
92+
' 'layout.shallow' 'true'
93+
94+
test_expect_success 'plaintext: output all default fields' "
95+
git repo-info --format=plaintext >actual &&
96+
test_line_count = $DEFAULT_NUMBER_OF_FIELDS actual
97+
"
98+
99+
test_expect_success PERLJSON 'json: output all default fields' "
100+
git repo-info --format=json > output &&
101+
cat output | parse_json | grep '.*\..*\..*' >actual &&
102+
test_line_count = $DEFAULT_NUMBER_OF_FIELDS actual
103+
"
104+
105+
test_done

0 commit comments

Comments
 (0)