From 5b5432f6edbd27ab3d020bf99e71dd8329899dcd Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Sun, 1 Nov 2026 12:09:32 -1000 Subject: Offer blame only where it works The blame page answers 404 for a symlink, since git blames a regular file alone, and it refuses a binary file and one past max-blob-size, yet a tree listing offered blame for all of them, and so did a symlink's own page. Each row already knows its size, and git calls a file binary by its first 8000 bytes alone, which a stream reads without inflating the rest. With blame on, a listing of 2,000 delta-packed files went from about 11 to 15 ms. --- source/ui-tree.c | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) (limited to 'source/ui-tree.c') diff --git a/source/ui-tree.c b/source/ui-tree.c index 8e17cc4..63b8984 100644 --- a/source/ui-tree.c +++ b/source/ui-tree.c @@ -17,6 +17,8 @@ #include "ui-shared.h" #include "ui-tree.h" +#include + #define HEXDUMP_ROW_BYTES 32 #define HEXDUMP_ROW_HALF (HEXDUMP_ROW_BYTES / 2) #define HEXDUMP_GAP_WIDTH 4 @@ -25,6 +27,10 @@ // max-blob-size would make a page several times its own size. #define HEXDUMP_MAX_BYTES (64 * 1024) +// How far into a buffer buffer_is_binary looks, the window git judges a file +// binary by. +#define BINARY_SNIFF_BYTES 8000 + // How many single-child directories a listing row follows into one path. #define MAX_DIR_CHAIN_LEVELS 15 @@ -147,7 +153,7 @@ static void print_binary_buffer(char *buf, size_t size, const char *path, const * false means a complete error page went out in place of the blob, so nothing * may be added to it. */ -static bool print_object(const struct object_id *oid, const char *path, +static bool print_object(const struct object_id *oid, unsigned mode, const char *path, const char *filename, const char *rev) { enum object_type type; @@ -182,7 +188,7 @@ static bool print_object(const struct object_id *oid, const char *path, cgit_print_layout_start(); htmlf("
blob: %s (", oid_to_hex(oid)); cgit_plain_link("plain", NULL, NULL, ctx.qry.head, rev, path); - if (ctx.repo->enable_blame && !is_binary) { + if (ctx.repo->enable_blame && S_ISREG(mode) && !is_binary) { html(") ("); cgit_blame_link("blame", NULL, NULL, ctx.qry.head, rev, path); } @@ -259,6 +265,26 @@ static void print_dir_chain(const struct object_id *oid, const char *name, strbuf_setlen(fullpath, initial_length); } +/* + * Whether git counts the blob as binary, read through a stream so that only + * the bytes buffer_is_binary looks at are inflated, however large the blob. + */ +static bool blob_starts_binary(const struct object_id *oid) +{ + char buf[BINARY_SNIFF_BYTES]; + struct odb_stream *stream; + size_t got = 0; + ssize_t n; + + stream = odb_stream_from_object(the_repository->objects, oid, NULL); + if (!stream) + return false; + while (got < sizeof(buf) && (n = odb_stream_read(stream, buf + got, sizeof(buf) - got)) > 0) + got += n; + odb_stream_close(stream); + return buffer_is_binary(buf, got); +} + static void print_ls_row(const struct object_id *oid, const char *name, unsigned mode, struct walk_tree_context *walk) { @@ -345,7 +371,14 @@ static void print_ls_row(const struct object_id *oid, const char *name, cgit_stats_link("stats", NULL, "button", ctx.qry.head, fullpath.buf); if (!S_ISGITLINK(mode)) cgit_plain_link("plain", NULL, "button", ctx.qry.head, walk->rev, fullpath.buf); - if (!S_ISDIR(mode) && !S_ISGITLINK(mode) && ctx.repo->enable_blame) + // The blame page refuses a binary file and one past max-blob-size, so + // neither is offered it. + if ( + S_ISREG(mode) && + ctx.repo->enable_blame && + !cgit_blob_too_large(size) && + !blob_starts_binary(oid) + ) cgit_blame_link("blame", NULL, "button", ctx.qry.head, walk->rev, fullpath.buf); html("\n"); @@ -438,7 +471,7 @@ static int walk_tree(const struct object_id *oid, struct strbuf *base, ls_head(); return READ_TREE_RECURSIVE; } else { - bool shown = print_object(oid, fullpath.buf, pathname, walk->rev); + bool shown = print_object(oid, mode, fullpath.buf, pathname, walk->rev); walk->state = shown ? WALK_BLOB_SHOWN : WALK_ERROR_SHOWN; strbuf_release(&fullpath); -- cgit 2.12.1