builtin/repo: humanise count values in structure output

The table output format for the git-repo(1) structure subcommand is used
by default and intended to provide output to users in a human-friendly
manner. When the reference/object count values in a repository are
large, it becomes more cumbersome for users to read the values.

For larger values, update the table output format to instead produce
more human-friendly count values that are scaled down with the
appropriate unit prefix. Output for the keyvalue and nul formats remains
unchanged.

Signed-off-by: Justin Tobler <jltobler@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Justin Tobler 2025-12-17 11:54:00 -06:00 committed by Junio C Hamano
parent ce849b1851
commit 54731320cc
4 changed files with 91 additions and 41 deletions

View File

@ -223,6 +223,7 @@ struct stats_table {
int name_col_width;
int value_col_width;
int unit_col_width;
};
/*
@ -230,6 +231,7 @@ struct stats_table {
*/
struct stats_table_entry {
char *value;
const char *unit;
};
static void stats_table_vaddf(struct stats_table *table,
@ -250,11 +252,18 @@ static void stats_table_vaddf(struct stats_table *table,
if (name_width > table->name_col_width)
table->name_col_width = name_width;
if (entry) {
if (!entry)
return;
if (entry->value) {
int value_width = utf8_strwidth(entry->value);
if (value_width > table->value_col_width)
table->value_col_width = value_width;
}
if (entry->unit) {
int unit_width = utf8_strwidth(entry->unit);
if (unit_width > table->unit_col_width)
table->unit_col_width = unit_width;
}
}
static void stats_table_addf(struct stats_table *table, const char *format, ...)
@ -273,7 +282,7 @@ static void stats_table_count_addf(struct stats_table *table, size_t value,
va_list ap;
CALLOC_ARRAY(entry, 1);
entry->value = xstrfmt("%" PRIuMAX, (uintmax_t)value);
humanise_count(value, &entry->value, &entry->unit);
va_start(ap, format);
stats_table_vaddf(table, entry, format, ap);
@ -324,20 +333,24 @@ static void stats_table_print_structure(const struct stats_table *table)
{
const char *name_col_title = _("Repository structure");
const char *value_col_title = _("Value");
int name_col_width = utf8_strwidth(name_col_title);
int value_col_width = utf8_strwidth(value_col_title);
int title_name_width = utf8_strwidth(name_col_title);
int title_value_width = utf8_strwidth(value_col_title);
int name_col_width = table->name_col_width;
int value_col_width = table->value_col_width;
int unit_col_width = table->unit_col_width;
struct string_list_item *item;
struct strbuf buf = STRBUF_INIT;
if (table->name_col_width > name_col_width)
name_col_width = table->name_col_width;
if (table->value_col_width > value_col_width)
value_col_width = table->value_col_width;
if (title_name_width > name_col_width)
name_col_width = title_name_width;
if (title_value_width > value_col_width + unit_col_width + 1)
value_col_width = title_value_width - unit_col_width;
strbuf_addstr(&buf, "| ");
strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width, name_col_title);
strbuf_addstr(&buf, " | ");
strbuf_utf8_align(&buf, ALIGN_LEFT, value_col_width, value_col_title);
strbuf_utf8_align(&buf, ALIGN_LEFT,
value_col_width + unit_col_width + 1, value_col_title);
strbuf_addstr(&buf, " |");
printf("%s\n", buf.buf);
@ -345,17 +358,20 @@ static void stats_table_print_structure(const struct stats_table *table)
for (int i = 0; i < name_col_width; i++)
putchar('-');
printf(" | ");
for (int i = 0; i < value_col_width; i++)
for (int i = 0; i < value_col_width + unit_col_width + 1; i++)
putchar('-');
printf(" |\n");
for_each_string_list_item(item, &table->rows) {
struct stats_table_entry *entry = item->util;
const char *value = "";
const char *unit = "";
if (entry) {
struct stats_table_entry *entry = item->util;
value = entry->value;
if (entry->unit)
unit = entry->unit;
}
strbuf_reset(&buf);
@ -363,6 +379,8 @@ static void stats_table_print_structure(const struct stats_table *table)
strbuf_utf8_align(&buf, ALIGN_LEFT, name_col_width, item->string);
strbuf_addstr(&buf, " | ");
strbuf_utf8_align(&buf, ALIGN_RIGHT, value_col_width, value);
strbuf_addch(&buf, ' ');
strbuf_utf8_align(&buf, ALIGN_LEFT, unit_col_width, unit);
strbuf_addstr(&buf, " |");
printf("%s\n", buf.buf);
}

View File

@ -836,6 +836,32 @@ void strbuf_addstr_urlencode(struct strbuf *sb, const char *s,
strbuf_add_urlencode(sb, s, strlen(s), allow_unencoded_fn);
}
void humanise_count(size_t count, char **value, const char **unit)
{
if (count >= 1000000000) {
size_t x = count + 5000000; /* for rounding */
*value = xstrfmt(_("%u.%2.2u"), (unsigned)(x / 1000000000),
(unsigned)(x % 1000000000 / 10000000));
/* TRANSLATORS: SI decimal prefix symbol for 10^9 */
*unit = _("G");
} else if (count >= 1000000) {
size_t x = count + 5000; /* for rounding */
*value = xstrfmt(_("%u.%2.2u"), (unsigned)(x / 1000000),
(unsigned)(x % 1000000 / 10000));
/* TRANSLATORS: SI decimal prefix symbol for 10^6 */
*unit = _("M");
} else if (count >= 1000) {
size_t x = count + 5; /* for rounding */
*value = xstrfmt(_("%u.%2.2u"), (unsigned)(x / 1000),
(unsigned)(x % 1000 / 10));
/* TRANSLATORS: SI decimal prefix symbol for 10^3 */
*unit = _("k");
} else {
*value = xstrfmt("%u", (unsigned)count);
*unit = NULL;
}
}
void humanise_bytes(off_t bytes, char **value, const char **unit,
unsigned flags)
{

View File

@ -381,6 +381,12 @@ enum humanise_flags {
void humanise_bytes(off_t bytes, char **value, const char **unit,
unsigned flags);
/**
* Converts the given count into a downscaled human-readable value and
* corresponding unit as two separate strings.
*/
void humanise_count(size_t count, char **value, const char **unit);
/**
* Append the given byte size as a human-readable string (i.e. 12.23 KiB,
* 3.50 MiB).

View File

@ -10,21 +10,21 @@ test_expect_success 'empty repository' '
(
cd repo &&
cat >expect <<-\EOF &&
| Repository structure | Value |
| -------------------- | ----- |
| * References | |
| * Count | 0 |
| * Branches | 0 |
| * Tags | 0 |
| * Remotes | 0 |
| * Others | 0 |
| | |
| * Reachable objects | |
| * Count | 0 |
| * Commits | 0 |
| * Trees | 0 |
| * Blobs | 0 |
| * Tags | 0 |
| Repository structure | Value |
| -------------------- | ------ |
| * References | |
| * Count | 0 |
| * Branches | 0 |
| * Tags | 0 |
| * Remotes | 0 |
| * Others | 0 |
| | |
| * Reachable objects | |
| * Count | 0 |
| * Commits | 0 |
| * Trees | 0 |
| * Blobs | 0 |
| * Tags | 0 |
EOF
git repo structure >out 2>err &&
@ -39,7 +39,7 @@ test_expect_success 'repository with references and objects' '
git init repo &&
(
cd repo &&
test_commit_bulk 42 &&
test_commit_bulk 1005 &&
git tag -a foo -m bar &&
oid="$(git rev-parse HEAD)" &&
@ -49,21 +49,21 @@ test_expect_success 'repository with references and objects' '
git notes add -m foo &&
cat >expect <<-\EOF &&
| Repository structure | Value |
| -------------------- | ----- |
| * References | |
| * Count | 4 |
| * Branches | 1 |
| * Tags | 1 |
| * Remotes | 1 |
| * Others | 1 |
| | |
| * Reachable objects | |
| * Count | 130 |
| * Commits | 43 |
| * Trees | 43 |
| * Blobs | 43 |
| * Tags | 1 |
| Repository structure | Value |
| -------------------- | ------ |
| * References | |
| * Count | 4 |
| * Branches | 1 |
| * Tags | 1 |
| * Remotes | 1 |
| * Others | 1 |
| | |
| * Reachable objects | |
| * Count | 3.02 k |
| * Commits | 1.01 k |
| * Trees | 1.01 k |
| * Blobs | 1.01 k |
| * Tags | 1 |
EOF
git repo structure >out 2>err &&