{"thread":{"id":"65633","subject":"[PATCH] hex: add and use strbuf_add_oid_hex()","startedAt":"2026-05-13T15:49:13Z","lastAt":"2026-05-13T16:55:49Z","messageCount":3,"participants":["René Scharfe","Jeff King"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"543246","messageId":"183aa0fd-d455-4ec9-9c42-d511fac8b3e4@web.de","threadId":"65633","inReplyTo":null,"subject":"[PATCH] hex: add and use strbuf_add_oid_hex()","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2026-05-13T15:49:11Z","receivedAt":"2026-05-13T15:49:13Z","isPatch":true,"body":"Add a function for adding the full hexadecimal hash value of an object\nID to a strbuf.  It's thread-safe and slightly more efficient than using\nstrbuf_addstr() with oid_to_hex() because it doesn't have to determine\nthe length of the string or copy it from the intermediate static buffer.\n\nAdd and apply a semantic patch to use it throughout the code base.\n\nI get a tiny speedup for git log showing a single hash per commit:\n\nBenchmark 1: ./git_main log --format=%H\n  Time (mean ± σ):      91.2 ms ±   0.7 ms    [User: 51.9 ms, System: 38.6 ms]\n  Range (min … max):    89.8 ms …  92.6 ms    31 runs\n\nBenchmark 2: ./git log --format=%H\n  Time (mean ± σ):      90.5 ms ±   0.7 ms    [User: 51.0 ms, System: 38.8 ms]\n  Range (min … max):    89.2 ms …  92.3 ms    32 runs\n\nSummary\n  ./git log --format=%H ran\n    1.01 ± 0.01 times faster than ./git_main log --format=%H\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n bisect.c                      |  2 +-\n builtin/bisect.c              |  2 +-\n builtin/cat-file.c            |  5 ++---\n builtin/replace.c             |  2 +-\n convert.c                     |  2 +-\n fsck.c                        |  2 +-\n hex.c                         | 10 ++++++++++\n hex.h                         |  5 +++++\n pretty.c                      |  8 ++++----\n refs.c                        |  2 +-\n sequencer.c                   |  4 ++--\n shallow.c                     |  2 +-\n tools/coccinelle/strbuf.cocci |  6 ++++++\n transport-helper.c            |  2 +-\n 14 files changed, 37 insertions(+), 17 deletions(-)\n\ndiff --git a/bisect.c b/bisect.c\nindex ef17a442e5..e67226a6dc 100644\n--- a/bisect.c\n+++ b/bisect.c\n@@ -512,7 +512,7 @@ static char *join_oid_array_hex(struct oid_array *array, char delim)\n \tint i;\n \n \tfor (i = 0; i < array->nr; i++) {\n-\t\tstrbuf_addstr(&joined_hexs, oid_to_hex(array->oid + i));\n+\t\tstrbuf_add_oid_hex(&joined_hexs, array->oid + i);\n \t\tif (i + 1 < array->nr)\n \t\t\tstrbuf_addch(&joined_hexs, delim);\n \t}\ndiff --git a/builtin/bisect.c b/builtin/bisect.c\nindex 4520e585d0..0f679e7af9 100644\n--- a/builtin/bisect.c\n+++ b/builtin/bisect.c\n@@ -833,7 +833,7 @@ static enum bisect_error bisect_start(struct bisect_terms *terms, int argc,\n \t\tif (!repo_get_oid(the_repository, head, &head_oid) &&\n \t\t    !starts_with(head, \"refs/heads/\")) {\n \t\t\tstrbuf_reset(&start_head);\n-\t\t\tstrbuf_addstr(&start_head, oid_to_hex(&head_oid));\n+\t\t\tstrbuf_add_oid_hex(&start_head, &head_oid);\n \t\t} else if (!repo_get_oid(the_repository, head, &head_oid) &&\n \t\t\t   skip_prefix(head, \"refs/heads/\", &head)) {\n \t\t\tstrbuf_addstr(&start_head, head);\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex d9fbad5358..f015e5f415 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -320,7 +320,7 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,\n {\n \tif (is_atom(\"objectname\", atom, len)) {\n \t\tif (!data->mark_query)\n-\t\t\tstrbuf_addstr(sb, oid_to_hex(&data->oid));\n+\t\t\tstrbuf_add_oid_hex(sb, &data->oid);\n \t} else if (is_atom(\"objecttype\", atom, len)) {\n \t\tif (data->mark_query)\n \t\t\tdata->info.typep = &data->type;\n@@ -345,8 +345,7 @@ static int expand_atom(struct strbuf *sb, const char *atom, int len,\n \t\tif (data->mark_query)\n \t\t\tdata->info.delta_base_oid = &data->delta_base_oid;\n \t\telse\n-\t\t\tstrbuf_addstr(sb,\n-\t\t\t\t      oid_to_hex(&data->delta_base_oid));\n+\t\t\tstrbuf_add_oid_hex(sb, &data->delta_base_oid);\n \t} else if (is_atom(\"objectmode\", atom, len)) {\n \t\tif (!data->mark_query && !(S_IFINVALID == data->mode))\n \t\t\tstrbuf_addf(sb, \"%06o\", data->mode);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex 4c62c5ab58..aed6b2c8de 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -127,7 +127,7 @@ static int for_each_replace_name(const char **argv, each_replace_name_fn fn)\n \t\t}\n \n \t\tstrbuf_setlen(&ref, base_len);\n-\t\tstrbuf_addstr(&ref, oid_to_hex(&oid));\n+\t\tstrbuf_add_oid_hex(&ref, &oid);\n \t\tfull_hex = ref.buf + base_len;\n \n \t\tif (refs_read_ref(get_main_ref_store(the_repository), ref.buf, &oid)) {\ndiff --git a/convert.c b/convert.c\nindex eae36c8a59..036506842c 100644\n--- a/convert.c\n+++ b/convert.c\n@@ -1239,7 +1239,7 @@ static int ident_to_worktree(const char *src, size_t len,\n \n \t\t/* step 4: substitute */\n \t\tstrbuf_addstr(buf, \"Id: \");\n-\t\tstrbuf_addstr(buf, oid_to_hex(&oid));\n+\t\tstrbuf_add_oid_hex(buf, &oid);\n \t\tstrbuf_addstr(buf, \" $\");\n \t}\n \tstrbuf_add(buf, src, len);\ndiff --git a/fsck.c b/fsck.c\nindex b72200c352..b4ffee6a04 100644\n--- a/fsck.c\n+++ b/fsck.c\n@@ -344,7 +344,7 @@ const char *fsck_describe_object(struct fsck_options *options,\n \tbuf = bufs + b;\n \tb = (b + 1) % ARRAY_SIZE(bufs);\n \tstrbuf_reset(buf);\n-\tstrbuf_addstr(buf, oid_to_hex(oid));\n+\tstrbuf_add_oid_hex(buf, oid);\n \tif (name)\n \t\tstrbuf_addf(buf, \" (%s)\", name);\n \ndiff --git a/hex.c b/hex.c\nindex bc756722ca..f02832140d 100644\n--- a/hex.c\n+++ b/hex.c\n@@ -3,6 +3,7 @@\n #include \"git-compat-util.h\"\n #include \"hash.h\"\n #include \"hex.h\"\n+#include \"strbuf.h\"\n \n static int get_hash_hex_algop(const char *hex, unsigned char *hash,\n \t\t\t      const struct git_hash_algo *algop)\n@@ -122,3 +123,12 @@ char *oid_to_hex(const struct object_id *oid)\n {\n \treturn hash_to_hex_algop(oid->hash, &hash_algos[oid->algo]);\n }\n+\n+void strbuf_add_oid_hex(struct strbuf *sb, const struct object_id *oid)\n+{\n+\tconst struct git_hash_algo *algop = oid->algo ?\n+\t\t&hash_algos[oid->algo] : the_hash_algo;\n+\tstrbuf_grow(sb, algop->hexsz);\n+\thash_to_hex_algop_r(sb->buf + sb->len, oid->hash, algop);\n+\tstrbuf_setlen(sb, sb->len + algop->hexsz);\n+}\ndiff --git a/hex.h b/hex.h\nindex 1e9a65d83a..f15c7e2220 100644\n--- a/hex.h\n+++ b/hex.h\n@@ -33,6 +33,11 @@ char *oid_to_hex_r(char *out, const struct object_id *oid);\n char *hash_to_hex_algop(const unsigned char *hash, const struct git_hash_algo *);\t/* static buffer result! */\n char *oid_to_hex(const struct object_id *oid);\t\t\t\t\t\t/* same static buffer */\n \n+struct strbuf;\n+\n+/* Apply oid_to_hex_r() to a strbuf to append the hexadecimal hash. */\n+void strbuf_add_oid_hex(struct strbuf *sb, const struct object_id *oid);\n+\n /*\n  * Parse a 40-character hexadecimal object ID starting from hex, updating the\n  * pointer specified by end when parsing stops.  The resulting object ID is\ndiff --git a/pretty.c b/pretty.c\nindex 814803980b..2684223946 100644\n--- a/pretty.c\n+++ b/pretty.c\n@@ -662,7 +662,7 @@ static void add_merge_info(const struct pretty_print_context *pp,\n \t\tif (pp->abbrev)\n \t\t\tstrbuf_add_unique_abbrev(sb, oidp, pp->abbrev);\n \t\telse\n-\t\t\tstrbuf_addstr(sb, oid_to_hex(oidp));\n+\t\t\tstrbuf_add_oid_hex(sb, oidp);\n \t\tparent = parent->next;\n \t}\n \tstrbuf_addch(sb, '\\n');\n@@ -1567,7 +1567,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \tswitch (placeholder[0]) {\n \tcase 'H':\t\t/* commit hash */\n \t\tstrbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_COMMIT));\n-\t\tstrbuf_addstr(sb, oid_to_hex(&commit->object.oid));\n+\t\tstrbuf_add_oid_hex(sb, &commit->object.oid);\n \t\tstrbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));\n \t\treturn 1;\n \tcase 'h':\t\t/* abbreviated commit hash */\n@@ -1577,7 +1577,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\tstrbuf_addstr(sb, diff_get_color(c->auto_color, DIFF_RESET));\n \t\treturn 1;\n \tcase 'T':\t\t/* tree hash */\n-\t\tstrbuf_addstr(sb, oid_to_hex(get_commit_tree_oid(commit)));\n+\t\tstrbuf_add_oid_hex(sb, get_commit_tree_oid(commit));\n \t\treturn 1;\n \tcase 't':\t\t/* abbreviated tree hash */\n \t\tstrbuf_add_unique_abbrev(sb,\n@@ -1588,7 +1588,7 @@ static size_t format_commit_one(struct strbuf *sb, /* in UTF-8 */\n \t\tfor (p = commit->parents; p; p = p->next) {\n \t\t\tif (p != commit->parents)\n \t\t\t\tstrbuf_addch(sb, ' ');\n-\t\t\tstrbuf_addstr(sb, oid_to_hex(&p->item->object.oid));\n+\t\t\tstrbuf_add_oid_hex(sb, &p->item->object.oid);\n \t\t}\n \t\treturn 1;\n \tcase 'p':\t\t/* abbreviated parent hashes */\ndiff --git a/refs.c b/refs.c\nindex 844785219d..ee92f18d41 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2498,7 +2498,7 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref,\n \tif (referent && refs_read_symbolic_ref(refs, ref, referent) == NOT_A_SYMREF) {\n \t\tstruct object_id oid;\n \t\tif (!refs_read_ref(refs, ref, &oid)) {\n-\t\t\tstrbuf_addstr(referent, oid_to_hex(&oid));\n+\t\t\tstrbuf_add_oid_hex(referent, &oid);\n \t\t\tret = NOT_A_SYMREF;\n \t\t}\n \t}\ndiff --git a/sequencer.c b/sequencer.c\nindex b7d8dca47f..b4df04b672 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -2223,7 +2223,7 @@ static void refer_to_commit(struct repository *r, struct strbuf *msgbuf,\n \t\trepo_format_commit_message(r, commit,\n \t\t\t\t\t   \"%h (%s, %ad)\", msgbuf, &ctx);\n \t} else {\n-\t\tstrbuf_addstr(msgbuf, oid_to_hex(&commit->object.oid));\n+\t\tstrbuf_add_oid_hex(msgbuf, &commit->object.oid);\n \t}\n }\n \n@@ -2395,7 +2395,7 @@ static int do_pick_commit(struct repository *r,\n \t\t\tif (!has_conforming_footer(&ctx->message, NULL, 0))\n \t\t\t\tstrbuf_addch(&ctx->message, '\\n');\n \t\t\tstrbuf_addstr(&ctx->message, cherry_picked_prefix);\n-\t\t\tstrbuf_addstr(&ctx->message, oid_to_hex(&commit->object.oid));\n+\t\t\tstrbuf_add_oid_hex(&ctx->message, &commit->object.oid);\n \t\t\tstrbuf_addstr(&ctx->message, \")\\n\");\n \t\t}\n \t\tif (!is_fixup(command))\ndiff --git a/shallow.c b/shallow.c\nindex a8ad92e303..b4b4e2e32a 100644\n--- a/shallow.c\n+++ b/shallow.c\n@@ -395,7 +395,7 @@ static int write_shallow_commits_1(struct strbuf *out, int use_pack_protocol,\n \tif (!extra)\n \t\treturn data.count;\n \tfor (size_t i = 0; i < extra->nr; i++) {\n-\t\tstrbuf_addstr(out, oid_to_hex(extra->oid + i));\n+\t\tstrbuf_add_oid_hex(out, extra->oid + i);\n \t\tstrbuf_addch(out, '\\n');\n \t\tdata.count++;\n \t}\ndiff --git a/tools/coccinelle/strbuf.cocci b/tools/coccinelle/strbuf.cocci\nindex f586128329..667903d1d4 100644\n--- a/tools/coccinelle/strbuf.cocci\n+++ b/tools/coccinelle/strbuf.cocci\n@@ -78,3 +78,9 @@ struct strbuf SB;\n @@\n - SB.buf ? SB.buf : \"\"\n + SB.buf\n+\n+@@\n+expression SB, OID;\n+@@\n+- strbuf_addstr(SB, oid_to_hex(OID))\n++ strbuf_add_oid_hex(SB, OID)\ndiff --git a/transport-helper.c b/transport-helper.c\nindex 4614036c99..4a54769789 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -1051,7 +1051,7 @@ static int push_refs_with_push(struct transport *transport,\n \t\t\tif (ref->peer_ref)\n \t\t\t\tstrbuf_addstr(&buf, ref->peer_ref->name);\n \t\t\telse\n-\t\t\t\tstrbuf_addstr(&buf, oid_to_hex(&ref->new_oid));\n+\t\t\t\tstrbuf_add_oid_hex(&buf, &ref->new_oid);\n \t\t}\n \t\tstrbuf_addch(&buf, ':');\n \t\tstrbuf_addstr(&buf, ref->name);\n-- \n2.54.0\n"},{"id":"543248","messageId":"20260513160155.GA103037@coredump.intra.peff.net","threadId":"65633","inReplyTo":"183aa0fd-d455-4ec9-9c42-d511fac8b3e4@web.de","subject":"Re: [PATCH] hex: add and use strbuf_add_oid_hex()","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2026-05-13T16:01:55Z","receivedAt":"2026-05-13T16:01:57Z","isPatch":true,"body":"On Wed, May 13, 2026 at 05:49:11PM +0200, René Scharfe wrote:\n\n> Add a function for adding the full hexadecimal hash value of an object\n> ID to a strbuf.  It's thread-safe and slightly more efficient than using\n> strbuf_addstr() with oid_to_hex() because it doesn't have to determine\n> the length of the string or copy it from the intermediate static buffer.\n> \n> Add and apply a semantic patch to use it throughout the code base.\n> \n> I get a tiny speedup for git log showing a single hash per commit:\n> \n> Benchmark 1: ./git_main log --format=%H\n>   Time (mean ± σ):      91.2 ms ±   0.7 ms    [User: 51.9 ms, System: 38.6 ms]\n>   Range (min … max):    89.8 ms …  92.6 ms    31 runs\n> \n> Benchmark 2: ./git log --format=%H\n>   Time (mean ± σ):      90.5 ms ±   0.7 ms    [User: 51.0 ms, System: 38.8 ms]\n>   Range (min … max):    89.2 ms …  92.3 ms    32 runs\n\nProbably the most extreme benchmark would be:\n\n  git cat-file --batch-all-objects --batch-check='%(objectname)'\n\nwhich is really just dumping the oids from packfiles. I got ~3% speedup,\nthough like yours it's within the run-to-run noise.\n\nI think this is worth doing solely for removing more instances of global\nbuffers, though.\n\n-Peff\n"},{"id":"543254","messageId":"a427dd4d-773c-45e4-87a7-3adb6f04dfb3@web.de","threadId":"65633","inReplyTo":"20260513160155.GA103037@coredump.intra.peff.net","subject":"Re: [PATCH] hex: add and use strbuf_add_oid_hex()","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2026-05-13T16:55:47Z","receivedAt":"2026-05-13T16:55:49Z","isPatch":true,"body":"On 5/13/26 6:01 PM, Jeff King wrote:\n> On Wed, May 13, 2026 at 05:49:11PM +0200, René Scharfe wrote:\n> \n>> Add a function for adding the full hexadecimal hash value of an object\n>> ID to a strbuf.  It's thread-safe and slightly more efficient than using\n>> strbuf_addstr() with oid_to_hex() because it doesn't have to determine\n>> the length of the string or copy it from the intermediate static buffer.\n>>\n>> Add and apply a semantic patch to use it throughout the code base.\n>>\n>> I get a tiny speedup for git log showing a single hash per commit:\n>>\n>> Benchmark 1: ./git_main log --format=%H\n>>   Time (mean ± σ):      91.2 ms ±   0.7 ms    [User: 51.9 ms, System: 38.6 ms]\n>>   Range (min … max):    89.8 ms …  92.6 ms    31 runs\n>>\n>> Benchmark 2: ./git log --format=%H\n>>   Time (mean ± σ):      90.5 ms ±   0.7 ms    [User: 51.0 ms, System: 38.8 ms]\n>>   Range (min … max):    89.2 ms …  92.3 ms    32 runs\n> \n> Probably the most extreme benchmark would be:\n> \n>   git cat-file --batch-all-objects --batch-check='%(objectname)'\n> \n> which is really just dumping the oids from packfiles. I got ~3% speedup,\n> though like yours it's within the run-to-run noise.\n\nHmm, should've lead with that; on an Apple M1:\n\nBenchmark 1: ./git_main cat-file --batch-all-objects --batch-check='%(objectname)'\n  Time (mean ± σ):     117.9 ms ±   0.2 ms    [User: 111.3 ms, System: 5.6 ms]\n  Range (min … max):   117.5 ms … 118.5 ms    24 runs\n\nBenchmark 2: ./git cat-file --batch-all-objects --batch-check='%(objectname)'\n  Time (mean ± σ):     109.9 ms ±   0.2 ms    [User: 103.2 ms, System: 5.6 ms]\n  Range (min … max):   109.5 ms … 110.8 ms    26 runs\n\nSummary\n  ./git cat-file --batch-all-objects --batch-check='%(objectname)' ran\n    1.07 ± 0.00 times faster than ./git_main cat-file --batch-all-objects --batch-check='%(objectname)'\n\n... and on an M5:\n\nBenchmark 1: ./git_main cat-file --batch-all-objects --batch-check='%(objectname)'\n  Time (mean ± σ):      76.8 ms ±   1.9 ms    [User: 73.0 ms, System: 3.2 ms]\n  Range (min … max):    73.7 ms …  80.7 ms    38 runs\n\nBenchmark 2: ./git cat-file --batch-all-objects --batch-check='%(objectname)'\n  Time (mean ± σ):      70.8 ms ±   1.0 ms    [User: 66.9 ms, System: 3.3 ms]\n  Range (min … max):    69.4 ms …  73.7 ms    41 runs\n\nSummary\n  ./git cat-file --batch-all-objects --batch-check='%(objectname)' ran\n    1.08 ± 0.03 times faster than ./git_main cat-file --batch-all-objects --batch-check='%(objectname)'‚\n\nRené\n\n"}]}