Raw
1 #define USE_THE_REPOSITORY_VARIABLE
2
3 #include "builtin.h"
4 #include "commit.h"
5 #include "environment.h"
6 #include "hash.h"
7 #include "hex.h"
8 #include "odb.h"
9 #include "parse-options.h"
10 #include "path.h"
11 #include "path-walk.h"
12 #include "progress.h"
13 #include "quote.h"
14 #include "ref-filter.h"
15 #include "refs.h"
16 #include "revision.h"
17 #include "setup.h"
18 #include "strbuf.h"
19 #include "string-list.h"
20 #include "shallow.h"
21 #include "submodule.h"
22 #include "tree.h"
23 #include "tree-walk.h"
24 #include "utf8.h"
25
26 #define REPO_INFO_USAGE \
27 "git repo info [--format=(lines|nul) | -z] [--all | <key>...]", \
28 "git repo info --keys [--format=(lines|nul) | -z]"
29
30 #define REPO_STRUCTURE_USAGE \
31 "git repo structure [--format=(table|lines|nul) | -z]"
32
33 static const char *const repo_usage[] = {
34 REPO_INFO_USAGE,
35 REPO_STRUCTURE_USAGE,
36 NULL,
37 };
38
39 static const char *const repo_info_usage[] = {
40 REPO_INFO_USAGE,
41 NULL,
42 };
43
44 static const char *const repo_structure_usage[] = {
45 REPO_STRUCTURE_USAGE,
46 NULL,
47 };
48
49 typedef int get_value_fn(struct repository *repo, struct strbuf *buf);
50
51 enum output_format {
52 FORMAT_TABLE,
53 FORMAT_NEWLINE_TERMINATED,
54 FORMAT_NUL_TERMINATED,
55 };
56
57 struct repo_info_field {
58 const char *key;
59 get_value_fn *get_value;
60 };
61
62 static int get_layout_bare(struct repository *repo UNUSED, struct strbuf *buf)
63 {
64 strbuf_addstr(buf, is_bare_repository(the_repository) ? "true" : "false");
65 return 0;
66 }
67
68 static int get_layout_shallow(struct repository *repo, struct strbuf *buf)
69 {
70 strbuf_addstr(buf,
71 is_repository_shallow(repo) ? "true" : "false");
72 return 0;
73 }
74
75 static int get_object_format(struct repository *repo, struct strbuf *buf)
76 {
77 strbuf_addstr(buf, repo->hash_algo->name);
78 return 0;
79 }
80
81 static int get_path_commondir_absolute(struct repository *repo, struct strbuf *buf)
82 {
83 const char *common_dir = repo_get_common_dir(repo);
84
85 if (!common_dir)
86 return error(_("unable to get common directory"));
87
88 format_path(buf, common_dir, repo->prefix, PATH_FORMAT_CANONICAL);
89 return 0;
90 }
91
92 static int get_path_commondir_relative(struct repository *repo, struct strbuf *buf)
93 {
94 const char *common_dir = repo_get_common_dir(repo);
95
96 if (!common_dir)
97 return error(_("unable to get common directory"));
98
99 format_path(buf, common_dir, repo->prefix, PATH_FORMAT_RELATIVE);
100 return 0;
101 }
102
103 static int get_path_git_prefix(struct repository *repo, struct strbuf *buf)
104 {
105 /*
106 * repo->prefix is NULL if we are at the working tree root.
107 * We add an empty string to ensure the buffer is cleanly initialized.
108 */
109 strbuf_addstr(buf, repo->prefix ? repo->prefix : "");
110 return 0;
111 }
112
113 static int get_path_gitdir_absolute(struct repository *repo, struct strbuf *buf)
114 {
115 const char *git_dir = repo_get_git_dir(repo);
116
117 if (!git_dir)
118 return error(_("unable to get git directory"));
119
120 format_path(buf, git_dir, repo->prefix, PATH_FORMAT_CANONICAL);
121 return 0;
122 }
123
124 static int get_path_gitdir_relative(struct repository *repo, struct strbuf *buf)
125 {
126 const char *git_dir = repo_get_git_dir(repo);
127
128 if (!git_dir)
129 return error(_("unable to get git directory"));
130
131 format_path(buf, git_dir, repo->prefix, PATH_FORMAT_RELATIVE);
132 return 0;
133 }
134
135 static int get_path_grafts_absolute(struct repository *repo, struct strbuf *buf)
136 {
137 const char *graft_file = repo_get_graft_file(repo);
138
139 if (!graft_file)
140 return error(_("unable to get graft file"));
141
142 format_path(buf, graft_file, repo->prefix, PATH_FORMAT_CANONICAL);
143 return 0;
144 }
145
146 static int get_path_grafts_relative(struct repository *repo, struct strbuf *buf)
147 {
148 const char *graft_file = repo_get_graft_file(repo);
149
150 if (!graft_file)
151 return error(_("unable to get graft file"));
152
153 format_path(buf, graft_file, repo->prefix, PATH_FORMAT_RELATIVE);
154 return 0;
155 }
156
157 static int get_path_hooks_absolute(struct repository *repo, struct strbuf *buf)
158 {
159 struct strbuf hooks_path = STRBUF_INIT;
160
161 repo_git_path_replace(repo, &hooks_path, "hooks");
162 format_path(buf, hooks_path.buf, repo->prefix, PATH_FORMAT_CANONICAL);
163 strbuf_release(&hooks_path);
164 return 0;
165 }
166
167 static int get_path_hooks_relative(struct repository *repo, struct strbuf *buf)
168 {
169 struct strbuf hooks_path = STRBUF_INIT;
170
171 repo_git_path_replace(repo, &hooks_path, "hooks");
172 format_path(buf, hooks_path.buf, repo->prefix, PATH_FORMAT_RELATIVE);
173 strbuf_release(&hooks_path);
174 return 0;
175 }
176
177 static int get_path_index_absolute(struct repository *repo, struct strbuf *buf)
178 {
179 const char *index_file = repo_get_index_file(repo);
180
181 if (!index_file)
182 return error(_("unable to get index file"));
183
184 format_path(buf, index_file, repo->prefix, PATH_FORMAT_CANONICAL);
185 return 0;
186 }
187
188 static int get_path_index_relative(struct repository *repo, struct strbuf *buf)
189 {
190 const char *index_file = repo_get_index_file(repo);
191
192 if (!index_file)
193 return error(_("unable to get index file"));
194
195 format_path(buf, index_file, repo->prefix, PATH_FORMAT_RELATIVE);
196 return 0;
197 }
198
199 static int get_path_objects_absolute(struct repository *repo, struct strbuf *buf)
200 {
201 const char *obj_dir = repo_get_object_directory(repo);
202
203 if (!obj_dir)
204 return error(_("unable to get object directory"));
205
206 format_path(buf, obj_dir, repo->prefix, PATH_FORMAT_CANONICAL);
207 return 0;
208 }
209
210 static int get_path_objects_relative(struct repository *repo, struct strbuf *buf)
211 {
212 const char *obj_dir = repo_get_object_directory(repo);
213
214 if (!obj_dir)
215 return error(_("unable to get object directory"));
216
217 format_path(buf, obj_dir, repo->prefix, PATH_FORMAT_RELATIVE);
218 return 0;
219 }
220
221 static int get_path_superproject_absolute(struct repository *repo, struct strbuf *buf)
222 {
223 struct strbuf superproject = STRBUF_INIT;
224
225 if (!get_superproject_working_tree(&superproject)) {
226 strbuf_release(&superproject);
227 strbuf_addstr(buf, "");
228 return 0;
229 }
230
231 format_path(buf, superproject.buf, repo->prefix, PATH_FORMAT_CANONICAL);
232 strbuf_release(&superproject);
233 return 0;
234 }
235
236 static int get_path_superproject_relative(struct repository *repo, struct strbuf *buf)
237 {
238 struct strbuf superproject = STRBUF_INIT;
239
240 if (!get_superproject_working_tree(&superproject)) {
241 strbuf_release(&superproject);
242 strbuf_addstr(buf, "");
243 return 0;
244 }
245
246 format_path(buf, superproject.buf, repo->prefix, PATH_FORMAT_RELATIVE);
247 strbuf_release(&superproject);
248 return 0;
249 }
250
251 static int get_path_toplevel_absolute(struct repository *repo, struct strbuf *buf)
252 {
253 const char *work_tree = repo_get_work_tree(repo);
254
255 if (!work_tree) {
256 strbuf_addstr(buf, "");
257 return 0;
258 }
259
260 format_path(buf, work_tree, repo->prefix, PATH_FORMAT_CANONICAL);
261 return 0;
262 }
263
264 static int get_path_toplevel_relative(struct repository *repo, struct strbuf *buf)
265 {
266 const char *work_tree = repo_get_work_tree(repo);
267
268 if (!work_tree) {
269 strbuf_addstr(buf, "");
270 return 0;
271 }
272
273 format_path(buf, work_tree, repo->prefix, PATH_FORMAT_RELATIVE);
274 return 0;
275 }
276
277 static int get_references_format(struct repository *repo, struct strbuf *buf)
278 {
279 strbuf_addstr(buf,
280 ref_storage_format_to_name(repo->ref_storage_format));
281 return 0;
282 }
283
284 /* repo_info_field keys must be in lexicographical order */
285 static const struct repo_info_field repo_info_field[] = {
286 { "layout.bare", get_layout_bare },
287 { "layout.shallow", get_layout_shallow },
288 { "object.format", get_object_format },
289 { "path.commondir.absolute", get_path_commondir_absolute },
290 { "path.commondir.relative", get_path_commondir_relative },
291 { "path.git-prefix", get_path_git_prefix },
292 { "path.gitdir.absolute", get_path_gitdir_absolute },
293 { "path.gitdir.relative", get_path_gitdir_relative },
294 { "path.grafts.absolute", get_path_grafts_absolute },
295 { "path.grafts.relative", get_path_grafts_relative },
296 { "path.hooks.absolute", get_path_hooks_absolute },
297 { "path.hooks.relative", get_path_hooks_relative },
298 { "path.index.absolute", get_path_index_absolute },
299 { "path.index.relative", get_path_index_relative },
300 { "path.objects.absolute", get_path_objects_absolute },
301 { "path.objects.relative", get_path_objects_relative },
302 { "path.superproject-working-tree.absolute", get_path_superproject_absolute },
303 { "path.superproject-working-tree.relative", get_path_superproject_relative },
304 { "path.toplevel.absolute", get_path_toplevel_absolute },
305 { "path.toplevel.relative", get_path_toplevel_relative },
306 { "references.format", get_references_format },
307 };
308
309 static int repo_info_field_cmp(const void *va, const void *vb)
310 {
311 const struct repo_info_field *a = va;
312 const struct repo_info_field *b = vb;
313
314 return strcmp(a->key, b->key);
315 }
316
317 static const struct repo_info_field *get_repo_info_field(const char *key)
318 {
319 const struct repo_info_field search_key = { key, NULL };
320 const struct repo_info_field *found = bsearch(&search_key,
321 repo_info_field,
322 ARRAY_SIZE(repo_info_field),
323 sizeof(*found),
324 repo_info_field_cmp);
325
326 return found;
327 }
328
329 static void print_field(enum output_format format, const char *key,
330 const char *value)
331 {
332 switch (format) {
333 case FORMAT_NEWLINE_TERMINATED:
334 printf("%s=", key);
335 quote_c_style(value, NULL, stdout, 0);
336 putchar('\n');
337 break;
338 case FORMAT_NUL_TERMINATED:
339 printf("%s\n%s%c", key, value, '\0');
340 break;
341 default:
342 BUG("not a valid output format: %d", format);
343 }
344 }
345
346 static int print_fields(int argc, const char **argv,
347 struct repository *repo,
348 enum output_format format)
349 {
350 int ret = 0;
351 struct strbuf valbuf = STRBUF_INIT;
352
353 for (int i = 0; i < argc; i++) {
354 const char *key = argv[i];
355 const struct repo_info_field *field = get_repo_info_field(key);
356
357 if (!field) {
358 ret = error(_("key '%s' not found"), key);
359 continue;
360 }
361
362 strbuf_reset(&valbuf);
363 field->get_value(repo, &valbuf);
364 print_field(format, key, valbuf.buf);
365 }
366
367 strbuf_release(&valbuf);
368 return ret;
369 }
370
371 static int print_all_fields(struct repository *repo,
372 enum output_format format)
373 {
374 struct strbuf valbuf = STRBUF_INIT;
375
376 for (size_t i = 0; i < ARRAY_SIZE(repo_info_field); i++) {
377 const struct repo_info_field *field = &repo_info_field[i];
378
379 strbuf_reset(&valbuf);
380 field->get_value(repo, &valbuf);
381 print_field(format, field->key, valbuf.buf);
382 }
383
384 strbuf_release(&valbuf);
385 return 0;
386 }
387
388 static int print_keys(enum output_format format)
389 {
390 char sep;
391
392 switch (format) {
393 case FORMAT_NEWLINE_TERMINATED:
394 sep = '\n';
395 break;
396 case FORMAT_NUL_TERMINATED:
397 sep = '\0';
398 break;
399 default:
400 die(_("--keys can only be used with --format=lines or --format=nul"));
401 }
402
403 for (size_t i = 0; i < ARRAY_SIZE(repo_info_field); i++) {
404 const struct repo_info_field *field = &repo_info_field[i];
405 printf("%s%c", field->key, sep);
406 }
407
408 return 0;
409 }
410
411 static int parse_format_cb(const struct option *opt,
412 const char *arg, int unset UNUSED)
413 {
414 enum output_format *format = opt->value;
415
416 if (opt->short_name == 'z')
417 *format = FORMAT_NUL_TERMINATED;
418 else if (!strcmp(arg, "nul"))
419 *format = FORMAT_NUL_TERMINATED;
420 else if (!strcmp(arg, "lines"))
421 *format = FORMAT_NEWLINE_TERMINATED;
422 else if (!strcmp(arg, "table"))
423 *format = FORMAT_TABLE;
424 else
425 die(_("invalid format '%s'"), arg);
426
427 return 0;
428 }
429
430 static int cmd_repo_info(int argc, const char **argv, const char *prefix,
431 struct repository *repo)
432 {
433 enum output_format format = FORMAT_NEWLINE_TERMINATED;
434 int all_keys = 0;
435 int show_keys = 0;
436 struct option options[] = {
437 OPT_CALLBACK_F(0, "format", &format, N_("format"),
438 N_("output format"),
439 PARSE_OPT_NONEG, parse_format_cb),
440 OPT_CALLBACK_F('z', NULL, &format, NULL,
441 N_("synonym for --format=nul"),
442 PARSE_OPT_NONEG | PARSE_OPT_NOARG,
443 parse_format_cb),
444 OPT_BOOL(0, "all", &all_keys, N_("print all keys/values")),
445 OPT_BOOL(0, "keys", &show_keys, N_("show keys")),
446 OPT_END()
447 };
448
449 argc = parse_options(argc, argv, prefix, options, repo_info_usage, 0);
450
451 if (show_keys && (all_keys || argc))
452 die(_("--keys cannot be used with a <key> or --all"));
453
454 if (show_keys)
455 return print_keys(format);
456
457 if (format != FORMAT_NEWLINE_TERMINATED && format != FORMAT_NUL_TERMINATED)
458 die(_("unsupported output format"));
459
460 if (all_keys && argc)
461 die(_("--all and <key> cannot be used together"));
462
463 if (all_keys)
464 return print_all_fields(repo, format);
465 else
466 return print_fields(argc, argv, repo, format);
467 }
468
469 struct object_data {
470 struct object_id oid;
471 size_t value;
472 };
473
474 struct largest_objects {
475 struct object_data tag_size;
476 struct object_data commit_size;
477 struct object_data tree_size;
478 struct object_data blob_size;
479
480 struct object_data parent_count;
481 struct object_data tree_entries;
482 };
483
484 struct ref_stats {
485 size_t branches;
486 size_t remotes;
487 size_t tags;
488 size_t others;
489 };
490
491 struct object_values {
492 size_t tags;
493 size_t commits;
494 size_t trees;
495 size_t blobs;
496 };
497
498 struct object_stats {
499 struct object_values type_counts;
500 struct object_values inflated_sizes;
501 struct object_values disk_sizes;
502 struct largest_objects largest;
503 };
504
505 struct repo_structure {
506 struct ref_stats refs;
507 struct object_stats objects;
508 };
509
510 struct stats_table {
511 struct string_list rows;
512 struct string_list annotations;
513
514 int name_col_width;
515 int value_col_width;
516 int unit_col_width;
517 };
518
519 /*
520 * Holds column data that gets stored for each row.
521 */
522 struct stats_table_entry {
523 char *value;
524 const char *unit;
525 size_t index;
526 struct object_id *oid;
527 };
528
529 static void stats_table_vaddf(struct stats_table *table,
530 struct stats_table_entry *entry,
531 const char *format, va_list ap)
532 {
533 struct strbuf buf = STRBUF_INIT;
534 struct string_list_item *item;
535 char *formatted_name;
536 int name_width;
537
538 strbuf_vaddf(&buf, format, ap);
539 formatted_name = strbuf_detach(&buf, NULL);
540 name_width = utf8_strwidth(formatted_name);
541
542 item = string_list_append_nodup(&table->rows, formatted_name);
543 item->util = entry;
544
545 if (name_width > table->name_col_width)
546 table->name_col_width = name_width;
547 if (!entry)
548 return;
549 if (entry->oid) {
550 entry->index = table->annotations.nr + 1;
551 strbuf_addf(&buf, "[%" PRIuMAX "] %s", (uintmax_t)entry->index,
552 oid_to_hex(entry->oid));
553 string_list_append_nodup(&table->annotations, strbuf_detach(&buf, NULL));
554 }
555 if (entry->value) {
556 int value_width = utf8_strwidth(entry->value);
557 if (value_width > table->value_col_width)
558 table->value_col_width = value_width;
559 }
560 if (entry->unit) {
561 int unit_width = utf8_strwidth(entry->unit);
562 if (unit_width > table->unit_col_width)
563 table->unit_col_width = unit_width;
564 }
565
566 strbuf_release(&buf);
567 }
568
569 static void stats_table_addf(struct stats_table *table, const char *format, ...)
570 {
571 va_list ap;
572
573 va_start(ap, format);
574 stats_table_vaddf(table, NULL, format, ap);
575 va_end(ap);
576 }
577
578 static void stats_table_count_addf(struct stats_table *table, size_t value,
579 const char *format, ...)
580 {
581 struct stats_table_entry *entry;
582 va_list ap;
583
584 CALLOC_ARRAY(entry, 1);
585 humanise_count(value, &entry->value, &entry->unit);
586
587 va_start(ap, format);
588 stats_table_vaddf(table, entry, format, ap);
589 va_end(ap);
590 }
591
592 static void stats_table_object_count_addf(struct stats_table *table,
593 struct object_id *oid, size_t value,
594 const char *format, ...)
595 {
596 struct stats_table_entry *entry;
597 va_list ap;
598
599 CALLOC_ARRAY(entry, 1);
600 humanise_count(value, &entry->value, &entry->unit);
601
602 /*
603 * A NULL OID should not have a table annotation.
604 */
605 if (!is_null_oid(oid))
606 entry->oid = oid;
607
608 va_start(ap, format);
609 stats_table_vaddf(table, entry, format, ap);
610 va_end(ap);
611 }
612
613 static void stats_table_size_addf(struct stats_table *table, size_t value,
614 const char *format, ...)
615 {
616 struct stats_table_entry *entry;
617 va_list ap;
618
619 CALLOC_ARRAY(entry, 1);
620 humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);
621
622 va_start(ap, format);
623 stats_table_vaddf(table, entry, format, ap);
624 va_end(ap);
625 }
626
627 static void stats_table_object_size_addf(struct stats_table *table,
628 struct object_id *oid, size_t value,
629 const char *format, ...)
630 {
631 struct stats_table_entry *entry;
632 va_list ap;
633
634 CALLOC_ARRAY(entry, 1);
635 humanise_bytes(value, &entry->value, &entry->unit, HUMANISE_COMPACT);
636
637 /*
638 * A NULL OID should not have a table annotation.
639 */
640 if (!is_null_oid(oid))
641 entry->oid = oid;
642
643 va_start(ap, format);
644 stats_table_vaddf(table, entry, format, ap);
645 va_end(ap);
646 }
647
648 static inline size_t get_total_reference_count(struct ref_stats *stats)
649 {
650 return stats->branches + stats->remotes + stats->tags + stats->others;
651 }
652
653 static inline size_t get_total_object_values(struct object_values *values)
654 {
655 return values->tags + values->commits + values->trees + values->blobs;
656 }
657
658 static void stats_table_setup_structure(struct stats_table *table,
659 struct repo_structure *stats)
660 {
661 struct object_stats *objects = &stats->objects;
662 struct ref_stats *refs = &stats->refs;
663 size_t inflated_object_total;
664 size_t object_count_total;
665 size_t disk_object_total;
666 size_t ref_total;
667
668 ref_total = get_total_reference_count(refs);
669 stats_table_addf(table, "* %s", _("References"));
670 stats_table_count_addf(table, ref_total, " * %s", _("Count"));
671 stats_table_count_addf(table, refs->branches, " * %s", _("Branches"));
672 stats_table_count_addf(table, refs->tags, " * %s", _("Tags"));
673 stats_table_count_addf(table, refs->remotes, " * %s", _("Remotes"));
674 stats_table_count_addf(table, refs->others, " * %s", _("Others"));
675
676 object_count_total = get_total_object_values(&objects->type_counts);
677 stats_table_addf(table, "");
678 stats_table_addf(table, "* %s", _("Reachable objects"));
679 stats_table_count_addf(table, object_count_total, " * %s", _("Count"));
680 stats_table_count_addf(table, objects->type_counts.commits,
681 " * %s", _("Commits"));
682 stats_table_count_addf(table, objects->type_counts.trees,
683 " * %s", _("Trees"));
684 stats_table_count_addf(table, objects->type_counts.blobs,
685 " * %s", _("Blobs"));
686 stats_table_count_addf(table, objects->type_counts.tags,
687 " * %s", _("Tags"));
688
689 inflated_object_total = get_total_object_values(&objects->inflated_sizes);
690 stats_table_size_addf(table, inflated_object_total,
691 " * %s", _("Inflated size"));
692 stats_table_size_addf(table, objects->inflated_sizes.commits,
693 " * %s", _("Commits"));
694 stats_table_size_addf(table, objects->inflated_sizes.trees,
695 " * %s", _("Trees"));
696 stats_table_size_addf(table, objects->inflated_sizes.blobs,
697 " * %s", _("Blobs"));
698 stats_table_size_addf(table, objects->inflated_sizes.tags,
699 " * %s", _("Tags"));
700
701 disk_object_total = get_total_object_values(&objects->disk_sizes);
702 stats_table_size_addf(table, disk_object_total,
703 " * %s", _("Disk size"));
704 stats_table_size_addf(table, objects->disk_sizes.commits,
705 " * %s", _("Commits"));
706 stats_table_size_addf(table, objects->disk_sizes.trees,
707 " * %s", _("Trees"));
708 stats_table_size_addf(table, objects->disk_sizes.blobs,
709 " * %s", _("Blobs"));
710 stats_table_size_addf(table, objects->disk_sizes.tags,
711 " * %s", _("Tags"));
712
713 stats_table_addf(table, "");
714 stats_table_addf(table, "* %s", _("Largest objects"));
715 stats_table_addf(table, " * %s", _("Commits"));
716 stats_table_object_size_addf(table,
717 &objects->largest.commit_size.oid,
718 objects->largest.commit_size.value,
719 " * %s", _("Maximum size"));
720 stats_table_object_count_addf(table,
721 &objects->largest.parent_count.oid,
722 objects->largest.parent_count.value,
723 " * %s", _("Maximum parents"));
724 stats_table_addf(table, " * %s", _("Trees"));
725 stats_table_object_size_addf(table,
726 &objects->largest.tree_size.oid,
727 objects->largest.tree_size.value,
728 " * %s", _("Maximum size"));
729 stats_table_object_count_addf(table,
730 &objects->largest.tree_entries.oid,
731 objects->largest.tree_entries.value,
732 " * %s", _("Maximum entries"));
733 stats_table_addf(table, " * %s", _("Blobs"));
734 stats_table_object_size_addf(table,
735 &objects->largest.blob_size.oid,
736 objects->largest.blob_size.value,
737 " * %s", _("Maximum size"));
738 stats_table_addf(table, " * %s", _("Tags"));
739 stats_table_object_size_addf(table,
740 &objects->largest.tag_size.oid,
741 objects->largest.tag_size.value,
742 " * %s", _("Maximum size"));
743 }
744
745 #define INDEX_WIDTH 4
746
747 static void stats_table_print_structure(const struct stats_table *table)
748 {
749 const char *name_col_title = _("Repository structure");
750 const char *value_col_title = _("Value");
751 int title_name_width = utf8_strwidth(name_col_title);
752 int title_value_width = utf8_strwidth(value_col_title);
753 int name_col_width = table->name_col_width;
754 int value_col_width = table->value_col_width;
755 int unit_col_width = table->unit_col_width;
756 struct string_list_item *item;
757 struct strbuf buf = STRBUF_INIT;
758
759 if (title_name_width > name_col_width)
760 name_col_width = title_name_width;
761 if (title_value_width > value_col_width + unit_col_width + 1)
762 value_col_width = title_value_width - unit_col_width;
763
764 strbuf_addstr(&buf, "| ");
765 strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width + INDEX_WIDTH,
766 name_col_title);
767 strbuf_addstr(&buf, " | ");
768 strbuf_utf8_align(&buf, ALIGN_LEFT,
769 value_col_width + unit_col_width + 1, value_col_title);
770 strbuf_addstr(&buf, " |");
771 printf("%s\n", buf.buf);
772
773 printf("| ");
774 for (int i = 0; i < name_col_width + INDEX_WIDTH; i++)
775 putchar('-');
776 printf(" | ");
777 for (int i = 0; i < value_col_width + unit_col_width + 1; i++)
778 putchar('-');
779 printf(" |\n");
780
781 for_each_string_list_item(item, &table->rows) {
782 struct stats_table_entry *entry = item->util;
783 const char *value = "";
784 const char *unit = "";
785
786 if (entry) {
787 value = entry->value;
788 if (entry->unit)
789 unit = entry->unit;
790 }
791
792 strbuf_reset(&buf);
793 strbuf_addstr(&buf, "| ");
794 strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width, item->string);
795
796 if (entry && entry->oid)
797 strbuf_addf(&buf, " [%" PRIuMAX "]",
798 (uintmax_t)entry->index);
799 else
800 strbuf_addchars(&buf, ' ', INDEX_WIDTH);
801
802 strbuf_addstr(&buf, " | ");
803 strbuf_utf8_align(&buf, ALIGN_RIGHT, value_col_width, value);
804 strbuf_addch(&buf, ' ');
805 strbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit);
806 strbuf_addstr(&buf, " |");
807 printf("%s\n", buf.buf);
808 }
809
810 if (table->annotations.nr) {
811 printf("\n");
812 for_each_string_list_item(item, &table->annotations)
813 printf("%s\n", item->string);
814 }
815
816 strbuf_release(&buf);
817 }
818
819 static void stats_table_clear(struct stats_table *table)
820 {
821 struct stats_table_entry *entry;
822 struct string_list_item *item;
823
824 for_each_string_list_item(item, &table->rows) {
825 entry = item->util;
826 if (entry)
827 free(entry->value);
828 }
829
830 string_list_clear(&table->rows, 1);
831 string_list_clear(&table->annotations, 1);
832 }
833
834 static inline void print_keyvalue(const char *key, char key_delim, size_t value,
835 char value_delim)
836 {
837 printf("%s%c%" PRIuMAX "%c", key, key_delim, (uintmax_t)value,
838 value_delim);
839 }
840
841 static void print_object_data(const char *key, char key_delim,
842 struct object_data *data, char value_delim)
843 {
844 print_keyvalue(key, key_delim, data->value, value_delim);
845 printf("%s_oid%c%s%c", key, key_delim, oid_to_hex(&data->oid),
846 value_delim);
847 }
848
849 static void structure_keyvalue_print(struct repo_structure *stats,
850 char key_delim, char value_delim)
851 {
852 print_keyvalue("references.branches.count", key_delim,
853 stats->refs.branches, value_delim);
854 print_keyvalue("references.tags.count", key_delim,
855 stats->refs.tags, value_delim);
856 print_keyvalue("references.remotes.count", key_delim,
857 stats->refs.remotes, value_delim);
858 print_keyvalue("references.others.count", key_delim,
859 stats->refs.others, value_delim);
860
861 print_keyvalue("objects.commits.count", key_delim,
862 stats->objects.type_counts.commits, value_delim);
863 print_keyvalue("objects.trees.count", key_delim,
864 stats->objects.type_counts.trees, value_delim);
865 print_keyvalue("objects.blobs.count", key_delim,
866 stats->objects.type_counts.blobs, value_delim);
867 print_keyvalue("objects.tags.count", key_delim,
868 stats->objects.type_counts.tags, value_delim);
869
870 print_keyvalue("objects.commits.inflated_size", key_delim,
871 stats->objects.inflated_sizes.commits, value_delim);
872 print_keyvalue("objects.trees.inflated_size", key_delim,
873 stats->objects.inflated_sizes.trees, value_delim);
874 print_keyvalue("objects.blobs.inflated_size", key_delim,
875 stats->objects.inflated_sizes.blobs, value_delim);
876 print_keyvalue("objects.tags.inflated_size", key_delim,
877 stats->objects.inflated_sizes.tags, value_delim);
878
879 print_keyvalue("objects.commits.disk_size", key_delim,
880 stats->objects.disk_sizes.commits, value_delim);
881 print_keyvalue("objects.trees.disk_size", key_delim,
882 stats->objects.disk_sizes.trees, value_delim);
883 print_keyvalue("objects.blobs.disk_size", key_delim,
884 stats->objects.disk_sizes.blobs, value_delim);
885 print_keyvalue("objects.tags.disk_size", key_delim,
886 stats->objects.disk_sizes.tags, value_delim);
887
888 print_object_data("objects.commits.max_size", key_delim,
889 &stats->objects.largest.commit_size, value_delim);
890 print_object_data("objects.trees.max_size", key_delim,
891 &stats->objects.largest.tree_size, value_delim);
892 print_object_data("objects.blobs.max_size", key_delim,
893 &stats->objects.largest.blob_size, value_delim);
894 print_object_data("objects.tags.max_size", key_delim,
895 &stats->objects.largest.tag_size, value_delim);
896
897 print_object_data("objects.commits.max_parents", key_delim,
898 &stats->objects.largest.parent_count, value_delim);
899 print_object_data("objects.trees.max_entries", key_delim,
900 &stats->objects.largest.tree_entries, value_delim);
901
902 fflush(stdout);
903 }
904
905 struct count_references_data {
906 struct ref_stats *stats;
907 struct rev_info *revs;
908 struct progress *progress;
909 };
910
911 static int count_references(const struct reference *ref, void *cb_data)
912 {
913 struct count_references_data *data = cb_data;
914 struct ref_stats *stats = data->stats;
915 size_t ref_count;
916
917 switch (ref_kind_from_refname(ref->name)) {
918 case FILTER_REFS_BRANCHES:
919 stats->branches++;
920 break;
921 case FILTER_REFS_REMOTES:
922 stats->remotes++;
923 break;
924 case FILTER_REFS_TAGS:
925 stats->tags++;
926 break;
927 case FILTER_REFS_OTHERS:
928 stats->others++;
929 break;
930 default:
931 BUG("unexpected reference type");
932 }
933
934 /*
935 * While iterating through references for counting, also add OIDs in
936 * preparation for the path walk.
937 */
938 add_pending_oid(data->revs, NULL, ref->oid, 0);
939
940 ref_count = get_total_reference_count(stats);
941 display_progress(data->progress, ref_count);
942
943 return 0;
944 }
945
946 static void structure_count_references(struct ref_stats *stats,
947 struct rev_info *revs,
948 struct repository *repo,
949 int show_progress)
950 {
951 struct count_references_data data = {
952 .stats = stats,
953 .revs = revs,
954 };
955
956 if (show_progress)
957 data.progress = start_delayed_progress(repo,
958 _("Counting references"), 0);
959
960 refs_for_each_ref(get_main_ref_store(repo), count_references, &data);
961 stop_progress(&data.progress);
962 }
963
964 struct count_objects_data {
965 struct object_database *odb;
966 struct object_stats *stats;
967 struct progress *progress;
968 };
969
970 static void check_largest(struct object_data *data, struct object_id *oid,
971 size_t value)
972 {
973 if (value > data->value || is_null_oid(&data->oid)) {
974 oidcpy(&data->oid, oid);
975 data->value = value;
976 }
977 }
978
979 static size_t count_tree_entries(struct object *obj)
980 {
981 struct tree *t = object_as_type(obj, OBJ_TREE, 0);
982 struct name_entry entry;
983 struct tree_desc desc;
984 size_t count = 0;
985
986 init_tree_desc(&desc, &t->object.oid, t->buffer, t->size);
987 while (tree_entry(&desc, &entry))
988 count++;
989
990 return count;
991 }
992
993 static int count_objects(const char *path UNUSED, struct oid_array *oids,
994 enum object_type type, void *cb_data)
995 {
996 struct count_objects_data *data = cb_data;
997 struct object_stats *stats = data->stats;
998 size_t object_count;
999
1000 for (size_t i = 0; i < oids->nr; i++) {
1001 struct object_info oi = OBJECT_INFO_INIT;
1002 unsigned long inflated;
1003 size_t inflated_st = 0;
1004 struct commit *commit;
1005 struct object *obj;
1006 void *content;
1007 off_t disk;
1008 int eaten;
1009
1010 oi.sizep = &inflated_st;
1011 oi.disk_sizep = &disk;
1012 oi.contentp = &content;
1013
1014 if (odb_read_object_info_extended(data->odb, &oids->oid[i], &oi,
1015 OBJECT_INFO_SKIP_FETCH_OBJECT |
1016 OBJECT_INFO_QUICK) < 0)
1017 continue;
1018 inflated = cast_size_t_to_ulong(inflated_st);
1019
1020 obj = parse_object_buffer(the_repository, &oids->oid[i], type,
1021 inflated, content, &eaten);
1022
1023 switch (type) {
1024 case OBJ_TAG:
1025 stats->type_counts.tags++;
1026 stats->inflated_sizes.tags += inflated;
1027 stats->disk_sizes.tags += disk;
1028 check_largest(&stats->largest.tag_size, &oids->oid[i],
1029 inflated);
1030 break;
1031 case OBJ_COMMIT:
1032 commit = object_as_type(obj, OBJ_COMMIT, 0);
1033 stats->type_counts.commits++;
1034 stats->inflated_sizes.commits += inflated;
1035 stats->disk_sizes.commits += disk;
1036 check_largest(&stats->largest.commit_size, &oids->oid[i],
1037 inflated);
1038 check_largest(&stats->largest.parent_count, &oids->oid[i],
1039 commit_list_count(commit->parents));
1040 break;
1041 case OBJ_TREE:
1042 stats->type_counts.trees++;
1043 stats->inflated_sizes.trees += inflated;
1044 stats->disk_sizes.trees += disk;
1045 check_largest(&stats->largest.tree_size, &oids->oid[i],
1046 inflated);
1047 check_largest(&stats->largest.tree_entries, &oids->oid[i],
1048 count_tree_entries(obj));
1049 break;
1050 case OBJ_BLOB:
1051 stats->type_counts.blobs++;
1052 stats->inflated_sizes.blobs += inflated;
1053 stats->disk_sizes.blobs += disk;
1054 check_largest(&stats->largest.blob_size, &oids->oid[i],
1055 inflated);
1056 break;
1057 default:
1058 BUG("invalid object type");
1059 }
1060
1061 if (!eaten)
1062 free(content);
1063 }
1064
1065 object_count = get_total_object_values(&stats->type_counts);
1066 display_progress(data->progress, object_count);
1067
1068 return 0;
1069 }
1070
1071 static void structure_count_objects(struct object_stats *stats,
1072 struct rev_info *revs,
1073 struct repository *repo, int show_progress)
1074 {
1075 struct path_walk_info info = PATH_WALK_INFO_INIT;
1076 struct count_objects_data data = {
1077 .odb = repo->objects,
1078 .stats = stats,
1079 };
1080
1081 info.revs = revs;
1082 info.path_fn = count_objects;
1083 info.path_fn_data = &data;
1084
1085 if (show_progress)
1086 data.progress = start_delayed_progress(repo, _("Counting objects"), 0);
1087
1088 walk_objects_by_path(&info);
1089 path_walk_info_clear(&info);
1090 stop_progress(&data.progress);
1091 }
1092
1093 static int cmd_repo_structure(int argc, const char **argv, const char *prefix,
1094 struct repository *repo)
1095 {
1096 struct stats_table table = {
1097 .rows = STRING_LIST_INIT_DUP,
1098 .annotations = STRING_LIST_INIT_DUP,
1099 };
1100 enum output_format format = FORMAT_TABLE;
1101 struct repo_structure stats = { 0 };
1102 struct rev_info revs;
1103 int show_progress = -1;
1104 struct option options[] = {
1105 OPT_CALLBACK_F(0, "format", &format, N_("format"),
1106 N_("output format"),
1107 PARSE_OPT_NONEG, parse_format_cb),
1108 OPT_CALLBACK_F('z', NULL, &format, NULL,
1109 N_("synonym for --format=nul"),
1110 PARSE_OPT_NONEG | PARSE_OPT_NOARG,
1111 parse_format_cb),
1112 OPT_BOOL(0, "progress", &show_progress, N_("show progress")),
1113 OPT_END()
1114 };
1115
1116 argc = parse_options(argc, argv, prefix, options, repo_structure_usage, 0);
1117 if (argc)
1118 usage(_("too many arguments"));
1119
1120 repo_init_revisions(repo, &revs, prefix);
1121
1122 if (show_progress < 0)
1123 show_progress = isatty(2);
1124
1125 structure_count_references(&stats.refs, &revs, repo, show_progress);
1126 structure_count_objects(&stats.objects, &revs, repo, show_progress);
1127
1128 switch (format) {
1129 case FORMAT_TABLE:
1130 stats_table_setup_structure(&table, &stats);
1131 stats_table_print_structure(&table);
1132 break;
1133 case FORMAT_NEWLINE_TERMINATED:
1134 structure_keyvalue_print(&stats, '=', '\n');
1135 break;
1136 case FORMAT_NUL_TERMINATED:
1137 structure_keyvalue_print(&stats, '\n', '\0');
1138 break;
1139 default:
1140 BUG("invalid output format");
1141 }
1142
1143 stats_table_clear(&table);
1144 release_revisions(&revs);
1145
1146 return 0;
1147 }
1148
1149 int cmd_repo(int argc, const char **argv, const char *prefix,
1150 struct repository *repo)
1151 {
1152 parse_opt_subcommand_fn *fn = NULL;
1153 struct option options[] = {
1154 OPT_SUBCOMMAND("info", &fn, cmd_repo_info),
1155 OPT_SUBCOMMAND("structure", &fn, cmd_repo_structure),
1156 OPT_END()
1157 };
1158
1159 argc = parse_options(argc, argv, prefix, options, repo_usage, 0);
1160
1161 return fn(argc, argv, prefix, repo);
1162 }