diff options
context:
space:
mode:
authorBryce Kwon <bryce@brycekwon.com>
committerBryce Kwon <bryce@brycekwon.com>
commit
parent
tree
download
Stat oversized diffs instead of inlining them
Diffstat (limited to '')
-rw-r--r--assets/cgit.css10
-rw-r--r--cgitrc.5.txt12
-rw-r--r--examples/cgitrc10
-rw-r--r--source/cgit.c6
-rw-r--r--source/cgit.h2
-rw-r--r--source/ui-diff.c48
-rwxr-xr-xtests/t0201-limits.sh45
7 files changed, 130 insertions, 3 deletions
diff --git a/assets/cgit.css b/assets/cgit.css
index b55f002..7f7535d 100644
--- a/assets/cgit.css
+++ b/assets/cgit.css
@@ -1013,6 +1013,16 @@ div#cgit table.diff td div.del {
color: var(--del);
}
+/* Notice shown in place of a diff that exceeds max-diff-lines or
+ * max-diff-files, pointing at the page that renders it in full. */
+div#cgit div.truncated,
+div#cgit table.ssdiff td.truncated {
+ color: var(--muted);
+ font-family: var(--font-sans);
+ white-space: normal;
+ padding: 0.4em 0;
+}
+
div#cgit .oid {
font-family: var(--font-mono);
font-size: 90%;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 18916f9..e56cbae 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -298,6 +298,18 @@ max-commit-count::
Specifies the number of entries to list per page in "log" view. Default
value: "50".
+max-diff-files::
+ If a commit changes more files than this, the "commit" and "diff"
+ views show only the diffstat, where each file links to its own diff
+ page. The "rawdiff" and "patch" views are never limited. Default
+ value: "200". Set to "0" to always render every file inline.
+
+max-diff-lines::
+ If a single file's diff spans more lines than this, the whole-commit
+ views print a note in its place linking to the file's own diff page,
+ which always renders in full. Default value: "1000". Set to "0" to
+ always render oversized files inline.
+
max-message-length::
Specifies the maximum number of commit message characters to display in
"log" view. Default value: "80".
diff --git a/examples/cgitrc b/examples/cgitrc
index e86f43c..62b10b5 100644
--- a/examples/cgitrc
+++ b/examples/cgitrc
@@ -341,6 +341,16 @@ max-repo-count=50
# Default is 50.
max-commit-count=50
+# Commits changing more files than this render as a diffstat, where each file
+# links to its own diff page. A value of 0 always renders everything inline.
+# Value is an integer. Default is 200.
+max-diff-files=200
+
+# A file whose diff spans more lines than this is replaced by a link to its
+# own diff page in whole-commit views. A value of 0 always renders it inline.
+# Value is an integer. Default is 1000.
+max-diff-lines=1000
+
# Branches and tags listed per section on the refs page, and per page on the
# dedicated branch and tag pages. A value of 0 lists everything on one page.
# Value is an integer. Default is 200.
diff --git a/source/cgit.c b/source/cgit.c
index 5fcd17f..d0d454d 100644
--- a/source/cgit.c
+++ b/source/cgit.c
@@ -257,6 +257,10 @@ static void config_cb(const char *name, const char *value)
ctx.cfg.max_repodesc_len = atoi(value);
else if (!strcmp(name, "max-blob-size"))
ctx.cfg.max_blob_size = atoi(value);
+ else if (!strcmp(name, "max-diff-files"))
+ ctx.cfg.max_diff_files = atoi(value);
+ else if (!strcmp(name, "max-diff-lines"))
+ ctx.cfg.max_diff_lines = atoi(value);
else if (!strcmp(name, "max-ref-count"))
ctx.cfg.max_ref_count = atoi(value);
else if (!strcmp(name, "max-repo-count")) {
@@ -418,6 +422,8 @@ static void prepare_context(void)
ctx.cfg.enable_git_config = 0;
ctx.cfg.max_repo_count = 50;
ctx.cfg.max_commit_count = 50;
+ ctx.cfg.max_diff_files = 200; /* larger commits render stat only */
+ ctx.cfg.max_diff_lines = 1000; /* larger file diffs link out */
ctx.cfg.max_msg_len = 80;
ctx.cfg.max_ref_count = 200; /* refs beyond this paginate */
ctx.cfg.max_repodesc_len = 80;
diff --git a/source/cgit.h b/source/cgit.h
index 1b28023..727067a 100644
--- a/source/cgit.h
+++ b/source/cgit.h
@@ -249,6 +249,8 @@ struct cgit_config {
int max_atom_items;
int max_repo_count;
int max_commit_count;
+ int max_diff_files;
+ int max_diff_lines;
int max_msg_len;
int max_ref_count;
int max_repodesc_len;
diff --git a/source/ui-diff.c b/source/ui-diff.c
index baaa1f8..b34f7b3 100644
--- a/source/ui-diff.c
+++ b/source/ui-diff.c
@@ -40,6 +40,11 @@ static int use_ssdiff = 0;
static struct diff_filepair *current_filepair;
static const char *current_prefix;
+/* Caps apply only to whole-commit views. A single-file diff page must
+ * always render fully, since it is where the capped views link to. */
+static int cap_diffs;
+static int item_idx;
+
struct diff_filespec *cgit_get_current_old_file(void)
{
return current_filepair->one;
@@ -299,11 +304,13 @@ static void filepair_cb(struct diff_filepair *pair)
unsigned long old_size = 0;
unsigned long new_size = 0;
int binary = 0;
+ int idx;
linediff_fn print_line_fn = print_line;
if (!show_filepair(pair))
return;
+ idx = item_idx++;
current_filepair = pair;
if (use_ssdiff) {
cgit_ssdiff_header_begin();
@@ -313,6 +320,29 @@ static void filepair_cb(struct diff_filepair *pair)
&pair->two->oid, pair->two->path, pair->two->mode);
if (use_ssdiff)
cgit_ssdiff_header_end();
+
+ /* The diffstat pass counted this file's lines. Over the budget,
+ * print the header and hand off to the single-file diff page
+ * instead of rendering the whole thing inline. */
+ if (cap_diffs && ctx.cfg.max_diff_lines > 0 && idx < files &&
+ !items[idx].binary && items[idx].added + items[idx].removed >
+ (unsigned int)ctx.cfg.max_diff_lines) {
+ if (use_ssdiff)
+ html("<tr><td colspan='4' class='truncated'>");
+ else
+ html("<div class='truncated'>");
+ html("This diff is too large to be rendered inline. ");
+ cgit_diff_link("View it on its own page", NULL, NULL,
+ ctx.qry.head, ctx.qry.oid, ctx.qry.oid2,
+ items[idx].new_path);
+ html(".");
+ if (use_ssdiff) {
+ html("</td></tr>");
+ cgit_ssdiff_footer();
+ } else
+ html("</div>");
+ return;
+ }
if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
if (S_ISGITLINK(pair->one->mode)) {
char *l = fmt("-Subproject %s", oid_to_hex(&pair->one->oid));
@@ -406,6 +436,8 @@ void cgit_print_diff(const char *new_rev, const char *old_rev,
current_prefix = NULL;
}
+ cap_diffs = !prefix && !current_prefix;
+
if (!new_rev)
new_rev = ctx.qry.head;
if (repo_get_oid(the_repository, new_rev, new_rev_oid)) {
@@ -493,12 +525,28 @@ void cgit_print_diff(const char *new_rev, const char *old_rev,
return;
}
+ /* A commit touching more files than max-diff-files stops at the
+ * stat above, where every file links to its own diff page. */
+ if (cap_diffs && ctx.cfg.max_diff_files > 0 &&
+ files > ctx.cfg.max_diff_files) {
+ html("<div class='truncated'>");
+ html("This diff is too large to be rendered inline. "
+ "Follow a file above, or the ");
+ cgit_patch_link("patch", NULL, NULL, NULL, ctx.qry.oid, NULL);
+ html(" link for the whole commit.");
+ html("</div>");
+ if (show_ctrls)
+ cgit_print_layout_end();
+ return;
+ }
+
if (use_ssdiff) {
html("<table summary='ssdiff' class='ssdiff'>");
} else {
html("<table summary='diff' class='diff'>");
html("<tr><td>");
}
+ item_idx = 0;
cgit_diff_tree(old_rev_oid, new_rev_oid, filepair_cb, prefix,
ctx.qry.ignorews);
if (!use_ssdiff)
diff --git a/tests/t0201-limits.sh b/tests/t0201-limits.sh
index e7f74c6..b7d82de 100755
--- a/tests/t0201-limits.sh
+++ b/tests/t0201-limits.sh
@@ -1,9 +1,10 @@
#!/bin/sh
-test_description='Check the ref listing limits'
+test_description='Check the ref listing and diff size limits'
. ./setup.sh
-# A repo with several branches and tags, and a config that pins a tiny limit.
+# A repo with several branches and tags, one commit with an oversized file
+# diff beside a small one, and configs that pin tiny limits.
test_expect_success 'set up limit fixtures' '
mkrepo repos/limits 1 &&
(
@@ -22,9 +23,18 @@ test_expect_success 'set up limit fixtures' '
echo "virtual-root=/" &&
echo "cache-size=0" &&
echo "max-ref-count=2" &&
+ echo "max-diff-lines=20" &&
+ echo "max-diff-files=0" &&
echo "repo.url=limits" &&
echo "repo.path=$PWD/repos/limits/.git"
- } >limitrc
+ } >limitrc &&
+ {
+ echo "virtual-root=/" &&
+ echo "cache-size=0" &&
+ echo "max-diff-files=1" &&
+ echo "repo.url=limits" &&
+ echo "repo.path=$PWD/repos/limits/.git"
+ } >limitfilesrc
'
limitq() { CGIT_CONFIG="$PWD/limitrc" QUERY_STRING="$1" cgit; }
@@ -57,4 +67,33 @@ test_expect_success 'tag page paginates independently' '
! grep "log/?h=" tmp
'
+# --- Oversized file diffs link out instead of rendering inline --------------
+test_expect_success 'oversized file diff is replaced by a link' '
+ limitq "url=limits/commit/" >tmp &&
+ grep "too large to be rendered inline" tmp &&
+ grep "View it on its own page" tmp
+'
+
+test_expect_success 'small file in the same commit still renders inline' '
+ grep "class=.add.>+int y;" tmp
+'
+
+test_expect_success 'the single-file diff page always renders in full' '
+ limitq "url=limits/diff/big.c" >tmp &&
+ grep "class=.hunk." tmp &&
+ ! grep "too large to be rendered inline" tmp
+'
+
+# --- Commits over max-diff-files fall back to the diffstat ------------------
+test_expect_success 'a commit changing too many files shows stat only' '
+ CGIT_CONFIG="$PWD/limitfilesrc" QUERY_STRING="url=limits/commit/" cgit >tmp &&
+ grep "too large to be rendered inline" tmp &&
+ ! grep "class=.hunk." tmp
+'
+
+test_expect_success 'the single-file page is not limited by max-diff-files' '
+ CGIT_CONFIG="$PWD/limitfilesrc" QUERY_STRING="url=limits/diff/small.c" cgit >tmp &&
+ grep "class=.hunk." tmp
+'
+
test_done