From 62786f9a509a06ac031c066837d7a2285469677b Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Sat, 8 Aug 2026 12:49:54 -1000 Subject: Emit generated runs in batches, not a write each --- source/ui-blame.c | 64 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 11 deletions(-) (limited to 'source/ui-blame.c') diff --git a/source/ui-blame.c b/source/ui-blame.c index 418e3ae..d6779c0 100644 --- a/source/ui-blame.c +++ b/source/ui-blame.c @@ -16,6 +16,38 @@ #include "blame.h" +/* Blame coalesces neighbouring lines from one commit, but a commit that + * touched several separate parts of the file still comes back once per part. + * Each of those repeats a full commit parse, so the rendered detail is kept + * and looked up by object id. */ +static struct string_list suspect_details = STRING_LIST_INIT_DUP; + +static void free_suspect_details(void) +{ + struct string_list_item *item; + + for_each_string_list_item(item, &suspect_details) + free(item->util); + string_list_clear(&suspect_details, 0); +} + +/* Write out a run of one repeated character. A single blame entry can cover + * the whole file, so the run is handed on in batches rather than built whole. */ +static void emit_chars(char ch, unsigned long count) +{ + struct strbuf run = STRBUF_INIT; + + while (count) { + unsigned long n = count < HTML_BATCH ? count : HTML_BATCH; + + strbuf_addchars(&run, ch, n); + html_raw(run.buf, run.len); + strbuf_reset(&run); + count -= n; + } + strbuf_release(&run); +} + static char *emit_suspect_detail(struct blame_origin *suspect) { struct commitinfo *info; @@ -47,7 +79,6 @@ static void emit_blame_entry_hash(struct blame_entry *ent) { struct blame_origin *suspect = ent->suspect; struct object_id *oid = &suspect->commit->object.oid; - unsigned long line = 0; char *detail = emit_suspect_detail(suspect); html(""); @@ -65,28 +96,36 @@ static void emit_blame_entry_hash(struct blame_entry *ent) suspect->path); } - while (line++ < ent->num_lines) - html("\n"); + // Batched rather than one write per line of the entry. + emit_chars('\n', ent->num_lines); } static void emit_blame_entry_linenumber(struct blame_entry *ent) { const char *numberfmt = "%1$d\n"; - - unsigned long lineno = ent->lno; - while (lineno < ent->lno + ent->num_lines) - htmlf(numberfmt, ++lineno); + struct strbuf numbers = STRBUF_INIT; + int lineno = ent->lno; + + // Batched rather than a formatted write per line of the entry. + while (lineno < ent->lno + ent->num_lines) { + strbuf_addf(&numbers, numberfmt, ++lineno); + if (numbers.len >= HTML_BATCH) { + html_raw(numbers.buf, numbers.len); + strbuf_reset(&numbers); + } + } + html_raw(numbers.buf, numbers.len); + strbuf_release(&numbers); } static void emit_blame_entry_line_background(struct blame_scoreboard *sb, struct blame_entry *ent) { - unsigned long line; + int line; size_t len, maxlen = 2; const char* pos, *endpos; for (line = ent->lno; line < ent->lno + ent->num_lines; line++) { - html("\n"); pos = blame_nth_line(sb, line); endpos = blame_nth_line(sb, line + 1); len = 0; @@ -99,8 +138,11 @@ static void emit_blame_entry_line_background(struct blame_scoreboard *sb, maxlen = len; } - for (len = 0; len < maxlen - 1; len++) - html(" "); + // The widest line decides the padding, so the entry has to be measured + // before any of it is written. The newlines do not depend on that + // measurement, so both runs go out batched once it is known. + emit_chars('\n', ent->num_lines); + emit_chars(' ', maxlen - 1); } struct walk_tree_context { -- cgit v2.8.0