aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile1
-rw-r--r--cgit.c12
-rw-r--r--cgit.css104
-rw-r--r--cgit.h3
-rw-r--r--cgitrc.5.txt8
-rwxr-xr-xfilters/syntax-highlighting.sh29
-rw-r--r--ui-commit.c11
-rw-r--r--ui-diff.c62
-rw-r--r--ui-log.c4
-rw-r--r--ui-refs.c2
-rw-r--r--ui-shared.c34
-rw-r--r--ui-shared.h5
-rw-r--r--ui-ssdiff.c369
-rw-r--r--ui-ssdiff.h13
-rw-r--r--ui-tree.c6
15 files changed, 619 insertions, 44 deletions
diff --git a/Makefile b/Makefile
index 4e101d3..f8a4d47 100644
--- a/Makefile
+++ b/Makefile
@@ -93,6 +93,7 @@ OBJECTS += ui-refs.o
93OBJECTS += ui-repolist.o 93OBJECTS += ui-repolist.o
94OBJECTS += ui-shared.o 94OBJECTS += ui-shared.o
95OBJECTS += ui-snapshot.o 95OBJECTS += ui-snapshot.o
96OBJECTS += ui-ssdiff.o
96OBJECTS += ui-stats.o 97OBJECTS += ui-stats.o
97OBJECTS += ui-summary.o 98OBJECTS += ui-summary.o
98OBJECTS += ui-tag.o 99OBJECTS += ui-tag.o
diff --git a/cgit.c b/cgit.c
index 29813cd..e46c00a 100644
--- a/cgit.c
+++ b/cgit.c
@@ -70,9 +70,9 @@ void repo_config(struct cgit_repo *repo, const char *name, const char *value)
70 repo->section = xstrdup(value); 70 repo->section = xstrdup(value);
71 else if (!strcmp(name, "readme") && value != NULL) { 71 else if (!strcmp(name, "readme") && value != NULL) {
72 if (*value == '/') 72 if (*value == '/')
73 ctx.repo->readme = xstrdup(value); 73 repo->readme = xstrdup(value);
74 else 74 else
75 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 75 repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
76 } else if (ctx.cfg.enable_filter_overrides) { 76 } else if (ctx.cfg.enable_filter_overrides) {
77 if (!strcmp(name, "about-filter")) 77 if (!strcmp(name, "about-filter"))
78 repo->about_filter = new_filter(value, 0); 78 repo->about_filter = new_filter(value, 0);
@@ -169,6 +169,8 @@ void config_cb(const char *name, const char *value)
169 ctx.cfg.max_msg_len = atoi(value); 169 ctx.cfg.max_msg_len = atoi(value);
170 else if (!strcmp(name, "max-repodesc-length")) 170 else if (!strcmp(name, "max-repodesc-length"))
171 ctx.cfg.max_repodesc_len = atoi(value); 171 ctx.cfg.max_repodesc_len = atoi(value);
172 else if (!strcmp(name, "max-blob-size"))
173 ctx.cfg.max_blob_size = atoi(value);
172 else if (!strcmp(name, "max-repo-count")) 174 else if (!strcmp(name, "max-repo-count"))
173 ctx.cfg.max_repo_count = atoi(value); 175 ctx.cfg.max_repo_count = atoi(value);
174 else if (!strcmp(name, "max-commit-count")) 176 else if (!strcmp(name, "max-commit-count"))
@@ -186,6 +188,8 @@ void config_cb(const char *name, const char *value)
186 ctx.cfg.summary_branches = atoi(value); 188 ctx.cfg.summary_branches = atoi(value);
187 else if (!strcmp(name, "summary-tags")) 189 else if (!strcmp(name, "summary-tags"))
188 ctx.cfg.summary_tags = atoi(value); 190 ctx.cfg.summary_tags = atoi(value);
191 else if (!strcmp(name, "side-by-side-diffs"))
192 ctx.cfg.ssdiff = atoi(value);
189 else if (!strcmp(name, "agefile")) 193 else if (!strcmp(name, "agefile"))
190 ctx.cfg.agefile = xstrdup(value); 194 ctx.cfg.agefile = xstrdup(value);
191 else if (!strcmp(name, "renamelimit")) 195 else if (!strcmp(name, "renamelimit"))
@@ -244,6 +248,8 @@ static void querystring_cb(const char *name, const char *value)
244 ctx.qry.showmsg = atoi(value); 248 ctx.qry.showmsg = atoi(value);
245 } else if (!strcmp(name, "period")) { 249 } else if (!strcmp(name, "period")) {
246 ctx.qry.period = xstrdup(value); 250 ctx.qry.period = xstrdup(value);
251 } else if (!strcmp(name, "ss")) {
252 ctx.qry.ssdiff = atoi(value);
247 } 253 }
248} 254}
249 255
@@ -274,6 +280,7 @@ static void prepare_context(struct cgit_context *ctx)
274 ctx->cfg.max_lock_attempts = 5; 280 ctx->cfg.max_lock_attempts = 5;
275 ctx->cfg.max_msg_len = 80; 281 ctx->cfg.max_msg_len = 80;
276 ctx->cfg.max_repodesc_len = 80; 282 ctx->cfg.max_repodesc_len = 80;
283 ctx->cfg.max_blob_size = 0;
277 ctx->cfg.max_stats = 0; 284 ctx->cfg.max_stats = 0;
278 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 285 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
279 ctx->cfg.renamelimit = -1; 286 ctx->cfg.renamelimit = -1;
@@ -285,6 +292,7 @@ static void prepare_context(struct cgit_context *ctx)
285 ctx->cfg.summary_branches = 10; 292 ctx->cfg.summary_branches = 10;
286 ctx->cfg.summary_log = 10; 293 ctx->cfg.summary_log = 10;
287 ctx->cfg.summary_tags = 10; 294 ctx->cfg.summary_tags = 10;
295 ctx->cfg.ssdiff = 0;
288 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 296 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
289 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 297 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
290 ctx->env.https = xstrdupn(getenv("HTTPS")); 298 ctx->env.https = xstrdupn(getenv("HTTPS"));
diff --git a/cgit.css b/cgit.css
index c47ebc9..0cb894a 100644
--- a/cgit.css
+++ b/cgit.css
@@ -162,6 +162,11 @@ table.list td a {
162 color: black; 162 color: black;
163} 163}
164 164
165table.list td a.ls-dir {
166 font-weight: bold;
167 color: #00f;
168}
169
165table.list td a:hover { 170table.list td a:hover {
166 color: #00f; 171 color: #00f;
167} 172}
@@ -601,3 +606,102 @@ table.hgraph div.bar {
601 background-color: #eee; 606 background-color: #eee;
602 height: 1em; 607 height: 1em;
603} 608}
609
610table.ssdiff {
611 width: 100%;
612}
613
614table.ssdiff td {
615 font-size: 75%;
616 font-family: monospace;
617 white-space: pre;
618 padding: 1px 4px 1px 4px;
619 border-left: solid 1px #aaa;
620 border-right: solid 1px #aaa;
621}
622
623table.ssdiff td.add {
624 color: black;
625 background: #cfc;
626 min-width: 50%;
627}
628
629table.ssdiff td.add_dark {
630 color: black;
631 background: #aca;
632 min-width: 50%;
633}
634
635table.ssdiff span.add {
636 background: #cfc;
637 font-weight: bold;
638}
639
640table.ssdiff td.del {
641 color: black;
642 background: #fcc;
643 min-width: 50%;
644}
645
646table.ssdiff td.del_dark {
647 color: black;
648 background: #caa;
649 min-width: 50%;
650}
651
652table.ssdiff span.del {
653 background: #fcc;
654 font-weight: bold;
655}
656
657table.ssdiff td.changed {
658 color: black;
659 background: #ffc;
660 min-width: 50%;
661}
662
663table.ssdiff td.changed_dark {
664 color: black;
665 background: #cca;
666 min-width: 50%;
667}
668
669table.ssdiff td.lineno {
670 color: black;
671 background: #eee;
672 text-align: right;
673 width: 3em;
674 min-width: 3em;
675}
676
677table.ssdiff td.hunk {
678 color: #black;
679 background: #ccf;
680 border-top: solid 1px #aaa;
681 border-bottom: solid 1px #aaa;
682}
683
684table.ssdiff td.head {
685 border-top: solid 1px #aaa;
686 border-bottom: solid 1px #aaa;
687}
688
689table.ssdiff td.head div.head {
690 font-weight: bold;
691 color: black;
692}
693
694table.ssdiff td.foot {
695 border-top: solid 1px #aaa;
696 border-left: none;
697 border-right: none;
698 border-bottom: none;
699}
700
701table.ssdiff td.space {
702 border: none;
703}
704
705table.ssdiff td.space div {
706 min-height: 3em;
707} \ No newline at end of file
diff --git a/cgit.h b/cgit.h
index 1de2335..cd4af72 100644
--- a/cgit.h
+++ b/cgit.h
@@ -144,6 +144,7 @@ struct cgit_query {
144 int nohead; 144 int nohead;
145 char *sort; 145 char *sort;
146 int showmsg; 146 int showmsg;
147 int ssdiff;
147}; 148};
148 149
149struct cgit_config { 150struct cgit_config {
@@ -187,6 +188,7 @@ struct cgit_config {
187 int max_lock_attempts; 188 int max_lock_attempts;
188 int max_msg_len; 189 int max_msg_len;
189 int max_repodesc_len; 190 int max_repodesc_len;
191 int max_blob_size;
190 int max_stats; 192 int max_stats;
191 int nocache; 193 int nocache;
192 int noplainemail; 194 int noplainemail;
@@ -196,6 +198,7 @@ struct cgit_config {
196 int summary_branches; 198 int summary_branches;
197 int summary_log; 199 int summary_log;
198 int summary_tags; 200 int summary_tags;
201 int ssdiff;
199 struct string_list mimetypes; 202 struct string_list mimetypes;
200 struct cgit_filter *about_filter; 203 struct cgit_filter *about_filter;
201 struct cgit_filter *commit_filter; 204 struct cgit_filter *commit_filter;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 0bb429a..d74d9e7 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -182,6 +182,10 @@ max-repodesc-length::
182 Specifies the maximum number of repo description characters to display 182 Specifies the maximum number of repo description characters to display
183 on the repository index page. Default value: "80". 183 on the repository index page. Default value: "80".
184 184
185max-blob-size::
186 Specifies the maximum size of a blob to display HTML for in KBytes.
187 Default value: "0" (limit disabled).
188
185max-stats:: 189max-stats::
186 Set the default maximum statistics period. Valid values are "week", 190 Set the default maximum statistics period. Valid values are "week",
187 "month", "quarter" and "year". If unspecified, statistics are 191 "month", "quarter" and "year". If unspecified, statistics are
@@ -246,6 +250,10 @@ section::
246 after this option will inherit the current section name. Default value: 250 after this option will inherit the current section name. Default value:
247 none. 251 none.
248 252
253side-by-side-diffs::
254 If set to "1" shows side-by-side diffs instead of unidiffs per
255 default. Default value: "0".
256
249snapshots:: 257snapshots::
250 Text which specifies the default set of snapshot formats generated by 258 Text which specifies the default set of snapshot formats generated by
251 cgit. The value is a space-separated list of zero or more of the 259 cgit. The value is a space-separated list of zero or more of the
diff --git a/filters/syntax-highlighting.sh b/filters/syntax-highlighting.sh
index 999ad0c..6b1c576 100755
--- a/filters/syntax-highlighting.sh
+++ b/filters/syntax-highlighting.sh
@@ -3,6 +3,10 @@
3# tree-view by refering to this file with the source-filter or repo.source- 3# tree-view by refering to this file with the source-filter or repo.source-
4# filter options in cgitrc. 4# filter options in cgitrc.
5# 5#
6# This script requires a shell supporting the ${var##pattern} syntax.
7# It is supported by at least dash and bash, however busybox environments
8# might have to use an external call to sed instead.
9#
6# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax 10# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
7# highlighting, so you'll probably want something like the following included 11# highlighting, so you'll probably want something like the following included
8# in your css file (generated by highlight 2.4.8 and adapted for cgit): 12# in your css file (generated by highlight 2.4.8 and adapted for cgit):
@@ -20,20 +24,11 @@
20# table.blob .kwc { color:#000000; font-weight:bold; } 24# table.blob .kwc { color:#000000; font-weight:bold; }
21# table.blob .kwd { color:#010181; } 25# table.blob .kwd { color:#010181; }
22 26
23case "$1" in 27# store filename and extension in local vars
24 *.c) 28BASENAME="$1"
25 highlight -f -I -X -S c 29EXTENSION="${BASENAME##*.}"
26 ;; 30
27 *.h) 31# map Makefile and Makefile.* to .mk
28 highlight -f -I -X -S c 32[ "${BASENAME%%.*}" == "Makefile" ] && EXTENSION=mk
29 ;; 33
30 *.sh) 34exec highlight --force -f -I -X -S $EXTENSION 2>/dev/null
31 highlight -f -I -X -S sh
32 ;;
33 *.css)
34 highlight -f -I -X -S css
35 ;;
36 *)
37 highlight -f -I -X -S txt
38 ;;
39esac
diff --git a/ui-commit.c b/ui-commit.c
index f5b0ae5..b5e3c01 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -58,9 +58,14 @@ void cgit_print_commit(char *hex)
58 html("</td></tr>\n"); 58 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 59 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 60 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); 61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, 0);
62 html(" ("); 62 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 63 cgit_patch_link("patch", NULL, NULL, NULL, tmp);
64 html(") (");
65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, 1);
67 else
68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, 1);
64 html(")</td></tr>\n"); 69 html(")</td></tr>\n");
65 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 70 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
66 tmp = xstrdup(hex); 71 tmp = xstrdup(hex);
@@ -78,10 +83,10 @@ void cgit_print_commit(char *hex)
78 html("<tr><th>parent</th>" 83 html("<tr><th>parent</th>"
79 "<td colspan='2' class='sha1'>"); 84 "<td colspan='2' class='sha1'>");
80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 85 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL,
81 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 86 ctx.qry.head, sha1_to_hex(p->item->object.sha1), 0);
82 html(" ("); 87 html(" (");
83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 88 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
84 sha1_to_hex(p->item->object.sha1), NULL); 89 sha1_to_hex(p->item->object.sha1), NULL, 0);
85 html(")</td></tr>"); 90 html(")</td></tr>");
86 parents++; 91 parents++;
87 } 92 }
diff --git a/ui-diff.c b/ui-diff.c
index 2196745..a92a768 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -9,6 +9,7 @@
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-ssdiff.h"
12 13
13unsigned char old_rev_sha1[20]; 14unsigned char old_rev_sha1[20];
14unsigned char new_rev_sha1[20]; 15unsigned char new_rev_sha1[20];
@@ -32,6 +33,7 @@ static struct fileinfo {
32 int binary:1; 33 int binary:1;
33} *items; 34} *items;
34 35
36static int use_ssdiff = 0;
35 37
36static void print_fileinfo(struct fileinfo *info) 38static void print_fileinfo(struct fileinfo *info)
37{ 39{
@@ -83,7 +85,7 @@ static void print_fileinfo(struct fileinfo *info)
83 } 85 }
84 htmlf("</td><td class='%s'>", class); 86 htmlf("</td><td class='%s'>", class);
85 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, 87 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
86 ctx.qry.sha2, info->new_path); 88 ctx.qry.sha2, info->new_path, 0);
87 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
88 htmlf(" (%s from %s)", 90 htmlf(" (%s from %s)",
89 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
@@ -158,7 +160,7 @@ void cgit_print_diffstat(const unsigned char *old_sha1,
158 160
159 html("<div class='diffstat-header'>"); 161 html("<div class='diffstat-header'>");
160 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, 162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
161 ctx.qry.sha2, NULL); 163 ctx.qry.sha2, NULL, 0);
162 html("</div>"); 164 html("</div>");
163 html("<table summary='diffstat' class='diffstat'>"); 165 html("<table summary='diffstat' class='diffstat'>");
164 max_changes = 0; 166 max_changes = 0;
@@ -246,26 +248,54 @@ static void header(unsigned char *sha1, char *path1, int mode1,
246 html("</div>"); 248 html("</div>");
247} 249}
248 250
251static void print_ssdiff_link()
252{
253 if (!strcmp(ctx.qry.page, "diff")) {
254 if (use_ssdiff)
255 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
256 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1);
257 else
258 cgit_diff_link("Side-by-side diff", NULL, NULL,
259 ctx.qry.head, ctx.qry.sha1,
260 ctx.qry.sha2, ctx.qry.path, 1);
261 }
262}
263
249static void filepair_cb(struct diff_filepair *pair) 264static void filepair_cb(struct diff_filepair *pair)
250{ 265{
251 unsigned long old_size = 0; 266 unsigned long old_size = 0;
252 unsigned long new_size = 0; 267 unsigned long new_size = 0;
253 int binary = 0; 268 int binary = 0;
269 linediff_fn print_line_fn = print_line;
254 270
271 if (use_ssdiff) {
272 cgit_ssdiff_header_begin();
273 print_line_fn = cgit_ssdiff_line_cb;
274 }
255 header(pair->one->sha1, pair->one->path, pair->one->mode, 275 header(pair->one->sha1, pair->one->path, pair->one->mode,
256 pair->two->sha1, pair->two->path, pair->two->mode); 276 pair->two->sha1, pair->two->path, pair->two->mode);
277 if (use_ssdiff)
278 cgit_ssdiff_header_end();
257 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 279 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
258 if (S_ISGITLINK(pair->one->mode)) 280 if (S_ISGITLINK(pair->one->mode))
259 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 281 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
260 if (S_ISGITLINK(pair->two->mode)) 282 if (S_ISGITLINK(pair->two->mode))
261 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 283 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
284 if (use_ssdiff)
285 cgit_ssdiff_footer();
262 return; 286 return;
263 } 287 }
264 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 288 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
265 &new_size, &binary, print_line)) 289 &new_size, &binary, print_line_fn))
266 cgit_print_error("Error running diff"); 290 cgit_print_error("Error running diff");
267 if (binary) 291 if (binary) {
268 html("Binary files differ"); 292 if (use_ssdiff)
293 html("<tr><td colspan='4'>Binary files differ</td></tr>");
294 else
295 html("Binary files differ");
296 }
297 if (use_ssdiff)
298 cgit_ssdiff_footer();
269} 299}
270 300
271void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) 301void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
@@ -303,11 +333,21 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefi
303 if (!commit2 || parse_commit(commit2)) 333 if (!commit2 || parse_commit(commit2))
304 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 334 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
305 } 335 }
336
337 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
338 use_ssdiff = 1;
339
340 print_ssdiff_link();
306 cgit_print_diffstat(old_rev_sha1, new_rev_sha1); 341 cgit_print_diffstat(old_rev_sha1, new_rev_sha1);
307 342
308 html("<table summary='diff' class='diff'>"); 343 if (use_ssdiff) {
309 html("<tr><td>"); 344 html("<table summary='ssdiff' class='ssdiff'>");
345 } else {
346 html("<table summary='diff' class='diff'>");
347 html("<tr><td>");
348 }
310 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); 349 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix);
311 html("</td></tr>"); 350 if (!use_ssdiff)
351 html("</td></tr>");
312 html("</table>"); 352 html("</table>");
313} 353}
diff --git a/ui-log.c b/ui-log.c
index f3132c9..0947604 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -66,7 +66,7 @@ void show_commit_decorations(struct commit *commit)
66 else { 66 else {
67 strncpy(buf, deco->name, sizeof(buf) - 1); 67 strncpy(buf, deco->name, sizeof(buf) - 1);
68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
69 sha1_to_hex(commit->object.sha1)); 69 sha1_to_hex(commit->object.sha1), 0);
70 } 70 }
71 deco = deco->next; 71 deco = deco->next;
72 } 72 }
@@ -89,7 +89,7 @@ void print_commit(struct commit *commit)
89 htmlf("</td><td%s>", 89 htmlf("</td><td%s>",
90 ctx.qry.showmsg ? " class='logsubject'" : ""); 90 ctx.qry.showmsg ? " class='logsubject'" : "");
91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
92 sha1_to_hex(commit->object.sha1)); 92 sha1_to_hex(commit->object.sha1), 0);
93 show_commit_decorations(commit); 93 show_commit_decorations(commit);
94 html("</td><td>"); 94 html("</td><td>");
95 html_txt(info->author); 95 html_txt(info->author);
diff --git a/ui-refs.c b/ui-refs.c
index b3489ee..98738db 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -74,7 +74,7 @@ static int print_branch(struct refinfo *ref)
74 html("</td><td>"); 74 html("</td><td>");
75 75
76 if (ref->object->type == OBJ_COMMIT) { 76 if (ref->object->type == OBJ_COMMIT) {
77 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, 0);
78 html("</td><td>"); 78 html("</td><td>");
79 html_txt(info->author); 79 html_txt(info->author);
80 html("</td><td colspan='2'>"); 80 html("</td><td colspan='2'>");
diff --git a/ui-shared.c b/ui-shared.c
index 3a9e67b..08ea003 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -317,7 +317,7 @@ void cgit_log_link(char *name, char *title, char *class, char *head,
317} 317}
318 318
319void cgit_commit_link(char *name, char *title, char *class, char *head, 319void cgit_commit_link(char *name, char *title, char *class, char *head,
320 char *rev) 320 char *rev, int toggle_ssdiff)
321{ 321{
322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
323 name[ctx.cfg.max_msg_len] = '\0'; 323 name[ctx.cfg.max_msg_len] = '\0';
@@ -325,7 +325,23 @@ void cgit_commit_link(char *name, char *title, char *class, char *head,
325 name[ctx.cfg.max_msg_len - 2] = '.'; 325 name[ctx.cfg.max_msg_len - 2] = '.';
326 name[ctx.cfg.max_msg_len - 3] = '.'; 326 name[ctx.cfg.max_msg_len - 3] = '.';
327 } 327 }
328 reporevlink("commit", name, title, class, head, rev, NULL); 328
329 char *delim;
330
331 delim = repolink(title, class, "commit", head, NULL);
332 if (rev && strcmp(rev, ctx.qry.head)) {
333 html(delim);
334 html("id=");
335 html_url_arg(rev);
336 delim = "&amp;";
337 }
338 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
339 html(delim);
340 html("ss=1");
341 }
342 html("'>");
343 html_txt(name);
344 html("</a>");
329} 345}
330 346
331void cgit_refs_link(char *name, char *title, char *class, char *head, 347void cgit_refs_link(char *name, char *title, char *class, char *head,
@@ -341,7 +357,8 @@ void cgit_snapshot_link(char *name, char *title, char *class, char *head,
341} 357}
342 358
343void cgit_diff_link(char *name, char *title, char *class, char *head, 359void cgit_diff_link(char *name, char *title, char *class, char *head,
344 char *new_rev, char *old_rev, char *path) 360 char *new_rev, char *old_rev, char *path,
361 int toggle_ssdiff)
345{ 362{
346 char *delim; 363 char *delim;
347 364
@@ -356,6 +373,11 @@ void cgit_diff_link(char *name, char *title, char *class, char *head,
356 html(delim); 373 html(delim);
357 html("id2="); 374 html("id2=");
358 html_url_arg(old_rev); 375 html_url_arg(old_rev);
376 delim = "&amp;";
377 }
378 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
379 html(delim);
380 html("ss=1");
359 } 381 }
360 html("'>"); 382 html("'>");
361 html_txt(name); 383 html_txt(name);
@@ -383,7 +405,7 @@ void cgit_object_link(struct object *obj)
383 shortrev[10] = '\0'; 405 shortrev[10] = '\0';
384 if (obj->type == OBJ_COMMIT) { 406 if (obj->type == OBJ_COMMIT) {
385 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 407 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
386 ctx.qry.head, fullrev); 408 ctx.qry.head, fullrev, 0);
387 return; 409 return;
388 } else if (obj->type == OBJ_TREE) 410 } else if (obj->type == OBJ_TREE)
389 page = "tree"; 411 page = "tree";
@@ -695,9 +717,9 @@ void cgit_print_pageheader(struct cgit_context *ctx)
695 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 717 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
696 ctx->qry.sha1, NULL); 718 ctx->qry.sha1, NULL);
697 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 719 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
698 ctx->qry.head, ctx->qry.sha1); 720 ctx->qry.head, ctx->qry.sha1, 0);
699 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 721 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
700 ctx->qry.sha1, ctx->qry.sha2, NULL); 722 ctx->qry.sha1, ctx->qry.sha2, NULL, 0);
701 if (ctx->repo->max_stats) 723 if (ctx->repo->max_stats)
702 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 724 cgit_stats_link("stats", NULL, hc(cmd, "stats"),
703 ctx->qry.head, NULL); 725 ctx->qry.head, NULL);
diff --git a/ui-shared.h b/ui-shared.h
index b12aa89..9ebc1f9 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -23,7 +23,7 @@ extern void cgit_log_link(char *name, char *title, char *class, char *head,
23 char *rev, char *path, int ofs, char *grep, 23 char *rev, char *path, int ofs, char *grep,
24 char *pattern, int showmsg); 24 char *pattern, int showmsg);
25extern void cgit_commit_link(char *name, char *title, char *class, char *head, 25extern void cgit_commit_link(char *name, char *title, char *class, char *head,
26 char *rev); 26 char *rev, int toggle_ssdiff);
27extern void cgit_patch_link(char *name, char *title, char *class, char *head, 27extern void cgit_patch_link(char *name, char *title, char *class, char *head,
28 char *rev); 28 char *rev);
29extern void cgit_refs_link(char *name, char *title, char *class, char *head, 29extern void cgit_refs_link(char *name, char *title, char *class, char *head,
@@ -31,7 +31,8 @@ extern void cgit_refs_link(char *name, char *title, char *class, char *head,
31extern void cgit_snapshot_link(char *name, char *title, char *class, 31extern void cgit_snapshot_link(char *name, char *title, char *class,
32 char *head, char *rev, char *archivename); 32 char *head, char *rev, char *archivename);
33extern void cgit_diff_link(char *name, char *title, char *class, char *head, 33extern void cgit_diff_link(char *name, char *title, char *class, char *head,
34 char *new_rev, char *old_rev, char *path); 34 char *new_rev, char *old_rev, char *path,
35 int toggle_ssdiff);
35extern void cgit_stats_link(char *name, char *title, char *class, char *head, 36extern void cgit_stats_link(char *name, char *title, char *class, char *head,
36 char *path); 37 char *path);
37extern void cgit_object_link(struct object *obj); 38extern void cgit_object_link(struct object *obj);
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
new file mode 100644
index 0000000..408e620
--- /dev/null
+++ b/ui-ssdiff.c
@@ -0,0 +1,369 @@
1#include "cgit.h"
2#include "html.h"
3#include "ui-shared.h"
4
5extern int use_ssdiff;
6
7static int current_old_line, current_new_line;
8
9struct deferred_lines {
10 int line_no;
11 char *line;
12 struct deferred_lines *next;
13};
14
15static struct deferred_lines *deferred_old, *deferred_old_last;
16static struct deferred_lines *deferred_new, *deferred_new_last;
17
18static char *longest_common_subsequence(char *A, char *B)
19{
20 int i, j, ri;
21 int m = strlen(A);
22 int n = strlen(B);
23 int L[m + 1][n + 1];
24 int tmp1, tmp2;
25 int lcs_length;
26 char *result;
27
28 for (i = m; i >= 0; i--) {
29 for (j = n; j >= 0; j--) {
30 if (A[i] == '\0' || B[j] == '\0') {
31 L[i][j] = 0;
32 } else if (A[i] == B[j]) {
33 L[i][j] = 1 + L[i + 1][j + 1];
34 } else {
35 tmp1 = L[i + 1][j];
36 tmp2 = L[i][j + 1];
37 L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2);
38 }
39 }
40 }
41
42 lcs_length = L[0][0];
43 result = xmalloc(lcs_length + 2);
44 memset(result, 0, sizeof(*result) * (lcs_length + 2));
45
46 ri = 0;
47 i = 0;
48 j = 0;
49 while (i < m && j < n) {
50 if (A[i] == B[j]) {
51 result[ri] = A[i];
52 ri += 1;
53 i += 1;
54 j += 1;
55 } else if (L[i + 1][j] >= L[i][j + 1]) {
56 i += 1;
57 } else {
58 j += 1;
59 }
60 }
61 return result;
62}
63
64static int line_from_hunk(char *line, char type)
65{
66 char *buf1, *buf2;
67 int len;
68
69 buf1 = strchr(line, type);
70 if (buf1 == NULL)
71 return 0;
72 buf1 += 1;
73 buf2 = strchr(buf1, ',');
74 if (buf2 == NULL)
75 return 0;
76 len = buf2 - buf1;
77 buf2 = xmalloc(len + 1);
78 strncpy(buf2, buf1, len);
79 buf2[len] = '\0';
80 int res = atoi(buf2);
81 free(buf2);
82 return res;
83}
84
85static char *replace_tabs(char *line)
86{
87 char *prev_buf = line;
88 char *cur_buf;
89 int linelen = strlen(line);
90 int n_tabs = 0;
91 int i;
92 char *result;
93 char *spaces = " ";
94
95 if (linelen == 0) {
96 result = xmalloc(1);
97 result[0] = '\0';
98 return result;
99 }
100
101 for (i = 0; i < linelen; i++)
102 if (line[i] == '\t')
103 n_tabs += 1;
104 result = xmalloc(linelen + n_tabs * 8 + 1);
105 result[0] = '\0';
106
107 while (1) {
108 cur_buf = strchr(prev_buf, '\t');
109 if (!cur_buf) {
110 strcat(result, prev_buf);
111 break;
112 } else {
113 strcat(result, " ");
114 strncat(result, spaces, 8 - (strlen(result) % 8));
115 strncat(result, prev_buf, cur_buf - prev_buf);
116 }
117 prev_buf = cur_buf + 1;
118 }
119 return result;
120}
121
122static int calc_deferred_lines(struct deferred_lines *start)
123{
124 struct deferred_lines *item = start;
125 int result = 0;
126 while (item) {
127 result += 1;
128 item = item->next;
129 }
130 return result;
131}
132
133static void deferred_old_add(char *line, int line_no)
134{
135 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
136 item->line = xstrdup(line);
137 item->line_no = line_no;
138 item->next = NULL;
139 if (deferred_old) {
140 deferred_old_last->next = item;
141 deferred_old_last = item;
142 } else {
143 deferred_old = deferred_old_last = item;
144 }
145}
146
147static void deferred_new_add(char *line, int line_no)
148{
149 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
150 item->line = xstrdup(line);
151 item->line_no = line_no;
152 item->next = NULL;
153 if (deferred_new) {
154 deferred_new_last->next = item;
155 deferred_new_last = item;
156 } else {
157 deferred_new = deferred_new_last = item;
158 }
159}
160
161static void print_part_with_lcs(char *class, char *line, char *lcs)
162{
163 int line_len = strlen(line);
164 int i, j;
165 char c[2] = " ";
166 int same = 1;
167
168 j = 0;
169 for (i = 0; i < line_len; i++) {
170 c[0] = line[i];
171 if (same) {
172 if (line[i] == lcs[j])
173 j += 1;
174 else {
175 same = 0;
176 htmlf("<span class='%s'>", class);
177 }
178 } else if (line[i] == lcs[j]) {
179 same = 1;
180 htmlf("</span>");
181 j += 1;
182 }
183 html_txt(c);
184 }
185}
186
187static void print_ssdiff_line(char *class,
188 int old_line_no,
189 char *old_line,
190 int new_line_no,
191 char *new_line, int individual_chars)
192{
193 char *lcs = NULL;
194 if (old_line)
195 old_line = replace_tabs(old_line + 1);
196 if (new_line)
197 new_line = replace_tabs(new_line + 1);
198 if (individual_chars && old_line && new_line)
199 lcs = longest_common_subsequence(old_line, new_line);
200 html("<tr>");
201 if (old_line_no > 0)
202 htmlf("<td class='lineno'>%d</td><td class='%s'>",
203 old_line_no, class);
204 else if (old_line)
205 htmlf("<td class='lineno'></td><td class='%s'>", class);
206 else
207 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
208 if (old_line) {
209 if (lcs)
210 print_part_with_lcs("del", old_line, lcs);
211 else
212 html_txt(old_line);
213 }
214
215 html("</td>");
216 if (new_line_no > 0)
217 htmlf("<td class='lineno'>%d</td><td class='%s'>",
218 new_line_no, class);
219 else if (new_line)
220 htmlf("<td class='lineno'></td><td class='%s'>", class);
221 else
222 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
223 if (new_line) {
224 if (lcs)
225 print_part_with_lcs("add", new_line, lcs);
226 else
227 html_txt(new_line);
228 }
229
230 html("</td></tr>");
231 if (lcs)
232 free(lcs);
233 if (new_line)
234 free(new_line);
235 if (old_line)
236 free(old_line);
237}
238
239static void print_deferred_old_lines()
240{
241 struct deferred_lines *iter_old, *tmp;
242 iter_old = deferred_old;
243 while (iter_old) {
244 print_ssdiff_line("del", iter_old->line_no,
245 iter_old->line, -1, NULL, 0);
246 tmp = iter_old->next;
247 free(iter_old);
248 iter_old = tmp;
249 }
250}
251
252static void print_deferred_new_lines()
253{
254 struct deferred_lines *iter_new, *tmp;
255 iter_new = deferred_new;
256 while (iter_new) {
257 print_ssdiff_line("add", -1, NULL,
258 iter_new->line_no, iter_new->line, 0);
259 tmp = iter_new->next;
260 free(iter_new);
261 iter_new = tmp;
262 }
263}
264
265static void print_deferred_changed_lines()
266{
267 struct deferred_lines *iter_old, *iter_new, *tmp;
268 int n_old_lines = calc_deferred_lines(deferred_old);
269 int n_new_lines = calc_deferred_lines(deferred_new);
270 int individual_chars = (n_old_lines == n_new_lines ? 1 : 0);
271
272 iter_old = deferred_old;
273 iter_new = deferred_new;
274 while (iter_old || iter_new) {
275 if (iter_old && iter_new)
276 print_ssdiff_line("changed", iter_old->line_no,
277 iter_old->line,
278 iter_new->line_no, iter_new->line,
279 individual_chars);
280 else if (iter_old)
281 print_ssdiff_line("changed", iter_old->line_no,
282 iter_old->line, -1, NULL, 0);
283 else if (iter_new)
284 print_ssdiff_line("changed", -1, NULL,
285 iter_new->line_no, iter_new->line, 0);
286 if (iter_old) {
287 tmp = iter_old->next;
288 free(iter_old);
289 iter_old = tmp;
290 }
291
292 if (iter_new) {
293 tmp = iter_new->next;
294 free(iter_new);
295 iter_new = tmp;
296 }
297 }
298}
299
300void cgit_ssdiff_print_deferred_lines()
301{
302 if (!deferred_old && !deferred_new)
303 return;
304 if (deferred_old && !deferred_new)
305 print_deferred_old_lines();
306 else if (!deferred_old && deferred_new)
307 print_deferred_new_lines();
308 else
309 print_deferred_changed_lines();
310 deferred_old = deferred_old_last = NULL;
311 deferred_new = deferred_new_last = NULL;
312}
313
314/*
315 * print a single line returned from xdiff
316 */
317void cgit_ssdiff_line_cb(char *line, int len)
318{
319 char c = line[len - 1];
320 line[len - 1] = '\0';
321 if (line[0] == '@') {
322 current_old_line = line_from_hunk(line, '-');
323 current_new_line = line_from_hunk(line, '+');
324 }
325
326 if (line[0] == ' ') {
327 if (deferred_old || deferred_new)
328 cgit_ssdiff_print_deferred_lines();
329 print_ssdiff_line("ctx", current_old_line, line,
330 current_new_line, line, 0);
331 current_old_line += 1;
332 current_new_line += 1;
333 } else if (line[0] == '+') {
334 deferred_new_add(line, current_new_line);
335 current_new_line += 1;
336 } else if (line[0] == '-') {
337 deferred_old_add(line, current_old_line);
338 current_old_line += 1;
339 } else if (line[0] == '@') {
340 html("<tr><td colspan='4' class='hunk'>");
341 html_txt(line);
342 html("</td></tr>");
343 } else {
344 html("<tr><td colspan='4' class='ctx'>");
345 html_txt(line);
346 html("</td></tr>");
347 }
348 line[len - 1] = c;
349}
350
351void cgit_ssdiff_header_begin()
352{
353 current_old_line = -1;
354 current_new_line = -1;
355 html("<tr><td class='space' colspan='4'><div></div></td></tr>");
356 html("<tr><td class='head' colspan='4'>");
357}
358
359void cgit_ssdiff_header_end()
360{
361 html("</td><tr>");
362}
363
364void cgit_ssdiff_footer()
365{
366 if (deferred_old || deferred_new)
367 cgit_ssdiff_print_deferred_lines();
368 html("<tr><td class='foot' colspan='4'></td></tr>");
369}
diff --git a/ui-ssdiff.h b/ui-ssdiff.h
new file mode 100644
index 0000000..64b4b12
--- /dev/null
+++ b/ui-ssdiff.h
@@ -0,0 +1,13 @@
1#ifndef UI_SSDIFF_H
2#define UI_SSDIFF_H
3
4extern void cgit_ssdiff_print_deferred_lines();
5
6extern void cgit_ssdiff_line_cb(char *line, int len);
7
8extern void cgit_ssdiff_header_begin();
9extern void cgit_ssdiff_header_end();
10
11extern void cgit_ssdiff_footer();
12
13#endif /* UI_SSDIFF_H */
diff --git a/ui-tree.c b/ui-tree.c
index f53ab64..f281937 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -107,6 +107,12 @@ static void print_object(const unsigned char *sha1, char *path, const char *base
107 curr_rev, path); 107 curr_rev, path);
108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1));
109 109
110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
111 htmlf("<div class='error'>blob size (%dKB) exceeds display size limit (%dKB).</div>",
112 size / 1024, ctx.cfg.max_blob_size);
113 return;
114 }
115
110 if (buffer_is_binary(buf, size)) 116 if (buffer_is_binary(buf, size))
111 print_binary_buffer(buf, size); 117 print_binary_buffer(buf, size);
112 else 118 else