strbuf: split out logic to humanise byte values

In a subsequent commit, byte size values displayed in table output for the git-repo(1) "structure" subcommand will be shown in a more human-readable format with the appropriate unit prefixes. For this usecase, the downscaled values and unit strings must be handled separately to ensure proper column alignment. Split out logic from strbuf_humanise() to downscale byte values and determine the corresponding unit prefix into a separate humanise_bytes() function that provides seperate value and unit strings. Note that the "byte" string in "t/helper/test-simple-ipc.c" is unmarked for translation here so that it doesn't conflict with the newly defined plural "byte/bytes" translation and instead uses it. Signed-off-by: Justin Tobler <jltobler@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Justin Tobler committed Dec 17, 2025 at 11:53 UTC ce849b1851102d974653701564573798034492d5
3 files changed +60 -35
strbuf.c
+40 -34
@@ -836,47 +836,53 @@ void strbuf_addstr_urlencode(struct strbuf *sb, const char *s,
836 strbuf_add_urlencode(sb, s, strlen(s), allow_unencoded_fn);
837 }
838
839 -static void strbuf_humanise(struct strbuf *buf, off_t bytes,
840 - int humanise_rate)
839 +void humanise_bytes(off_t bytes, char **value, const char **unit,
840 + unsigned flags)
841 {
842 + int humanise_rate = flags & HUMANISE_RATE;
843 +
844 if (bytes > 1 << 30) {
843 - strbuf_addf(buf,
844 - humanise_rate == 0 ?
845 - /* TRANSLATORS: IEC 80000-13:2008 gibibyte */
846 - _("%u.%2.2u GiB") :
847 - /* TRANSLATORS: IEC 80000-13:2008 gibibyte/second */
848 - _("%u.%2.2u GiB/s"),
849 - (unsigned)(bytes >> 30),
850 - (unsigned)(bytes & ((1 << 30) - 1)) / 10737419);
845 + *value = xstrfmt(_("%u.%2.2u"), (unsigned)(bytes >> 30),
846 + (unsigned)(bytes & ((1 << 30) - 1)) / 10737419);
847 + /* TRANSLATORS: IEC 80000-13:2008 gibibyte/second and gibibyte */
848 + *unit = humanise_rate ? _("GiB/s") : _("GiB");
849 } else if (bytes > 1 << 20) {
852 - unsigned x = bytes + 5243; /* for rounding */
853 - strbuf_addf(buf,
854 - humanise_rate == 0 ?
855 - /* TRANSLATORS: IEC 80000-13:2008 mebibyte */
856 - _("%u.%2.2u MiB") :
857 - /* TRANSLATORS: IEC 80000-13:2008 mebibyte/second */
858 - _("%u.%2.2u MiB/s"),
859 - x >> 20, ((x & ((1 << 20) - 1)) * 100) >> 20);
850 + unsigned x = bytes + 5243; /* for rounding */
851 + *value = xstrfmt(_("%u.%2.2u"), x >> 20,
852 + ((x & ((1 << 20) - 1)) * 100) >> 20);
853 + /* TRANSLATORS: IEC 80000-13:2008 mebibyte/second and mebibyte */
854 + *unit = humanise_rate ? _("MiB/s") : _("MiB");
855 } else if (bytes > 1 << 10) {
861 - unsigned x = bytes + 5; /* for rounding */
862 - strbuf_addf(buf,
863 - humanise_rate == 0 ?
864 - /* TRANSLATORS: IEC 80000-13:2008 kibibyte */
865 - _("%u.%2.2u KiB") :
866 - /* TRANSLATORS: IEC 80000-13:2008 kibibyte/second */
867 - _("%u.%2.2u KiB/s"),
868 - x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10);
856 + unsigned x = bytes + 5; /* for rounding */
857 + *value = xstrfmt(_("%u.%2.2u"), x >> 10,
858 + ((x & ((1 << 10) - 1)) * 100) >> 10);
859 + /* TRANSLATORS: IEC 80000-13:2008 kibibyte/second and kibibyte */
860 + *unit = humanise_rate ? _("KiB/s") : _("KiB");
861 } else {
870 - strbuf_addf(buf,
871 - humanise_rate == 0 ?
872 - /* TRANSLATORS: IEC 80000-13:2008 byte */
873 - Q_("%u byte", "%u bytes", bytes) :
874 - /* TRANSLATORS: IEC 80000-13:2008 byte/second */
875 - Q_("%u byte/s", "%u bytes/s", bytes),
876 - (unsigned)bytes);
862 + *value = xstrfmt("%u", (unsigned)bytes);
863 + *unit = humanise_rate ?
864 + /* TRANSLATORS: IEC 80000-13:2008 byte/second */
865 + Q_("byte/s", "bytes/s", bytes) :
866 + /* TRANSLATORS: IEC 80000-13:2008 byte */
867 + Q_("byte", "bytes", bytes);
868 }
869 }
870
871 +static void strbuf_humanise(struct strbuf *buf, off_t bytes, unsigned flags)
872 +{
873 + char *value;
874 + const char *unit;
875 +
876 + humanise_bytes(bytes, &value, &unit, flags);
877 +
878 + /*
879 + * TRANSLATORS: The first argument is the number string. The second
880 + * argument is the unit string (i.e. "12.34 MiB/s").
881 + */
882 + strbuf_addf(buf, _("%s %s"), value, unit);
883 + free(value);
884 +}
885 +
886 void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes)
887 {
888 strbuf_humanise(buf, bytes, 0);
@@ -884,7 +890,7 @@ void strbuf_humanise_bytes(struct strbuf *buf, off_t bytes)
890
891 void strbuf_humanise_rate(struct strbuf *buf, off_t bytes)
892 {
887 - strbuf_humanise(buf, bytes, 1);
893 + strbuf_humanise(buf, bytes, HUMANISE_RATE);
894 }
895
896 int printf_ln(const char *fmt, ...)
strbuf.h
+14
@@ -367,6 +367,20 @@ void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
367 */
368 void strbuf_add_percentencode(struct strbuf *dst, const char *src, int flags);
369
370 +enum humanise_flags {
371 + /*
372 + * Use rate based units for humanised values.
373 + */
374 + HUMANISE_RATE = (1 << 0),
375 +};
376 +
377 +/**
378 + * Converts the given byte size into a downscaled human-readable value and
379 + * corresponding unit as two separate strings.
380 + */
381 +void humanise_bytes(off_t bytes, char **value, const char **unit,
382 + unsigned flags);
383 +
384 /**
385 * Append the given byte size as a human-readable string (i.e. 12.23 KiB,
386 * 3.50 MiB).
t/helper/test-simple-ipc.c
+6 -1
@@ -603,7 +603,12 @@ int cmd__simple_ipc(int argc, const char **argv)
603 OPT_INTEGER(0, "bytecount", &cl_args.bytecount, N_("number of bytes")),
604 OPT_INTEGER(0, "batchsize", &cl_args.batchsize, N_("number of requests per thread")),
605
606 - OPT_STRING(0, "byte", &bytevalue, N_("byte"), N_("ballast character")),
606 + /*
607 + * The "byte" string here is not marked for translation and
608 + * instead relies on translation in strbuf.c:humanise_bytes() to
609 + * avoid conflict with the plural form.
610 + */
611 + OPT_STRING(0, "byte", &bytevalue, "byte", N_("ballast character")),
612 OPT_STRING(0, "token", &cl_args.token, N_("token"), N_("command token to send to the server")),
613
614 OPT_END()