summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Makefile1
-rw-r--r--cgit.c5
-rw-r--r--cgit.css99
-rw-r--r--cgit.h2
-rw-r--r--cgitrc.5.txt4
-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
13 files changed, 586 insertions, 25 deletions
diff --git a/Makefile b/Makefile
index 4e101d3..f8a4d47 100644
--- a/Makefile
+++ b/Makefile
@@ -72,48 +72,49 @@ endif
72 72
73 73
74EXTLIBS = git/libgit.a git/xdiff/lib.a -lz 74EXTLIBS = git/libgit.a git/xdiff/lib.a -lz
75OBJECTS = 75OBJECTS =
76OBJECTS += cache.o 76OBJECTS += cache.o
77OBJECTS += cgit.o 77OBJECTS += cgit.o
78OBJECTS += cmd.o 78OBJECTS += cmd.o
79OBJECTS += configfile.o 79OBJECTS += configfile.o
80OBJECTS += html.o 80OBJECTS += html.o
81OBJECTS += parsing.o 81OBJECTS += parsing.o
82OBJECTS += scan-tree.o 82OBJECTS += scan-tree.o
83OBJECTS += shared.o 83OBJECTS += shared.o
84OBJECTS += ui-atom.o 84OBJECTS += ui-atom.o
85OBJECTS += ui-blob.o 85OBJECTS += ui-blob.o
86OBJECTS += ui-clone.o 86OBJECTS += ui-clone.o
87OBJECTS += ui-commit.o 87OBJECTS += ui-commit.o
88OBJECTS += ui-diff.o 88OBJECTS += ui-diff.o
89OBJECTS += ui-log.o 89OBJECTS += ui-log.o
90OBJECTS += ui-patch.o 90OBJECTS += ui-patch.o
91OBJECTS += ui-plain.o 91OBJECTS += ui-plain.o
92OBJECTS += ui-refs.o 92OBJECTS += 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
99OBJECTS += ui-tree.o 100OBJECTS += ui-tree.o
100 101
101ifdef NEEDS_LIBICONV 102ifdef NEEDS_LIBICONV
102 EXTLIBS += -liconv 103 EXTLIBS += -liconv
103endif 104endif
104 105
105 106
106.PHONY: all libgit test install uninstall clean force-version get-git \ 107.PHONY: all libgit test install uninstall clean force-version get-git \
107 doc man-doc html-doc clean-doc 108 doc man-doc html-doc clean-doc
108 109
109all: cgit 110all: cgit
110 111
111VERSION: force-version 112VERSION: force-version
112 @./gen-version.sh "$(CGIT_VERSION)" 113 @./gen-version.sh "$(CGIT_VERSION)"
113-include VERSION 114-include VERSION
114 115
115 116
116CFLAGS += -g -Wall -Igit 117CFLAGS += -g -Wall -Igit
117CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' 118CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
118CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' 119CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
119CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 120CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
diff --git a/cgit.c b/cgit.c
index 08cb5d2..4f68a4b 100644
--- a/cgit.c
+++ b/cgit.c
@@ -163,48 +163,50 @@ void config_cb(const char *name, const char *value)
163 ctx.cfg.embedded = atoi(value); 163 ctx.cfg.embedded = atoi(value);
164 else if (!strcmp(name, "max-message-length")) 164 else if (!strcmp(name, "max-message-length"))
165 ctx.cfg.max_msg_len = atoi(value); 165 ctx.cfg.max_msg_len = atoi(value);
166 else if (!strcmp(name, "max-repodesc-length")) 166 else if (!strcmp(name, "max-repodesc-length"))
167 ctx.cfg.max_repodesc_len = atoi(value); 167 ctx.cfg.max_repodesc_len = atoi(value);
168 else if (!strcmp(name, "max-blob-size")) 168 else if (!strcmp(name, "max-blob-size"))
169 ctx.cfg.max_blob_size = atoi(value); 169 ctx.cfg.max_blob_size = atoi(value);
170 else if (!strcmp(name, "max-repo-count")) 170 else if (!strcmp(name, "max-repo-count"))
171 ctx.cfg.max_repo_count = atoi(value); 171 ctx.cfg.max_repo_count = atoi(value);
172 else if (!strcmp(name, "max-commit-count")) 172 else if (!strcmp(name, "max-commit-count"))
173 ctx.cfg.max_commit_count = atoi(value); 173 ctx.cfg.max_commit_count = atoi(value);
174 else if (!strcmp(name, "scan-path")) 174 else if (!strcmp(name, "scan-path"))
175 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 175 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
176 process_cached_repolist(value); 176 process_cached_repolist(value);
177 else 177 else
178 scan_tree(value, repo_config); 178 scan_tree(value, repo_config);
179 else if (!strcmp(name, "source-filter")) 179 else if (!strcmp(name, "source-filter"))
180 ctx.cfg.source_filter = new_filter(value, 1); 180 ctx.cfg.source_filter = new_filter(value, 1);
181 else if (!strcmp(name, "summary-log")) 181 else if (!strcmp(name, "summary-log"))
182 ctx.cfg.summary_log = atoi(value); 182 ctx.cfg.summary_log = atoi(value);
183 else if (!strcmp(name, "summary-branches")) 183 else if (!strcmp(name, "summary-branches"))
184 ctx.cfg.summary_branches = atoi(value); 184 ctx.cfg.summary_branches = atoi(value);
185 else if (!strcmp(name, "summary-tags")) 185 else if (!strcmp(name, "summary-tags"))
186 ctx.cfg.summary_tags = atoi(value); 186 ctx.cfg.summary_tags = atoi(value);
187 else if (!strcmp(name, "side-by-side-diffs"))
188 ctx.cfg.ssdiff = atoi(value);
187 else if (!strcmp(name, "agefile")) 189 else if (!strcmp(name, "agefile"))
188 ctx.cfg.agefile = xstrdup(value); 190 ctx.cfg.agefile = xstrdup(value);
189 else if (!strcmp(name, "renamelimit")) 191 else if (!strcmp(name, "renamelimit"))
190 ctx.cfg.renamelimit = atoi(value); 192 ctx.cfg.renamelimit = atoi(value);
191 else if (!strcmp(name, "robots")) 193 else if (!strcmp(name, "robots"))
192 ctx.cfg.robots = xstrdup(value); 194 ctx.cfg.robots = xstrdup(value);
193 else if (!strcmp(name, "clone-prefix")) 195 else if (!strcmp(name, "clone-prefix"))
194 ctx.cfg.clone_prefix = xstrdup(value); 196 ctx.cfg.clone_prefix = xstrdup(value);
195 else if (!strcmp(name, "local-time")) 197 else if (!strcmp(name, "local-time"))
196 ctx.cfg.local_time = atoi(value); 198 ctx.cfg.local_time = atoi(value);
197 else if (!prefixcmp(name, "mimetype.")) 199 else if (!prefixcmp(name, "mimetype."))
198 add_mimetype(name + 9, value); 200 add_mimetype(name + 9, value);
199 else if (!strcmp(name, "include")) 201 else if (!strcmp(name, "include"))
200 parse_configfile(value, config_cb); 202 parse_configfile(value, config_cb);
201} 203}
202 204
203static void querystring_cb(const char *name, const char *value) 205static void querystring_cb(const char *name, const char *value)
204{ 206{
205 if (!value) 207 if (!value)
206 value = ""; 208 value = "";
207 209
208 if (!strcmp(name,"r")) { 210 if (!strcmp(name,"r")) {
209 ctx.qry.repo = xstrdup(value); 211 ctx.qry.repo = xstrdup(value);
210 ctx.repo = cgit_get_repoinfo(value); 212 ctx.repo = cgit_get_repoinfo(value);
@@ -221,90 +223,93 @@ static void querystring_cb(const char *name, const char *value)
221 ctx.qry.search = xstrdup(value); 223 ctx.qry.search = xstrdup(value);
222 } else if (!strcmp(name, "h")) { 224 } else if (!strcmp(name, "h")) {
223 ctx.qry.head = xstrdup(value); 225 ctx.qry.head = xstrdup(value);
224 ctx.qry.has_symref = 1; 226 ctx.qry.has_symref = 1;
225 } else if (!strcmp(name, "id")) { 227 } else if (!strcmp(name, "id")) {
226 ctx.qry.sha1 = xstrdup(value); 228 ctx.qry.sha1 = xstrdup(value);
227 ctx.qry.has_sha1 = 1; 229 ctx.qry.has_sha1 = 1;
228 } else if (!strcmp(name, "id2")) { 230 } else if (!strcmp(name, "id2")) {
229 ctx.qry.sha2 = xstrdup(value); 231 ctx.qry.sha2 = xstrdup(value);
230 ctx.qry.has_sha1 = 1; 232 ctx.qry.has_sha1 = 1;
231 } else if (!strcmp(name, "ofs")) { 233 } else if (!strcmp(name, "ofs")) {
232 ctx.qry.ofs = atoi(value); 234 ctx.qry.ofs = atoi(value);
233 } else if (!strcmp(name, "path")) { 235 } else if (!strcmp(name, "path")) {
234 ctx.qry.path = trim_end(value, '/'); 236 ctx.qry.path = trim_end(value, '/');
235 } else if (!strcmp(name, "name")) { 237 } else if (!strcmp(name, "name")) {
236 ctx.qry.name = xstrdup(value); 238 ctx.qry.name = xstrdup(value);
237 } else if (!strcmp(name, "mimetype")) { 239 } else if (!strcmp(name, "mimetype")) {
238 ctx.qry.mimetype = xstrdup(value); 240 ctx.qry.mimetype = xstrdup(value);
239 } else if (!strcmp(name, "s")){ 241 } else if (!strcmp(name, "s")){
240 ctx.qry.sort = xstrdup(value); 242 ctx.qry.sort = xstrdup(value);
241 } else if (!strcmp(name, "showmsg")) { 243 } else if (!strcmp(name, "showmsg")) {
242 ctx.qry.showmsg = atoi(value); 244 ctx.qry.showmsg = atoi(value);
243 } else if (!strcmp(name, "period")) { 245 } else if (!strcmp(name, "period")) {
244 ctx.qry.period = xstrdup(value); 246 ctx.qry.period = xstrdup(value);
247 } else if (!strcmp(name, "ss")) {
248 ctx.qry.ssdiff = atoi(value);
245 } 249 }
246} 250}
247 251
248char *xstrdupn(const char *str) 252char *xstrdupn(const char *str)
249{ 253{
250 return (str ? xstrdup(str) : NULL); 254 return (str ? xstrdup(str) : NULL);
251} 255}
252 256
253static void prepare_context(struct cgit_context *ctx) 257static void prepare_context(struct cgit_context *ctx)
254{ 258{
255 memset(ctx, 0, sizeof(ctx)); 259 memset(ctx, 0, sizeof(ctx));
256 ctx->cfg.agefile = "info/web/last-modified"; 260 ctx->cfg.agefile = "info/web/last-modified";
257 ctx->cfg.nocache = 0; 261 ctx->cfg.nocache = 0;
258 ctx->cfg.cache_size = 0; 262 ctx->cfg.cache_size = 0;
259 ctx->cfg.cache_dynamic_ttl = 5; 263 ctx->cfg.cache_dynamic_ttl = 5;
260 ctx->cfg.cache_max_create_time = 5; 264 ctx->cfg.cache_max_create_time = 5;
261 ctx->cfg.cache_repo_ttl = 5; 265 ctx->cfg.cache_repo_ttl = 5;
262 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 266 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
263 ctx->cfg.cache_root_ttl = 5; 267 ctx->cfg.cache_root_ttl = 5;
264 ctx->cfg.cache_scanrc_ttl = 15; 268 ctx->cfg.cache_scanrc_ttl = 15;
265 ctx->cfg.cache_static_ttl = -1; 269 ctx->cfg.cache_static_ttl = -1;
266 ctx->cfg.css = "/cgit.css"; 270 ctx->cfg.css = "/cgit.css";
267 ctx->cfg.logo = "/cgit.png"; 271 ctx->cfg.logo = "/cgit.png";
268 ctx->cfg.local_time = 0; 272 ctx->cfg.local_time = 0;
269 ctx->cfg.enable_tree_linenumbers = 1; 273 ctx->cfg.enable_tree_linenumbers = 1;
270 ctx->cfg.max_repo_count = 50; 274 ctx->cfg.max_repo_count = 50;
271 ctx->cfg.max_commit_count = 50; 275 ctx->cfg.max_commit_count = 50;
272 ctx->cfg.max_lock_attempts = 5; 276 ctx->cfg.max_lock_attempts = 5;
273 ctx->cfg.max_msg_len = 80; 277 ctx->cfg.max_msg_len = 80;
274 ctx->cfg.max_repodesc_len = 80; 278 ctx->cfg.max_repodesc_len = 80;
275 ctx->cfg.max_blob_size = 0; 279 ctx->cfg.max_blob_size = 0;
276 ctx->cfg.max_stats = 0; 280 ctx->cfg.max_stats = 0;
277 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 281 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
278 ctx->cfg.renamelimit = -1; 282 ctx->cfg.renamelimit = -1;
279 ctx->cfg.robots = "index, nofollow"; 283 ctx->cfg.robots = "index, nofollow";
280 ctx->cfg.root_title = "Git repository browser"; 284 ctx->cfg.root_title = "Git repository browser";
281 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 285 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
282 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 286 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
283 ctx->cfg.section = ""; 287 ctx->cfg.section = "";
284 ctx->cfg.summary_branches = 10; 288 ctx->cfg.summary_branches = 10;
285 ctx->cfg.summary_log = 10; 289 ctx->cfg.summary_log = 10;
286 ctx->cfg.summary_tags = 10; 290 ctx->cfg.summary_tags = 10;
291 ctx->cfg.ssdiff = 0;
287 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 292 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
288 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 293 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
289 ctx->env.https = xstrdupn(getenv("HTTPS")); 294 ctx->env.https = xstrdupn(getenv("HTTPS"));
290 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 295 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
291 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 296 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
292 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 297 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
293 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 298 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
294 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 299 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
295 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 300 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
296 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 301 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
297 ctx->page.mimetype = "text/html"; 302 ctx->page.mimetype = "text/html";
298 ctx->page.charset = PAGE_ENCODING; 303 ctx->page.charset = PAGE_ENCODING;
299 ctx->page.filename = NULL; 304 ctx->page.filename = NULL;
300 ctx->page.size = 0; 305 ctx->page.size = 0;
301 ctx->page.modified = time(NULL); 306 ctx->page.modified = time(NULL);
302 ctx->page.expires = ctx->page.modified; 307 ctx->page.expires = ctx->page.modified;
303 ctx->page.etag = NULL; 308 ctx->page.etag = NULL;
304 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 309 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
305 if (ctx->env.script_name) 310 if (ctx->env.script_name)
306 ctx->cfg.script_name = ctx->env.script_name; 311 ctx->cfg.script_name = ctx->env.script_name;
307 if (ctx->env.query_string) 312 if (ctx->env.query_string)
308 ctx->qry.raw = ctx->env.query_string; 313 ctx->qry.raw = ctx->env.query_string;
309 if (!ctx->env.cgit_config) 314 if (!ctx->env.cgit_config)
310 ctx->env.cgit_config = CGIT_CONFIG; 315 ctx->env.cgit_config = CGIT_CONFIG;
diff --git a/cgit.css b/cgit.css
index ef7d3c1..0cb894a 100644
--- a/cgit.css
+++ b/cgit.css
@@ -585,24 +585,123 @@ table.vgraph div.bar {
585 background-color: #eee; 585 background-color: #eee;
586} 586}
587 587
588table.hgraph { 588table.hgraph {
589 border: solid 1px black; 589 border: solid 1px black;
590 width: 800px; 590 width: 800px;
591} 591}
592 592
593table.hgraph th { 593table.hgraph th {
594 background-color: #eee; 594 background-color: #eee;
595 font-weight: bold; 595 font-weight: bold;
596 border: solid 1px black; 596 border: solid 1px black;
597 padding: 1px 0.5em; 597 padding: 1px 0.5em;
598} 598}
599 599
600table.hgraph td { 600table.hgraph td {
601 vertical-align: center; 601 vertical-align: center;
602 padding: 2px 2px; 602 padding: 2px 2px;
603} 603}
604 604
605table.hgraph div.bar { 605table.hgraph div.bar {
606 background-color: #eee; 606 background-color: #eee;
607 height: 1em; 607 height: 1em;
608} 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 39853df..5941ec0 100644
--- a/cgit.h
+++ b/cgit.h
@@ -122,48 +122,49 @@ struct reflist {
122 int alloc; 122 int alloc;
123 int count; 123 int count;
124}; 124};
125 125
126struct cgit_query { 126struct cgit_query {
127 int has_symref; 127 int has_symref;
128 int has_sha1; 128 int has_sha1;
129 char *raw; 129 char *raw;
130 char *repo; 130 char *repo;
131 char *page; 131 char *page;
132 char *search; 132 char *search;
133 char *grep; 133 char *grep;
134 char *head; 134 char *head;
135 char *sha1; 135 char *sha1;
136 char *sha2; 136 char *sha2;
137 char *path; 137 char *path;
138 char *name; 138 char *name;
139 char *mimetype; 139 char *mimetype;
140 char *url; 140 char *url;
141 char *period; 141 char *period;
142 int ofs; 142 int ofs;
143 int nohead; 143 int nohead;
144 char *sort; 144 char *sort;
145 int showmsg; 145 int showmsg;
146 int ssdiff;
146}; 147};
147 148
148struct cgit_config { 149struct cgit_config {
149 char *agefile; 150 char *agefile;
150 char *cache_root; 151 char *cache_root;
151 char *clone_prefix; 152 char *clone_prefix;
152 char *css; 153 char *css;
153 char *favicon; 154 char *favicon;
154 char *footer; 155 char *footer;
155 char *head_include; 156 char *head_include;
156 char *header; 157 char *header;
157 char *index_header; 158 char *index_header;
158 char *index_info; 159 char *index_info;
159 char *logo; 160 char *logo;
160 char *logo_link; 161 char *logo_link;
161 char *module_link; 162 char *module_link;
162 char *robots; 163 char *robots;
163 char *root_title; 164 char *root_title;
164 char *root_desc; 165 char *root_desc;
165 char *root_readme; 166 char *root_readme;
166 char *script_name; 167 char *script_name;
167 char *section; 168 char *section;
168 char *virtual_root; 169 char *virtual_root;
169 int cache_size; 170 int cache_size;
@@ -174,48 +175,49 @@ struct cgit_config {
174 int cache_scanrc_ttl; 175 int cache_scanrc_ttl;
175 int cache_static_ttl; 176 int cache_static_ttl;
176 int embedded; 177 int embedded;
177 int enable_filter_overrides; 178 int enable_filter_overrides;
178 int enable_index_links; 179 int enable_index_links;
179 int enable_log_filecount; 180 int enable_log_filecount;
180 int enable_log_linecount; 181 int enable_log_linecount;
181 int enable_tree_linenumbers; 182 int enable_tree_linenumbers;
182 int local_time; 183 int local_time;
183 int max_repo_count; 184 int max_repo_count;
184 int max_commit_count; 185 int max_commit_count;
185 int max_lock_attempts; 186 int max_lock_attempts;
186 int max_msg_len; 187 int max_msg_len;
187 int max_repodesc_len; 188 int max_repodesc_len;
188 int max_blob_size; 189 int max_blob_size;
189 int max_stats; 190 int max_stats;
190 int nocache; 191 int nocache;
191 int noplainemail; 192 int noplainemail;
192 int noheader; 193 int noheader;
193 int renamelimit; 194 int renamelimit;
194 int snapshots; 195 int snapshots;
195 int summary_branches; 196 int summary_branches;
196 int summary_log; 197 int summary_log;
197 int summary_tags; 198 int summary_tags;
199 int ssdiff;
198 struct string_list mimetypes; 200 struct string_list mimetypes;
199 struct cgit_filter *about_filter; 201 struct cgit_filter *about_filter;
200 struct cgit_filter *commit_filter; 202 struct cgit_filter *commit_filter;
201 struct cgit_filter *source_filter; 203 struct cgit_filter *source_filter;
202}; 204};
203 205
204struct cgit_page { 206struct cgit_page {
205 time_t modified; 207 time_t modified;
206 time_t expires; 208 time_t expires;
207 size_t size; 209 size_t size;
208 char *mimetype; 210 char *mimetype;
209 char *charset; 211 char *charset;
210 char *filename; 212 char *filename;
211 char *etag; 213 char *etag;
212 char *title; 214 char *title;
213 int status; 215 int status;
214 char *statusmsg; 216 char *statusmsg;
215}; 217};
216 218
217struct cgit_environment { 219struct cgit_environment {
218 char *cgit_config; 220 char *cgit_config;
219 char *http_host; 221 char *http_host;
220 char *https; 222 char *https;
221 char *no_http; 223 char *no_http;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index e69140b..70e4c78 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -224,48 +224,52 @@ robots::
224 224
225root-desc:: 225root-desc::
226 Text printed below the heading on the repository index page. Default 226 Text printed below the heading on the repository index page. Default
227 value: "a fast webinterface for the git dscm". 227 value: "a fast webinterface for the git dscm".
228 228
229root-readme:: 229root-readme::
230 The content of the file specified with this option will be included 230 The content of the file specified with this option will be included
231 verbatim below the "about" link on the repository index page. Default 231 verbatim below the "about" link on the repository index page. Default
232 value: none. 232 value: none.
233 233
234root-title:: 234root-title::
235 Text printed as heading on the repository index page. Default value: 235 Text printed as heading on the repository index page. Default value:
236 "Git Repository Browser". 236 "Git Repository Browser".
237 237
238scan-path:: 238scan-path::
239 A path which will be scanned for repositories. If caching is enabled, 239 A path which will be scanned for repositories. If caching is enabled,
240 the result will be cached as a cgitrc include-file in the cache 240 the result will be cached as a cgitrc include-file in the cache
241 directory. Default value: none. See also: cache-scanrc-ttl. 241 directory. Default value: none. See also: cache-scanrc-ttl.
242 242
243section:: 243section::
244 The name of the current repository section - all repositories defined 244 The name of the current repository section - all repositories defined
245 after this option will inherit the current section name. Default value: 245 after this option will inherit the current section name. Default value:
246 none. 246 none.
247 247
248side-by-side-diffs::
249 If set to "1" shows side-by-side diffs instead of unidiffs per
250 default. Default value: "0".
251
248snapshots:: 252snapshots::
249 Text which specifies the default set of snapshot formats generated by 253 Text which specifies the default set of snapshot formats generated by
250 cgit. The value is a space-separated list of zero or more of the 254 cgit. The value is a space-separated list of zero or more of the
251 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 255 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
252 256
253source-filter:: 257source-filter::
254 Specifies a command which will be invoked to format plaintext blobs 258 Specifies a command which will be invoked to format plaintext blobs
255 in the tree view. The command will get the blob content on its STDIN 259 in the tree view. The command will get the blob content on its STDIN
256 and the name of the blob as its only command line argument. The STDOUT 260 and the name of the blob as its only command line argument. The STDOUT
257 from the command will be included verbatim as the blob contents, i.e. 261 from the command will be included verbatim as the blob contents, i.e.
258 this can be used to implement e.g. syntax highlighting. Default value: 262 this can be used to implement e.g. syntax highlighting. Default value:
259 none. 263 none.
260 264
261summary-branches:: 265summary-branches::
262 Specifies the number of branches to display in the repository "summary" 266 Specifies the number of branches to display in the repository "summary"
263 view. Default value: "10". 267 view. Default value: "10".
264 268
265summary-log:: 269summary-log::
266 Specifies the number of log entries to display in the repository 270 Specifies the number of log entries to display in the repository
267 "summary" view. Default value: "10". 271 "summary" view. Default value: "10".
268 272
269summary-tags:: 273summary-tags::
270 Specifies the number of tags to display in the repository "summary" 274 Specifies the number of tags to display in the repository "summary"
271 view. Default value: "10". 275 view. Default value: "10".
diff --git a/ui-commit.c b/ui-commit.c
index f5b0ae5..b5e3c01 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -37,72 +37,77 @@ void cgit_print_commit(char *hex)
37 37
38 load_ref_decorations(DECORATE_FULL_REFS); 38 load_ref_decorations(DECORATE_FULL_REFS);
39 39
40 html("<table summary='commit info' class='commit-info'>\n"); 40 html("<table summary='commit info' class='commit-info'>\n");
41 html("<tr><th>author</th><td>"); 41 html("<tr><th>author</th><td>");
42 html_txt(info->author); 42 html_txt(info->author);
43 if (!ctx.cfg.noplainemail) { 43 if (!ctx.cfg.noplainemail) {
44 html(" "); 44 html(" ");
45 html_txt(info->author_email); 45 html_txt(info->author_email);
46 } 46 }
47 html("</td><td class='right'>"); 47 html("</td><td class='right'>");
48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time); 48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time);
49 html("</td></tr>\n"); 49 html("</td></tr>\n");
50 html("<tr><th>committer</th><td>"); 50 html("<tr><th>committer</th><td>");
51 html_txt(info->committer); 51 html_txt(info->committer);
52 if (!ctx.cfg.noplainemail) { 52 if (!ctx.cfg.noplainemail) {
53 html(" "); 53 html(" ");
54 html_txt(info->committer_email); 54 html_txt(info->committer_email);
55 } 55 }
56 html("</td><td class='right'>"); 56 html("</td><td class='right'>");
57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
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);
67 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
68 ctx.qry.head, tmp, NULL); 73 ctx.qry.head, tmp, NULL);
69 html("</td></tr>\n"); 74 html("</td></tr>\n");
70 for (p = commit->parents; p ; p = p->next) { 75 for (p = commit->parents; p ; p = p->next) {
71 parent = lookup_commit_reference(p->item->object.sha1); 76 parent = lookup_commit_reference(p->item->object.sha1);
72 if (!parent) { 77 if (!parent) {
73 html("<tr><td colspan='3'>"); 78 html("<tr><td colspan='3'>");
74 cgit_print_error("Error reading parent commit"); 79 cgit_print_error("Error reading parent commit");
75 html("</td></tr>"); 80 html("</td></tr>");
76 continue; 81 continue;
77 } 82 }
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 }
88 if (ctx.repo->snapshots) { 93 if (ctx.repo->snapshots) {
89 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 94 html("<tr><th>download</th><td colspan='2' class='sha1'>");
90 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 95 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
91 hex, ctx.repo->snapshots); 96 hex, ctx.repo->snapshots);
92 html("</td></tr>"); 97 html("</td></tr>");
93 } 98 }
94 html("</table>\n"); 99 html("</table>\n");
95 html("<div class='commit-subject'>"); 100 html("<div class='commit-subject'>");
96 if (ctx.repo->commit_filter) 101 if (ctx.repo->commit_filter)
97 cgit_open_filter(ctx.repo->commit_filter); 102 cgit_open_filter(ctx.repo->commit_filter);
98 html_txt(info->subject); 103 html_txt(info->subject);
99 if (ctx.repo->commit_filter) 104 if (ctx.repo->commit_filter)
100 cgit_close_filter(ctx.repo->commit_filter); 105 cgit_close_filter(ctx.repo->commit_filter);
101 show_commit_decorations(commit); 106 show_commit_decorations(commit);
102 html("</div>"); 107 html("</div>");
103 html("<div class='commit-msg'>"); 108 html("<div class='commit-msg'>");
104 if (ctx.repo->commit_filter) 109 if (ctx.repo->commit_filter)
105 cgit_open_filter(ctx.repo->commit_filter); 110 cgit_open_filter(ctx.repo->commit_filter);
106 html_txt(info->msg); 111 html_txt(info->msg);
107 if (ctx.repo->commit_filter) 112 if (ctx.repo->commit_filter)
108 cgit_close_filter(ctx.repo->commit_filter); 113 cgit_close_filter(ctx.repo->commit_filter);
diff --git a/ui-diff.c b/ui-diff.c
index 2196745..a92a768 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -1,58 +1,60 @@
1/* ui-diff.c: show diff between two blobs 1/* ui-diff.c: show diff between two blobs
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
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];
15 16
16static int files, slots; 17static int files, slots;
17static int total_adds, total_rems, max_changes; 18static int total_adds, total_rems, max_changes;
18static int lines_added, lines_removed; 19static int lines_added, lines_removed;
19 20
20static struct fileinfo { 21static struct fileinfo {
21 char status; 22 char status;
22 unsigned char old_sha1[20]; 23 unsigned char old_sha1[20];
23 unsigned char new_sha1[20]; 24 unsigned char new_sha1[20];
24 unsigned short old_mode; 25 unsigned short old_mode;
25 unsigned short new_mode; 26 unsigned short new_mode;
26 char *old_path; 27 char *old_path;
27 char *new_path; 28 char *new_path;
28 unsigned int added; 29 unsigned int added;
29 unsigned int removed; 30 unsigned int removed;
30 unsigned long old_size; 31 unsigned long old_size;
31 unsigned long new_size; 32 unsigned long new_size;
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{
38 char *class; 40 char *class;
39 41
40 switch (info->status) { 42 switch (info->status) {
41 case DIFF_STATUS_ADDED: 43 case DIFF_STATUS_ADDED:
42 class = "add"; 44 class = "add";
43 break; 45 break;
44 case DIFF_STATUS_COPIED: 46 case DIFF_STATUS_COPIED:
45 class = "cpy"; 47 class = "cpy";
46 break; 48 break;
47 case DIFF_STATUS_DELETED: 49 case DIFF_STATUS_DELETED:
48 class = "del"; 50 class = "del";
49 break; 51 break;
50 case DIFF_STATUS_MODIFIED: 52 case DIFF_STATUS_MODIFIED:
51 class = "upd"; 53 class = "upd";
52 break; 54 break;
53 case DIFF_STATUS_RENAMED: 55 case DIFF_STATUS_RENAMED:
54 class = "mov"; 56 class = "mov";
55 break; 57 break;
56 case DIFF_STATUS_TYPE_CHANGED: 58 case DIFF_STATUS_TYPE_CHANGED:
57 class = "typ"; 59 class = "typ";
58 break; 60 break;
@@ -62,49 +64,49 @@ static void print_fileinfo(struct fileinfo *info)
62 case DIFF_STATUS_UNMERGED: 64 case DIFF_STATUS_UNMERGED:
63 class = "stg"; 65 class = "stg";
64 break; 66 break;
65 default: 67 default:
66 die("bug: unhandled diff status %c", info->status); 68 die("bug: unhandled diff status %c", info->status);
67 } 69 }
68 70
69 html("<tr>"); 71 html("<tr>");
70 htmlf("<td class='mode'>"); 72 htmlf("<td class='mode'>");
71 if (is_null_sha1(info->new_sha1)) { 73 if (is_null_sha1(info->new_sha1)) {
72 cgit_print_filemode(info->old_mode); 74 cgit_print_filemode(info->old_mode);
73 } else { 75 } else {
74 cgit_print_filemode(info->new_mode); 76 cgit_print_filemode(info->new_mode);
75 } 77 }
76 78
77 if (info->old_mode != info->new_mode && 79 if (info->old_mode != info->new_mode &&
78 !is_null_sha1(info->old_sha1) && 80 !is_null_sha1(info->old_sha1) &&
79 !is_null_sha1(info->new_sha1)) { 81 !is_null_sha1(info->new_sha1)) {
80 html("<span class='modechange'>["); 82 html("<span class='modechange'>[");
81 cgit_print_filemode(info->old_mode); 83 cgit_print_filemode(info->old_mode);
82 html("]</span>"); 84 html("]</span>");
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",
90 info->old_path); 92 info->old_path);
91 html("</td><td class='right'>"); 93 html("</td><td class='right'>");
92 if (info->binary) { 94 if (info->binary) {
93 htmlf("bin</td><td class='graph'>%d -> %d bytes", 95 htmlf("bin</td><td class='graph'>%d -> %d bytes",
94 info->old_size, info->new_size); 96 info->old_size, info->new_size);
95 return; 97 return;
96 } 98 }
97 htmlf("%d", info->added + info->removed); 99 htmlf("%d", info->added + info->removed);
98 html("</td><td class='graph'>"); 100 html("</td><td class='graph'>");
99 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); 101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
100 htmlf("<td class='add' style='width: %.1f%%;'/>", 102 htmlf("<td class='add' style='width: %.1f%%;'/>",
101 info->added * 100.0 / max_changes); 103 info->added * 100.0 / max_changes);
102 htmlf("<td class='rem' style='width: %.1f%%;'/>", 104 htmlf("<td class='rem' style='width: %.1f%%;'/>",
103 info->removed * 100.0 / max_changes); 105 info->removed * 100.0 / max_changes);
104 htmlf("<td class='none' style='width: %.1f%%;'/>", 106 htmlf("<td class='none' style='width: %.1f%%;'/>",
105 (max_changes - info->removed - info->added) * 100.0 / max_changes); 107 (max_changes - info->removed - info->added) * 100.0 / max_changes);
106 html("</tr></table></td></tr>\n"); 108 html("</tr></table></td></tr>\n");
107} 109}
108 110
109static void count_diff_lines(char *line, int len) 111static void count_diff_lines(char *line, int len)
110{ 112{
@@ -137,49 +139,49 @@ static void inspect_filepair(struct diff_filepair *pair)
137 hashcpy(items[files-1].old_sha1, pair->one->sha1); 139 hashcpy(items[files-1].old_sha1, pair->one->sha1);
138 hashcpy(items[files-1].new_sha1, pair->two->sha1); 140 hashcpy(items[files-1].new_sha1, pair->two->sha1);
139 items[files-1].old_mode = pair->one->mode; 141 items[files-1].old_mode = pair->one->mode;
140 items[files-1].new_mode = pair->two->mode; 142 items[files-1].new_mode = pair->two->mode;
141 items[files-1].old_path = xstrdup(pair->one->path); 143 items[files-1].old_path = xstrdup(pair->one->path);
142 items[files-1].new_path = xstrdup(pair->two->path); 144 items[files-1].new_path = xstrdup(pair->two->path);
143 items[files-1].added = lines_added; 145 items[files-1].added = lines_added;
144 items[files-1].removed = lines_removed; 146 items[files-1].removed = lines_removed;
145 items[files-1].old_size = old_size; 147 items[files-1].old_size = old_size;
146 items[files-1].new_size = new_size; 148 items[files-1].new_size = new_size;
147 items[files-1].binary = binary; 149 items[files-1].binary = binary;
148 if (lines_added + lines_removed > max_changes) 150 if (lines_added + lines_removed > max_changes)
149 max_changes = lines_added + lines_removed; 151 max_changes = lines_added + lines_removed;
150 total_adds += lines_added; 152 total_adds += lines_added;
151 total_rems += lines_removed; 153 total_rems += lines_removed;
152} 154}
153 155
154void cgit_print_diffstat(const unsigned char *old_sha1, 156void cgit_print_diffstat(const unsigned char *old_sha1,
155 const unsigned char *new_sha1) 157 const unsigned char *new_sha1)
156{ 158{
157 int i; 159 int i;
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;
165 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); 167 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL);
166 for(i = 0; i<files; i++) 168 for(i = 0; i<files; i++)
167 print_fileinfo(&items[i]); 169 print_fileinfo(&items[i]);
168 html("</table>"); 170 html("</table>");
169 html("<div class='diffstat-summary'>"); 171 html("<div class='diffstat-summary'>");
170 htmlf("%d files changed, %d insertions, %d deletions", 172 htmlf("%d files changed, %d insertions, %d deletions",
171 files, total_adds, total_rems); 173 files, total_adds, total_rems);
172 html("</div>"); 174 html("</div>");
173} 175}
174 176
175 177
176/* 178/*
177 * print a single line returned from xdiff 179 * print a single line returned from xdiff
178 */ 180 */
179static void print_line(char *line, int len) 181static void print_line(char *line, int len)
180{ 182{
181 char *class = "ctx"; 183 char *class = "ctx";
182 char c = line[len-1]; 184 char c = line[len-1];
183 185
184 if (line[0] == '+') 186 if (line[0] == '+')
185 class = "add"; 187 class = "add";
@@ -225,89 +227,127 @@ static void header(unsigned char *sha1, char *path1, int mode1,
225 htmlf("<br/>index %s..%s", abbrev1, abbrev2); 227 htmlf("<br/>index %s..%s", abbrev1, abbrev2);
226 free(abbrev1); 228 free(abbrev1);
227 free(abbrev2); 229 free(abbrev2);
228 if (mode1 != 0 && mode2 != 0) { 230 if (mode1 != 0 && mode2 != 0) {
229 htmlf(" %.6o", mode1); 231 htmlf(" %.6o", mode1);
230 if (mode2 != mode1) 232 if (mode2 != mode1)
231 htmlf("..%.6o", mode2); 233 htmlf("..%.6o", mode2);
232 } 234 }
233 html("<br/>--- a/"); 235 html("<br/>--- a/");
234 if (mode1 != 0) 236 if (mode1 != 0)
235 cgit_tree_link(path1, NULL, NULL, ctx.qry.head, 237 cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
236 sha1_to_hex(old_rev_sha1), path1); 238 sha1_to_hex(old_rev_sha1), path1);
237 else 239 else
238 html_txt(path1); 240 html_txt(path1);
239 html("<br/>+++ b/"); 241 html("<br/>+++ b/");
240 if (mode2 != 0) 242 if (mode2 != 0)
241 cgit_tree_link(path2, NULL, NULL, ctx.qry.head, 243 cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
242 sha1_to_hex(new_rev_sha1), path2); 244 sha1_to_hex(new_rev_sha1), path2);
243 else 245 else
244 html_txt(path2); 246 html_txt(path2);
245 } 247 }
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)
272{ 302{
273 enum object_type type; 303 enum object_type type;
274 unsigned long size; 304 unsigned long size;
275 struct commit *commit, *commit2; 305 struct commit *commit, *commit2;
276 306
277 if (!new_rev) 307 if (!new_rev)
278 new_rev = ctx.qry.head; 308 new_rev = ctx.qry.head;
279 get_sha1(new_rev, new_rev_sha1); 309 get_sha1(new_rev, new_rev_sha1);
280 type = sha1_object_info(new_rev_sha1, &size); 310 type = sha1_object_info(new_rev_sha1, &size);
281 if (type == OBJ_BAD) { 311 if (type == OBJ_BAD) {
282 cgit_print_error(fmt("Bad object name: %s", new_rev)); 312 cgit_print_error(fmt("Bad object name: %s", new_rev));
283 return; 313 return;
284 } 314 }
285 commit = lookup_commit_reference(new_rev_sha1); 315 commit = lookup_commit_reference(new_rev_sha1);
286 if (!commit || parse_commit(commit)) 316 if (!commit || parse_commit(commit))
287 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1))); 317 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1)));
288 318
289 if (old_rev) 319 if (old_rev)
290 get_sha1(old_rev, old_rev_sha1); 320 get_sha1(old_rev, old_rev_sha1);
291 else if (commit->parents && commit->parents->item) 321 else if (commit->parents && commit->parents->item)
292 hashcpy(old_rev_sha1, commit->parents->item->object.sha1); 322 hashcpy(old_rev_sha1, commit->parents->item->object.sha1);
293 else 323 else
294 hashclr(old_rev_sha1); 324 hashclr(old_rev_sha1);
295 325
296 if (!is_null_sha1(old_rev_sha1)) { 326 if (!is_null_sha1(old_rev_sha1)) {
297 type = sha1_object_info(old_rev_sha1, &size); 327 type = sha1_object_info(old_rev_sha1, &size);
298 if (type == OBJ_BAD) { 328 if (type == OBJ_BAD) {
299 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1))); 329 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1)));
300 return; 330 return;
301 } 331 }
302 commit2 = lookup_commit_reference(old_rev_sha1); 332 commit2 = lookup_commit_reference(old_rev_sha1);
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
@@ -45,72 +45,72 @@ void show_commit_decorations(struct commit *commit)
45 deco = lookup_decoration(&name_decoration, &commit->object); 45 deco = lookup_decoration(&name_decoration, &commit->object);
46 while (deco) { 46 while (deco) {
47 if (!prefixcmp(deco->name, "refs/heads/")) { 47 if (!prefixcmp(deco->name, "refs/heads/")) {
48 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 48 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, 49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL,
50 0, NULL, NULL, ctx.qry.showmsg); 50 0, NULL, NULL, ctx.qry.showmsg);
51 } 51 }
52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
53 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 53 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
55 } 55 }
56 else if (!prefixcmp(deco->name, "refs/tags/")) { 56 else if (!prefixcmp(deco->name, "refs/tags/")) {
57 strncpy(buf, deco->name + 10, sizeof(buf) - 1); 57 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
59 } 59 }
60 else if (!prefixcmp(deco->name, "refs/remotes/")) { 60 else if (!prefixcmp(deco->name, "refs/remotes/")) {
61 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 61 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
62 cgit_log_link(buf, NULL, "remote-deco", NULL, 62 cgit_log_link(buf, NULL, "remote-deco", NULL,
63 sha1_to_hex(commit->object.sha1), NULL, 63 sha1_to_hex(commit->object.sha1), NULL,
64 0, NULL, NULL, ctx.qry.showmsg); 64 0, NULL, NULL, ctx.qry.showmsg);
65 } 65 }
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 }
73} 73}
74 74
75void print_commit(struct commit *commit) 75void print_commit(struct commit *commit)
76{ 76{
77 struct commitinfo *info; 77 struct commitinfo *info;
78 char *tmp; 78 char *tmp;
79 int cols = 2; 79 int cols = 2;
80 80
81 info = cgit_parse_commit(commit); 81 info = cgit_parse_commit(commit);
82 htmlf("<tr%s><td>", 82 htmlf("<tr%s><td>",
83 ctx.qry.showmsg ? " class='logheader'" : ""); 83 ctx.qry.showmsg ? " class='logheader'" : "");
84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp);
86 html_link_open(tmp, NULL, NULL); 86 html_link_open(tmp, NULL, NULL);
87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
88 html_link_close(); 88 html_link_close();
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);
96 if (ctx.repo->enable_log_filecount) { 96 if (ctx.repo->enable_log_filecount) {
97 files = 0; 97 files = 0;
98 add_lines = 0; 98 add_lines = 0;
99 rem_lines = 0; 99 rem_lines = 0;
100 cgit_diff_commit(commit, inspect_files); 100 cgit_diff_commit(commit, inspect_files);
101 html("</td><td>"); 101 html("</td><td>");
102 htmlf("%d", files); 102 htmlf("%d", files);
103 if (ctx.repo->enable_log_linecount) { 103 if (ctx.repo->enable_log_linecount) {
104 html("</td><td>"); 104 html("</td><td>");
105 htmlf("-%d/+%d", rem_lines, add_lines); 105 htmlf("-%d/+%d", rem_lines, add_lines);
106 } 106 }
107 } 107 }
108 html("</td></tr>\n"); 108 html("</td></tr>\n");
109 if (ctx.qry.showmsg) { 109 if (ctx.qry.showmsg) {
110 if (ctx.repo->enable_log_filecount) { 110 if (ctx.repo->enable_log_filecount) {
111 cols++; 111 cols++;
112 if (ctx.repo->enable_log_linecount) 112 if (ctx.repo->enable_log_linecount)
113 cols++; 113 cols++;
114 } 114 }
115 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", 115 htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>",
116 cols); 116 cols);
diff --git a/ui-refs.c b/ui-refs.c
index d3b4f6e..33d9bec 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -53,49 +53,49 @@ static int cmp_tag_age(const void *a, const void *b)
53 else 53 else
54 r1date = r1->commit->committer_date; 54 r1date = r1->commit->committer_date;
55 55
56 if (r2->object->type != OBJ_COMMIT) 56 if (r2->object->type != OBJ_COMMIT)
57 r2date = r2->tag->tagger_date; 57 r2date = r2->tag->tagger_date;
58 else 58 else
59 r2date = r2->commit->committer_date; 59 r2date = r2->commit->committer_date;
60 60
61 return cmp_age(r1date, r2date); 61 return cmp_age(r1date, r2date);
62} 62}
63 63
64static int print_branch(struct refinfo *ref) 64static int print_branch(struct refinfo *ref)
65{ 65{
66 struct commitinfo *info = ref->commit; 66 struct commitinfo *info = ref->commit;
67 char *name = (char *)ref->refname; 67 char *name = (char *)ref->refname;
68 68
69 if (!info) 69 if (!info)
70 return 1; 70 return 1;
71 html("<tr><td>"); 71 html("<tr><td>");
72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
73 ctx.qry.showmsg); 73 ctx.qry.showmsg);
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'>");
81 cgit_print_age(info->commit->date, -1, NULL); 81 cgit_print_age(info->commit->date, -1, NULL);
82 } else { 82 } else {
83 html("</td><td></td><td>"); 83 html("</td><td></td><td>");
84 cgit_object_link(ref->object); 84 cgit_object_link(ref->object);
85 } 85 }
86 html("</td></tr>\n"); 86 html("</td></tr>\n");
87 return 0; 87 return 0;
88} 88}
89 89
90static void print_tag_header() 90static void print_tag_header()
91{ 91{
92 html("<tr class='nohover'><th class='left'>Tag</th>" 92 html("<tr class='nohover'><th class='left'>Tag</th>"
93 "<th class='left'>Download</th>" 93 "<th class='left'>Download</th>"
94 "<th class='left'>Author</th>" 94 "<th class='left'>Author</th>"
95 "<th class='left' colspan='2'>Age</th></tr>\n"); 95 "<th class='left' colspan='2'>Age</th></tr>\n");
96 header = 1; 96 header = 1;
97} 97}
98 98
99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) 99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
100{ 100{
101 const struct cgit_snapshot_format* f; 101 const struct cgit_snapshot_format* f;
diff --git a/ui-shared.c b/ui-shared.c
index 3a9e67b..08ea003 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -296,115 +296,137 @@ void cgit_log_link(char *name, char *title, char *class, char *head,
296 html(delim); 296 html(delim);
297 html("qt="); 297 html("qt=");
298 html_url_arg(grep); 298 html_url_arg(grep);
299 delim = "&"; 299 delim = "&";
300 html(delim); 300 html(delim);
301 html("q="); 301 html("q=");
302 html_url_arg(pattern); 302 html_url_arg(pattern);
303 } 303 }
304 if (ofs > 0) { 304 if (ofs > 0) {
305 html(delim); 305 html(delim);
306 html("ofs="); 306 html("ofs=");
307 htmlf("%d", ofs); 307 htmlf("%d", ofs);
308 delim = "&"; 308 delim = "&";
309 } 309 }
310 if (showmsg) { 310 if (showmsg) {
311 html(delim); 311 html(delim);
312 html("showmsg=1"); 312 html("showmsg=1");
313 } 313 }
314 html("'>"); 314 html("'>");
315 html_txt(name); 315 html_txt(name);
316 html("</a>"); 316 html("</a>");
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';
324 name[ctx.cfg.max_msg_len - 1] = '.'; 324 name[ctx.cfg.max_msg_len - 1] = '.';
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,
332 char *rev, char *path) 348 char *rev, char *path)
333{ 349{
334 reporevlink("refs", name, title, class, head, rev, path); 350 reporevlink("refs", name, title, class, head, rev, path);
335} 351}
336 352
337void cgit_snapshot_link(char *name, char *title, char *class, char *head, 353void cgit_snapshot_link(char *name, char *title, char *class, char *head,
338 char *rev, char *archivename) 354 char *rev, char *archivename)
339{ 355{
340 reporevlink("snapshot", name, title, class, head, rev, archivename); 356 reporevlink("snapshot", name, title, class, head, rev, archivename);
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
348 delim = repolink(title, class, "diff", head, path); 365 delim = repolink(title, class, "diff", head, path);
349 if (new_rev && strcmp(new_rev, ctx.qry.head)) { 366 if (new_rev && strcmp(new_rev, ctx.qry.head)) {
350 html(delim); 367 html(delim);
351 html("id="); 368 html("id=");
352 html_url_arg(new_rev); 369 html_url_arg(new_rev);
353 delim = "&amp;"; 370 delim = "&amp;";
354 } 371 }
355 if (old_rev) { 372 if (old_rev) {
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);
362 html("</a>"); 384 html("</a>");
363} 385}
364 386
365void cgit_patch_link(char *name, char *title, char *class, char *head, 387void cgit_patch_link(char *name, char *title, char *class, char *head,
366 char *rev) 388 char *rev)
367{ 389{
368 reporevlink("patch", name, title, class, head, rev, NULL); 390 reporevlink("patch", name, title, class, head, rev, NULL);
369} 391}
370 392
371void cgit_stats_link(char *name, char *title, char *class, char *head, 393void cgit_stats_link(char *name, char *title, char *class, char *head,
372 char *path) 394 char *path)
373{ 395{
374 reporevlink("stats", name, title, class, head, NULL, path); 396 reporevlink("stats", name, title, class, head, NULL, path);
375} 397}
376 398
377void cgit_object_link(struct object *obj) 399void cgit_object_link(struct object *obj)
378{ 400{
379 char *page, *shortrev, *fullrev, *name; 401 char *page, *shortrev, *fullrev, *name;
380 402
381 fullrev = sha1_to_hex(obj->sha1); 403 fullrev = sha1_to_hex(obj->sha1);
382 shortrev = xstrdup(fullrev); 404 shortrev = xstrdup(fullrev);
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";
390 else if (obj->type == OBJ_TAG) 412 else if (obj->type == OBJ_TAG)
391 page = "tag"; 413 page = "tag";
392 else 414 else
393 page = "blob"; 415 page = "blob";
394 name = fmt("%s %s...", typename(obj->type), shortrev); 416 name = fmt("%s %s...", typename(obj->type), shortrev);
395 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 417 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
396} 418}
397 419
398void cgit_print_date(time_t secs, char *format, int local_time) 420void cgit_print_date(time_t secs, char *format, int local_time)
399{ 421{
400 char buf[64]; 422 char buf[64];
401 struct tm *time; 423 struct tm *time;
402 424
403 if (!secs) 425 if (!secs)
404 return; 426 return;
405 if(local_time) 427 if(local_time)
406 time = localtime(&secs); 428 time = localtime(&secs);
407 else 429 else
408 time = gmtime(&secs); 430 time = gmtime(&secs);
409 strftime(buf, sizeof(buf)-1, format, time); 431 strftime(buf, sizeof(buf)-1, format, time);
410 html_txt(buf); 432 html_txt(buf);
@@ -674,51 +696,51 @@ static void print_header(struct cgit_context *ctx)
674} 696}
675 697
676void cgit_print_pageheader(struct cgit_context *ctx) 698void cgit_print_pageheader(struct cgit_context *ctx)
677{ 699{
678 struct cgit_cmd *cmd = cgit_get_cmd(ctx); 700 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
679 701
680 if (!cmd && ctx->repo) 702 if (!cmd && ctx->repo)
681 fallback_cmd = "summary"; 703 fallback_cmd = "summary";
682 704
683 html("<div id='cgit'>"); 705 html("<div id='cgit'>");
684 if (!ctx->cfg.noheader) 706 if (!ctx->cfg.noheader)
685 print_header(ctx); 707 print_header(ctx);
686 708
687 html("<table class='tabs'><tr><td>\n"); 709 html("<table class='tabs'><tr><td>\n");
688 if (ctx->repo) { 710 if (ctx->repo) {
689 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 711 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
690 ctx->qry.head); 712 ctx->qry.head);
691 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 713 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
692 ctx->qry.sha1, NULL); 714 ctx->qry.sha1, NULL);
693 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 715 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
694 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); 716 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
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);
704 if (ctx->repo->readme) 726 if (ctx->repo->readme)
705 reporevlink("about", "about", NULL, 727 reporevlink("about", "about", NULL,
706 hc(cmd, "about"), ctx->qry.head, NULL, 728 hc(cmd, "about"), ctx->qry.head, NULL,
707 NULL); 729 NULL);
708 html("</td><td class='form'>"); 730 html("</td><td class='form'>");
709 html("<form class='right' method='get' action='"); 731 html("<form class='right' method='get' action='");
710 if (ctx->cfg.virtual_root) 732 if (ctx->cfg.virtual_root)
711 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 733 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
712 ctx->qry.path, NULL)); 734 ctx->qry.path, NULL));
713 html("'>\n"); 735 html("'>\n");
714 cgit_add_hidden_formfields(1, 0, "log"); 736 cgit_add_hidden_formfields(1, 0, "log");
715 html("<select name='qt'>\n"); 737 html("<select name='qt'>\n");
716 html_option("grep", "log msg", ctx->qry.grep); 738 html_option("grep", "log msg", ctx->qry.grep);
717 html_option("author", "author", ctx->qry.grep); 739 html_option("author", "author", ctx->qry.grep);
718 html_option("committer", "committer", ctx->qry.grep); 740 html_option("committer", "committer", ctx->qry.grep);
719 html("</select>\n"); 741 html("</select>\n");
720 html("<input class='txt' type='text' size='10' name='q' value='"); 742 html("<input class='txt' type='text' size='10' name='q' value='");
721 html_attr(ctx->qry.search); 743 html_attr(ctx->qry.search);
722 html("'/>\n"); 744 html("'/>\n");
723 html("<input type='submit' value='search'/>\n"); 745 html("<input type='submit' value='search'/>\n");
724 html("</form>\n"); 746 html("</form>\n");
diff --git a/ui-shared.h b/ui-shared.h
index b12aa89..9ebc1f9 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -2,50 +2,51 @@
2#define UI_SHARED_H 2#define UI_SHARED_H
3 3
4extern char *cgit_httpscheme(); 4extern char *cgit_httpscheme();
5extern char *cgit_hosturl(); 5extern char *cgit_hosturl();
6extern char *cgit_rooturl(); 6extern char *cgit_rooturl();
7extern char *cgit_repourl(const char *reponame); 7extern char *cgit_repourl(const char *reponame);
8extern char *cgit_fileurl(const char *reponame, const char *pagename, 8extern char *cgit_fileurl(const char *reponame, const char *pagename,
9 const char *filename, const char *query); 9 const char *filename, const char *query);
10extern char *cgit_pageurl(const char *reponame, const char *pagename, 10extern char *cgit_pageurl(const char *reponame, const char *pagename,
11 const char *query); 11 const char *query);
12 12
13extern void cgit_index_link(char *name, char *title, char *class, 13extern void cgit_index_link(char *name, char *title, char *class,
14 char *pattern, int ofs); 14 char *pattern, int ofs);
15extern void cgit_summary_link(char *name, char *title, char *class, char *head); 15extern void cgit_summary_link(char *name, char *title, char *class, char *head);
16extern void cgit_tag_link(char *name, char *title, char *class, char *head, 16extern void cgit_tag_link(char *name, char *title, char *class, char *head,
17 char *rev); 17 char *rev);
18extern void cgit_tree_link(char *name, char *title, char *class, char *head, 18extern void cgit_tree_link(char *name, char *title, char *class, char *head,
19 char *rev, char *path); 19 char *rev, char *path);
20extern void cgit_plain_link(char *name, char *title, char *class, char *head, 20extern void cgit_plain_link(char *name, char *title, char *class, char *head,
21 char *rev, char *path); 21 char *rev, char *path);
22extern void cgit_log_link(char *name, char *title, char *class, char *head, 22extern 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,
30 char *rev, char *path); 30 char *rev, char *path);
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);
38 39
39extern void cgit_print_error(char *msg); 40extern void cgit_print_error(char *msg);
40extern void cgit_print_date(time_t secs, char *format, int local_time); 41extern void cgit_print_date(time_t secs, char *format, int local_time);
41extern void cgit_print_age(time_t t, time_t max_relative, char *format); 42extern void cgit_print_age(time_t t, time_t max_relative, char *format);
42extern void cgit_print_http_headers(struct cgit_context *ctx); 43extern void cgit_print_http_headers(struct cgit_context *ctx);
43extern void cgit_print_docstart(struct cgit_context *ctx); 44extern void cgit_print_docstart(struct cgit_context *ctx);
44extern void cgit_print_docend(); 45extern void cgit_print_docend();
45extern void cgit_print_pageheader(struct cgit_context *ctx); 46extern void cgit_print_pageheader(struct cgit_context *ctx);
46extern void cgit_print_filemode(unsigned short mode); 47extern void cgit_print_filemode(unsigned short mode);
47extern void cgit_print_snapshot_links(const char *repo, const char *head, 48extern void cgit_print_snapshot_links(const char *repo, const char *head,
48 const char *hex, int snapshots); 49 const char *hex, int snapshots);
49extern void cgit_add_hidden_formfields(int incl_head, int incl_search, 50extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
50 char *page); 51 char *page);
51#endif /* UI_SHARED_H */ 52#endif /* UI_SHARED_H */
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
new file mode 100644
index 0000000..408e620
--- a/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
--- a/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 */