summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2011-02-19 13:51:00 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2011-02-19 13:51:00 (UTC)
commit979c460e7f71d153ae79da67b8b21c3412f0fe02 (patch) (unidiff)
tree6da9ffb66ed0a68205e6644cb7e2b4652d6684be
parentfb9e6d1594a24fe4e551fd57a9c91fd18b14806e (diff)
parent0141b9f889bbaa1fe474f9a98dd377138ac73054 (diff)
downloadcgit-979c460e7f71d153ae79da67b8b21c3412f0fe02.zip
cgit-979c460e7f71d153ae79da67b8b21c3412f0fe02.tar.gz
cgit-979c460e7f71d153ae79da67b8b21c3412f0fe02.tar.bz2
Merge branch 'br/misc'
* br/misc: Use transparent background for the cgit logo ssdiff: anchors for ssdiff implement repo.logo and repo.logo-link
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c8
-rw-r--r--cgit.css2
-rw-r--r--cgit.h2
-rw-r--r--cgit.pngbin1840 -> 1488 bytes
-rw-r--r--cgitrc.5.txt9
-rw-r--r--ui-diff.c12
-rw-r--r--ui-diff.h6
-rw-r--r--ui-shared.c18
-rw-r--r--ui-ssdiff.c34
9 files changed, 74 insertions, 17 deletions
diff --git a/cgit.c b/cgit.c
index 71f3fc8..916feb4 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,174 +1,178 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com> 4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
5 * 5 *
6 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
7 * (see COPYING for full license text) 7 * (see COPYING for full license text)
8 */ 8 */
9 9
10#include "cgit.h" 10#include "cgit.h"
11#include "cache.h" 11#include "cache.h"
12#include "cmd.h" 12#include "cmd.h"
13#include "configfile.h" 13#include "configfile.h"
14#include "html.h" 14#include "html.h"
15#include "ui-shared.h" 15#include "ui-shared.h"
16#include "ui-stats.h" 16#include "ui-stats.h"
17#include "scan-tree.h" 17#include "scan-tree.h"
18 18
19const char *cgit_version = CGIT_VERSION; 19const char *cgit_version = CGIT_VERSION;
20 20
21void add_mimetype(const char *name, const char *value) 21void add_mimetype(const char *name, const char *value)
22{ 22{
23 struct string_list_item *item; 23 struct string_list_item *item;
24 24
25 item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name)); 25 item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name));
26 item->util = xstrdup(value); 26 item->util = xstrdup(value);
27} 27}
28 28
29struct cgit_filter *new_filter(const char *cmd, int extra_args) 29struct cgit_filter *new_filter(const char *cmd, int extra_args)
30{ 30{
31 struct cgit_filter *f; 31 struct cgit_filter *f;
32 32
33 if (!cmd || !cmd[0]) 33 if (!cmd || !cmd[0])
34 return NULL; 34 return NULL;
35 35
36 f = xmalloc(sizeof(struct cgit_filter)); 36 f = xmalloc(sizeof(struct cgit_filter));
37 f->cmd = xstrdup(cmd); 37 f->cmd = xstrdup(cmd);
38 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 38 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
39 f->argv[0] = f->cmd; 39 f->argv[0] = f->cmd;
40 f->argv[1] = NULL; 40 f->argv[1] = NULL;
41 return f; 41 return f;
42} 42}
43 43
44static void process_cached_repolist(const char *path); 44static void process_cached_repolist(const char *path);
45 45
46void repo_config(struct cgit_repo *repo, const char *name, const char *value) 46void repo_config(struct cgit_repo *repo, const char *name, const char *value)
47{ 47{
48 if (!strcmp(name, "name")) 48 if (!strcmp(name, "name"))
49 repo->name = xstrdup(value); 49 repo->name = xstrdup(value);
50 else if (!strcmp(name, "clone-url")) 50 else if (!strcmp(name, "clone-url"))
51 repo->clone_url = xstrdup(value); 51 repo->clone_url = xstrdup(value);
52 else if (!strcmp(name, "desc")) 52 else if (!strcmp(name, "desc"))
53 repo->desc = xstrdup(value); 53 repo->desc = xstrdup(value);
54 else if (!strcmp(name, "owner")) 54 else if (!strcmp(name, "owner"))
55 repo->owner = xstrdup(value); 55 repo->owner = xstrdup(value);
56 else if (!strcmp(name, "defbranch")) 56 else if (!strcmp(name, "defbranch"))
57 repo->defbranch = xstrdup(value); 57 repo->defbranch = xstrdup(value);
58 else if (!strcmp(name, "snapshots")) 58 else if (!strcmp(name, "snapshots"))
59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 59 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
60 else if (!strcmp(name, "enable-commit-graph")) 60 else if (!strcmp(name, "enable-commit-graph"))
61 repo->enable_commit_graph = ctx.cfg.enable_commit_graph * atoi(value); 61 repo->enable_commit_graph = ctx.cfg.enable_commit_graph * atoi(value);
62 else if (!strcmp(name, "enable-log-filecount")) 62 else if (!strcmp(name, "enable-log-filecount"))
63 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 63 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
64 else if (!strcmp(name, "enable-log-linecount")) 64 else if (!strcmp(name, "enable-log-linecount"))
65 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 65 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
66 else if (!strcmp(name, "enable-remote-branches")) 66 else if (!strcmp(name, "enable-remote-branches"))
67 repo->enable_remote_branches = atoi(value); 67 repo->enable_remote_branches = atoi(value);
68 else if (!strcmp(name, "enable-subject-links")) 68 else if (!strcmp(name, "enable-subject-links"))
69 repo->enable_subject_links = atoi(value); 69 repo->enable_subject_links = atoi(value);
70 else if (!strcmp(name, "max-stats")) 70 else if (!strcmp(name, "max-stats"))
71 repo->max_stats = cgit_find_stats_period(value, NULL); 71 repo->max_stats = cgit_find_stats_period(value, NULL);
72 else if (!strcmp(name, "module-link")) 72 else if (!strcmp(name, "module-link"))
73 repo->module_link= xstrdup(value); 73 repo->module_link= xstrdup(value);
74 else if (!strcmp(name, "section")) 74 else if (!strcmp(name, "section"))
75 repo->section = xstrdup(value); 75 repo->section = xstrdup(value);
76 else if (!strcmp(name, "readme") && value != NULL) { 76 else if (!strcmp(name, "readme") && value != NULL)
77 repo->readme = xstrdup(value); 77 repo->readme = xstrdup(value);
78 } else if (ctx.cfg.enable_filter_overrides) { 78 else if (!strcmp(name, "logo") && value != NULL)
79 repo->logo = xstrdup(value);
80 else if (!strcmp(name, "logo-link") && value != NULL)
81 repo->logo_link = xstrdup(value);
82 else if (ctx.cfg.enable_filter_overrides) {
79 if (!strcmp(name, "about-filter")) 83 if (!strcmp(name, "about-filter"))
80 repo->about_filter = new_filter(value, 0); 84 repo->about_filter = new_filter(value, 0);
81 else if (!strcmp(name, "commit-filter")) 85 else if (!strcmp(name, "commit-filter"))
82 repo->commit_filter = new_filter(value, 0); 86 repo->commit_filter = new_filter(value, 0);
83 else if (!strcmp(name, "source-filter")) 87 else if (!strcmp(name, "source-filter"))
84 repo->source_filter = new_filter(value, 1); 88 repo->source_filter = new_filter(value, 1);
85 } 89 }
86} 90}
87 91
88void config_cb(const char *name, const char *value) 92void config_cb(const char *name, const char *value)
89{ 93{
90 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 94 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
91 ctx.cfg.section = xstrdup(value); 95 ctx.cfg.section = xstrdup(value);
92 else if (!strcmp(name, "repo.url")) 96 else if (!strcmp(name, "repo.url"))
93 ctx.repo = cgit_add_repo(value); 97 ctx.repo = cgit_add_repo(value);
94 else if (ctx.repo && !strcmp(name, "repo.path")) 98 else if (ctx.repo && !strcmp(name, "repo.path"))
95 ctx.repo->path = trim_end(value, '/'); 99 ctx.repo->path = trim_end(value, '/');
96 else if (ctx.repo && !prefixcmp(name, "repo.")) 100 else if (ctx.repo && !prefixcmp(name, "repo."))
97 repo_config(ctx.repo, name + 5, value); 101 repo_config(ctx.repo, name + 5, value);
98 else if (!strcmp(name, "readme")) 102 else if (!strcmp(name, "readme"))
99 ctx.cfg.readme = xstrdup(value); 103 ctx.cfg.readme = xstrdup(value);
100 else if (!strcmp(name, "root-title")) 104 else if (!strcmp(name, "root-title"))
101 ctx.cfg.root_title = xstrdup(value); 105 ctx.cfg.root_title = xstrdup(value);
102 else if (!strcmp(name, "root-desc")) 106 else if (!strcmp(name, "root-desc"))
103 ctx.cfg.root_desc = xstrdup(value); 107 ctx.cfg.root_desc = xstrdup(value);
104 else if (!strcmp(name, "root-readme")) 108 else if (!strcmp(name, "root-readme"))
105 ctx.cfg.root_readme = xstrdup(value); 109 ctx.cfg.root_readme = xstrdup(value);
106 else if (!strcmp(name, "css")) 110 else if (!strcmp(name, "css"))
107 ctx.cfg.css = xstrdup(value); 111 ctx.cfg.css = xstrdup(value);
108 else if (!strcmp(name, "favicon")) 112 else if (!strcmp(name, "favicon"))
109 ctx.cfg.favicon = xstrdup(value); 113 ctx.cfg.favicon = xstrdup(value);
110 else if (!strcmp(name, "footer")) 114 else if (!strcmp(name, "footer"))
111 ctx.cfg.footer = xstrdup(value); 115 ctx.cfg.footer = xstrdup(value);
112 else if (!strcmp(name, "head-include")) 116 else if (!strcmp(name, "head-include"))
113 ctx.cfg.head_include = xstrdup(value); 117 ctx.cfg.head_include = xstrdup(value);
114 else if (!strcmp(name, "header")) 118 else if (!strcmp(name, "header"))
115 ctx.cfg.header = xstrdup(value); 119 ctx.cfg.header = xstrdup(value);
116 else if (!strcmp(name, "logo")) 120 else if (!strcmp(name, "logo"))
117 ctx.cfg.logo = xstrdup(value); 121 ctx.cfg.logo = xstrdup(value);
118 else if (!strcmp(name, "index-header")) 122 else if (!strcmp(name, "index-header"))
119 ctx.cfg.index_header = xstrdup(value); 123 ctx.cfg.index_header = xstrdup(value);
120 else if (!strcmp(name, "index-info")) 124 else if (!strcmp(name, "index-info"))
121 ctx.cfg.index_info = xstrdup(value); 125 ctx.cfg.index_info = xstrdup(value);
122 else if (!strcmp(name, "logo-link")) 126 else if (!strcmp(name, "logo-link"))
123 ctx.cfg.logo_link = xstrdup(value); 127 ctx.cfg.logo_link = xstrdup(value);
124 else if (!strcmp(name, "module-link")) 128 else if (!strcmp(name, "module-link"))
125 ctx.cfg.module_link = xstrdup(value); 129 ctx.cfg.module_link = xstrdup(value);
126 else if (!strcmp(name, "strict-export")) 130 else if (!strcmp(name, "strict-export"))
127 ctx.cfg.strict_export = xstrdup(value); 131 ctx.cfg.strict_export = xstrdup(value);
128 else if (!strcmp(name, "virtual-root")) { 132 else if (!strcmp(name, "virtual-root")) {
129 ctx.cfg.virtual_root = trim_end(value, '/'); 133 ctx.cfg.virtual_root = trim_end(value, '/');
130 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 134 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
131 ctx.cfg.virtual_root = ""; 135 ctx.cfg.virtual_root = "";
132 } else if (!strcmp(name, "nocache")) 136 } else if (!strcmp(name, "nocache"))
133 ctx.cfg.nocache = atoi(value); 137 ctx.cfg.nocache = atoi(value);
134 else if (!strcmp(name, "noplainemail")) 138 else if (!strcmp(name, "noplainemail"))
135 ctx.cfg.noplainemail = atoi(value); 139 ctx.cfg.noplainemail = atoi(value);
136 else if (!strcmp(name, "noheader")) 140 else if (!strcmp(name, "noheader"))
137 ctx.cfg.noheader = atoi(value); 141 ctx.cfg.noheader = atoi(value);
138 else if (!strcmp(name, "snapshots")) 142 else if (!strcmp(name, "snapshots"))
139 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 143 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
140 else if (!strcmp(name, "enable-filter-overrides")) 144 else if (!strcmp(name, "enable-filter-overrides"))
141 ctx.cfg.enable_filter_overrides = atoi(value); 145 ctx.cfg.enable_filter_overrides = atoi(value);
142 else if (!strcmp(name, "enable-gitweb-owner")) 146 else if (!strcmp(name, "enable-gitweb-owner"))
143 ctx.cfg.enable_gitweb_owner = atoi(value); 147 ctx.cfg.enable_gitweb_owner = atoi(value);
144 else if (!strcmp(name, "enable-index-links")) 148 else if (!strcmp(name, "enable-index-links"))
145 ctx.cfg.enable_index_links = atoi(value); 149 ctx.cfg.enable_index_links = atoi(value);
146 else if (!strcmp(name, "enable-commit-graph")) 150 else if (!strcmp(name, "enable-commit-graph"))
147 ctx.cfg.enable_commit_graph = atoi(value); 151 ctx.cfg.enable_commit_graph = atoi(value);
148 else if (!strcmp(name, "enable-log-filecount")) 152 else if (!strcmp(name, "enable-log-filecount"))
149 ctx.cfg.enable_log_filecount = atoi(value); 153 ctx.cfg.enable_log_filecount = atoi(value);
150 else if (!strcmp(name, "enable-log-linecount")) 154 else if (!strcmp(name, "enable-log-linecount"))
151 ctx.cfg.enable_log_linecount = atoi(value); 155 ctx.cfg.enable_log_linecount = atoi(value);
152 else if (!strcmp(name, "enable-remote-branches")) 156 else if (!strcmp(name, "enable-remote-branches"))
153 ctx.cfg.enable_remote_branches = atoi(value); 157 ctx.cfg.enable_remote_branches = atoi(value);
154 else if (!strcmp(name, "enable-subject-links")) 158 else if (!strcmp(name, "enable-subject-links"))
155 ctx.cfg.enable_subject_links = atoi(value); 159 ctx.cfg.enable_subject_links = atoi(value);
156 else if (!strcmp(name, "enable-tree-linenumbers")) 160 else if (!strcmp(name, "enable-tree-linenumbers"))
157 ctx.cfg.enable_tree_linenumbers = atoi(value); 161 ctx.cfg.enable_tree_linenumbers = atoi(value);
158 else if (!strcmp(name, "max-stats")) 162 else if (!strcmp(name, "max-stats"))
159 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 163 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
160 else if (!strcmp(name, "cache-size")) 164 else if (!strcmp(name, "cache-size"))
161 ctx.cfg.cache_size = atoi(value); 165 ctx.cfg.cache_size = atoi(value);
162 else if (!strcmp(name, "cache-root")) 166 else if (!strcmp(name, "cache-root"))
163 ctx.cfg.cache_root = xstrdup(expand_macros(value)); 167 ctx.cfg.cache_root = xstrdup(expand_macros(value));
164 else if (!strcmp(name, "cache-root-ttl")) 168 else if (!strcmp(name, "cache-root-ttl"))
165 ctx.cfg.cache_root_ttl = atoi(value); 169 ctx.cfg.cache_root_ttl = atoi(value);
166 else if (!strcmp(name, "cache-repo-ttl")) 170 else if (!strcmp(name, "cache-repo-ttl"))
167 ctx.cfg.cache_repo_ttl = atoi(value); 171 ctx.cfg.cache_repo_ttl = atoi(value);
168 else if (!strcmp(name, "cache-scanrc-ttl")) 172 else if (!strcmp(name, "cache-scanrc-ttl"))
169 ctx.cfg.cache_scanrc_ttl = atoi(value); 173 ctx.cfg.cache_scanrc_ttl = atoi(value);
170 else if (!strcmp(name, "cache-static-ttl")) 174 else if (!strcmp(name, "cache-static-ttl"))
171 ctx.cfg.cache_static_ttl = atoi(value); 175 ctx.cfg.cache_static_ttl = atoi(value);
172 else if (!strcmp(name, "cache-dynamic-ttl")) 176 else if (!strcmp(name, "cache-dynamic-ttl"))
173 ctx.cfg.cache_dynamic_ttl = atoi(value); 177 ctx.cfg.cache_dynamic_ttl = atoi(value);
174 else if (!strcmp(name, "about-filter")) 178 else if (!strcmp(name, "about-filter"))
diff --git a/cgit.css b/cgit.css
index 008cff8..1d90057 100644
--- a/cgit.css
+++ b/cgit.css
@@ -200,193 +200,193 @@ table.list td a {
200table.list td a.ls-dir { 200table.list td a.ls-dir {
201 font-weight: bold; 201 font-weight: bold;
202 color: #00f; 202 color: #00f;
203} 203}
204 204
205table.list td a:hover { 205table.list td a:hover {
206 color: #00f; 206 color: #00f;
207} 207}
208 208
209img { 209img {
210 border: none; 210 border: none;
211} 211}
212 212
213input#switch-btn { 213input#switch-btn {
214 margin: 2px 0px 0px 0px; 214 margin: 2px 0px 0px 0px;
215} 215}
216 216
217td#sidebar input.txt { 217td#sidebar input.txt {
218 width: 100%; 218 width: 100%;
219 margin: 2px 0px 0px 0px; 219 margin: 2px 0px 0px 0px;
220} 220}
221 221
222table#grid { 222table#grid {
223 margin: 0px; 223 margin: 0px;
224} 224}
225 225
226td#content { 226td#content {
227 vertical-align: top; 227 vertical-align: top;
228 padding: 1em 2em 1em 1em; 228 padding: 1em 2em 1em 1em;
229 border: none; 229 border: none;
230} 230}
231 231
232div#summary { 232div#summary {
233 vertical-align: top; 233 vertical-align: top;
234 margin-bottom: 1em; 234 margin-bottom: 1em;
235} 235}
236 236
237table#downloads { 237table#downloads {
238 float: right; 238 float: right;
239 border-collapse: collapse; 239 border-collapse: collapse;
240 border: solid 1px #777; 240 border: solid 1px #777;
241 margin-left: 0.5em; 241 margin-left: 0.5em;
242 margin-bottom: 0.5em; 242 margin-bottom: 0.5em;
243} 243}
244 244
245table#downloads th { 245table#downloads th {
246 background-color: #ccc; 246 background-color: #ccc;
247} 247}
248 248
249div#blob { 249div#blob {
250 border: solid 1px black; 250 border: solid 1px black;
251} 251}
252 252
253div.error { 253div.error {
254 color: red; 254 color: red;
255 font-weight: bold; 255 font-weight: bold;
256 margin: 1em 2em; 256 margin: 1em 2em;
257} 257}
258 258
259a.ls-blob, a.ls-dir, a.ls-mod { 259a.ls-blob, a.ls-dir, a.ls-mod {
260 font-family: monospace; 260 font-family: monospace;
261} 261}
262 262
263td.ls-size { 263td.ls-size {
264 text-align: right; 264 text-align: right;
265 font-family: monospace; 265 font-family: monospace;
266 width: 10em; 266 width: 10em;
267} 267}
268 268
269td.ls-mode { 269td.ls-mode {
270 font-family: monospace; 270 font-family: monospace;
271 width: 10em; 271 width: 10em;
272} 272}
273 273
274table.blob { 274table.blob {
275 margin-top: 0.5em; 275 margin-top: 0.5em;
276 border-top: solid 1px black; 276 border-top: solid 1px black;
277} 277}
278 278
279table.blob td.lines { 279table.blob td.lines {
280 margin: 0; padding: 0 0 0 0.5em; 280 margin: 0; padding: 0 0 0 0.5em;
281 vertical-align: top; 281 vertical-align: top;
282 color: black; 282 color: black;
283} 283}
284 284
285table.blob td.linenumbers { 285table.blob td.linenumbers {
286 margin: 0; padding: 0 0.5em 0 0.5em; 286 margin: 0; padding: 0 0.5em 0 0.5em;
287 vertical-align: top; 287 vertical-align: top;
288 text-align: right; 288 text-align: right;
289 border-right: 1px solid gray; 289 border-right: 1px solid gray;
290} 290}
291 291
292table.blob pre { 292table.blob pre {
293 padding: 0; margin: 0; 293 padding: 0; margin: 0;
294} 294}
295 295
296table.blob a.no { 296table.blob a.no, table.ssdiff a.no {
297 color: gray; 297 color: gray;
298 text-align: right; 298 text-align: right;
299 text-decoration: none; 299 text-decoration: none;
300} 300}
301 301
302table.blob a.no a:hover { 302table.blob a.no a:hover {
303 color: black; 303 color: black;
304} 304}
305 305
306table.bin-blob { 306table.bin-blob {
307 margin-top: 0.5em; 307 margin-top: 0.5em;
308 border: solid 1px black; 308 border: solid 1px black;
309} 309}
310 310
311table.bin-blob th { 311table.bin-blob th {
312 font-family: monospace; 312 font-family: monospace;
313 white-space: pre; 313 white-space: pre;
314 border: solid 1px #777; 314 border: solid 1px #777;
315 padding: 0.5em 1em; 315 padding: 0.5em 1em;
316} 316}
317 317
318table.bin-blob td { 318table.bin-blob td {
319 font-family: monospace; 319 font-family: monospace;
320 white-space: pre; 320 white-space: pre;
321 border-left: solid 1px #777; 321 border-left: solid 1px #777;
322 padding: 0em 1em; 322 padding: 0em 1em;
323} 323}
324 324
325table.nowrap td { 325table.nowrap td {
326 white-space: nowrap; 326 white-space: nowrap;
327} 327}
328 328
329table.commit-info { 329table.commit-info {
330 border-collapse: collapse; 330 border-collapse: collapse;
331 margin-top: 1.5em; 331 margin-top: 1.5em;
332} 332}
333 333
334table.commit-info th { 334table.commit-info th {
335 text-align: left; 335 text-align: left;
336 font-weight: normal; 336 font-weight: normal;
337 padding: 0.1em 1em 0.1em 0.1em; 337 padding: 0.1em 1em 0.1em 0.1em;
338 vertical-align: top; 338 vertical-align: top;
339} 339}
340 340
341table.commit-info td { 341table.commit-info td {
342 font-weight: normal; 342 font-weight: normal;
343 padding: 0.1em 1em 0.1em 0.1em; 343 padding: 0.1em 1em 0.1em 0.1em;
344} 344}
345 345
346div.commit-subject { 346div.commit-subject {
347 font-weight: bold; 347 font-weight: bold;
348 font-size: 125%; 348 font-size: 125%;
349 margin: 1.5em 0em 0.5em 0em; 349 margin: 1.5em 0em 0.5em 0em;
350 padding: 0em; 350 padding: 0em;
351} 351}
352 352
353div.commit-msg { 353div.commit-msg {
354 white-space: pre; 354 white-space: pre;
355 font-family: monospace; 355 font-family: monospace;
356} 356}
357 357
358div.notes-header { 358div.notes-header {
359 font-weight: bold; 359 font-weight: bold;
360 padding-top: 1.5em; 360 padding-top: 1.5em;
361} 361}
362 362
363div.notes { 363div.notes {
364 white-space: pre; 364 white-space: pre;
365 font-family: monospace; 365 font-family: monospace;
366 border: solid 1px #ee9; 366 border: solid 1px #ee9;
367 background-color: #ffd; 367 background-color: #ffd;
368 padding: 0.3em 2em 0.3em 1em; 368 padding: 0.3em 2em 0.3em 1em;
369 float: left; 369 float: left;
370} 370}
371 371
372div.notes-footer { 372div.notes-footer {
373 clear: left; 373 clear: left;
374} 374}
375 375
376div.diffstat-header { 376div.diffstat-header {
377 font-weight: bold; 377 font-weight: bold;
378 padding-top: 1.5em; 378 padding-top: 1.5em;
379} 379}
380 380
381table.diffstat { 381table.diffstat {
382 border-collapse: collapse; 382 border-collapse: collapse;
383 border: solid 1px #aaa; 383 border: solid 1px #aaa;
384 background-color: #eee; 384 background-color: #eee;
385} 385}
386 386
387table.diffstat th { 387table.diffstat th {
388 font-weight: normal; 388 font-weight: normal;
389 text-align: left; 389 text-align: left;
390 text-decoration: underline; 390 text-decoration: underline;
391 padding: 0.1em 1em 0.1em 0.1em; 391 padding: 0.1em 1em 0.1em 0.1em;
392 font-size: 100%; 392 font-size: 100%;
diff --git a/cgit.h b/cgit.h
index 74aa340..b5f00fc 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,169 +1,171 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22#include <notes.h> 22#include <notes.h>
23#include <graph.h> 23#include <graph.h>
24 24
25 25
26/* 26/*
27 * Dateformats used on misc. pages 27 * Dateformats used on misc. pages
28 */ 28 */
29#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 29#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
30#define FMT_SHORTDATE "%Y-%m-%d" 30#define FMT_SHORTDATE "%Y-%m-%d"
31#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 31#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
32 32
33 33
34/* 34/*
35 * Limits used for relative dates 35 * Limits used for relative dates
36 */ 36 */
37#define TM_MIN 60 37#define TM_MIN 60
38#define TM_HOUR (TM_MIN * 60) 38#define TM_HOUR (TM_MIN * 60)
39#define TM_DAY (TM_HOUR * 24) 39#define TM_DAY (TM_HOUR * 24)
40#define TM_WEEK (TM_DAY * 7) 40#define TM_WEEK (TM_DAY * 7)
41#define TM_YEAR (TM_DAY * 365) 41#define TM_YEAR (TM_DAY * 365)
42#define TM_MONTH (TM_YEAR / 12.0) 42#define TM_MONTH (TM_YEAR / 12.0)
43 43
44 44
45/* 45/*
46 * Default encoding 46 * Default encoding
47 */ 47 */
48#define PAGE_ENCODING "UTF-8" 48#define PAGE_ENCODING "UTF-8"
49 49
50typedef void (*configfn)(const char *name, const char *value); 50typedef void (*configfn)(const char *name, const char *value);
51typedef void (*filepair_fn)(struct diff_filepair *pair); 51typedef void (*filepair_fn)(struct diff_filepair *pair);
52typedef void (*linediff_fn)(char *line, int len); 52typedef void (*linediff_fn)(char *line, int len);
53 53
54struct cgit_filter { 54struct cgit_filter {
55 char *cmd; 55 char *cmd;
56 char **argv; 56 char **argv;
57 int old_stdout; 57 int old_stdout;
58 int pipe_fh[2]; 58 int pipe_fh[2];
59 int pid; 59 int pid;
60 int exitstatus; 60 int exitstatus;
61}; 61};
62 62
63struct cgit_repo { 63struct cgit_repo {
64 char *url; 64 char *url;
65 char *name; 65 char *name;
66 char *path; 66 char *path;
67 char *desc; 67 char *desc;
68 char *owner; 68 char *owner;
69 char *defbranch; 69 char *defbranch;
70 char *module_link; 70 char *module_link;
71 char *readme; 71 char *readme;
72 char *section; 72 char *section;
73 char *clone_url; 73 char *clone_url;
74 char *logo;
75 char *logo_link;
74 int snapshots; 76 int snapshots;
75 int enable_commit_graph; 77 int enable_commit_graph;
76 int enable_log_filecount; 78 int enable_log_filecount;
77 int enable_log_linecount; 79 int enable_log_linecount;
78 int enable_remote_branches; 80 int enable_remote_branches;
79 int enable_subject_links; 81 int enable_subject_links;
80 int max_stats; 82 int max_stats;
81 time_t mtime; 83 time_t mtime;
82 struct cgit_filter *about_filter; 84 struct cgit_filter *about_filter;
83 struct cgit_filter *commit_filter; 85 struct cgit_filter *commit_filter;
84 struct cgit_filter *source_filter; 86 struct cgit_filter *source_filter;
85}; 87};
86 88
87typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 89typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
88 const char *value); 90 const char *value);
89 91
90struct cgit_repolist { 92struct cgit_repolist {
91 int length; 93 int length;
92 int count; 94 int count;
93 struct cgit_repo *repos; 95 struct cgit_repo *repos;
94}; 96};
95 97
96struct commitinfo { 98struct commitinfo {
97 struct commit *commit; 99 struct commit *commit;
98 char *author; 100 char *author;
99 char *author_email; 101 char *author_email;
100 unsigned long author_date; 102 unsigned long author_date;
101 char *committer; 103 char *committer;
102 char *committer_email; 104 char *committer_email;
103 unsigned long committer_date; 105 unsigned long committer_date;
104 char *subject; 106 char *subject;
105 char *msg; 107 char *msg;
106 char *msg_encoding; 108 char *msg_encoding;
107}; 109};
108 110
109struct taginfo { 111struct taginfo {
110 char *tagger; 112 char *tagger;
111 char *tagger_email; 113 char *tagger_email;
112 unsigned long tagger_date; 114 unsigned long tagger_date;
113 char *msg; 115 char *msg;
114}; 116};
115 117
116struct refinfo { 118struct refinfo {
117 const char *refname; 119 const char *refname;
118 struct object *object; 120 struct object *object;
119 union { 121 union {
120 struct taginfo *tag; 122 struct taginfo *tag;
121 struct commitinfo *commit; 123 struct commitinfo *commit;
122 }; 124 };
123}; 125};
124 126
125struct reflist { 127struct reflist {
126 struct refinfo **refs; 128 struct refinfo **refs;
127 int alloc; 129 int alloc;
128 int count; 130 int count;
129}; 131};
130 132
131struct cgit_query { 133struct cgit_query {
132 int has_symref; 134 int has_symref;
133 int has_sha1; 135 int has_sha1;
134 char *raw; 136 char *raw;
135 char *repo; 137 char *repo;
136 char *page; 138 char *page;
137 char *search; 139 char *search;
138 char *grep; 140 char *grep;
139 char *head; 141 char *head;
140 char *sha1; 142 char *sha1;
141 char *sha2; 143 char *sha2;
142 char *path; 144 char *path;
143 char *name; 145 char *name;
144 char *mimetype; 146 char *mimetype;
145 char *url; 147 char *url;
146 char *period; 148 char *period;
147 int ofs; 149 int ofs;
148 int nohead; 150 int nohead;
149 char *sort; 151 char *sort;
150 int showmsg; 152 int showmsg;
151 int ssdiff; 153 int ssdiff;
152 int show_all; 154 int show_all;
153 int context; 155 int context;
154 int ignorews; 156 int ignorews;
155 char *vpath; 157 char *vpath;
156}; 158};
157 159
158struct cgit_config { 160struct cgit_config {
159 char *agefile; 161 char *agefile;
160 char *cache_root; 162 char *cache_root;
161 char *clone_prefix; 163 char *clone_prefix;
162 char *css; 164 char *css;
163 char *favicon; 165 char *favicon;
164 char *footer; 166 char *footer;
165 char *head_include; 167 char *head_include;
166 char *header; 168 char *header;
167 char *index_header; 169 char *index_header;
168 char *index_info; 170 char *index_info;
169 char *logo; 171 char *logo;
diff --git a/cgit.png b/cgit.png
index d7f70bc..0bdf5a7 100644
--- a/cgit.png
+++ b/cgit.png
Binary files differ
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index a832830..c3698a6 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -294,192 +294,201 @@ section::
294 The name of the current repository section - all repositories defined 294 The name of the current repository section - all repositories defined
295 after this option will inherit the current section name. Default value: 295 after this option will inherit the current section name. Default value:
296 none. 296 none.
297 297
298section-from-path:: 298section-from-path::
299 A number which, if specified before scan-path, specifies how many 299 A number which, if specified before scan-path, specifies how many
300 path elements from each repo path to use as a default section name. 300 path elements from each repo path to use as a default section name.
301 If negative, cgit will discard the specified number of path elements 301 If negative, cgit will discard the specified number of path elements
302 above the repo directory. Default value: 0. 302 above the repo directory. Default value: 0.
303 303
304side-by-side-diffs:: 304side-by-side-diffs::
305 If set to "1" shows side-by-side diffs instead of unidiffs per 305 If set to "1" shows side-by-side diffs instead of unidiffs per
306 default. Default value: "0". 306 default. Default value: "0".
307 307
308snapshots:: 308snapshots::
309 Text which specifies the default set of snapshot formats generated by 309 Text which specifies the default set of snapshot formats generated by
310 cgit. The value is a space-separated list of zero or more of the 310 cgit. The value is a space-separated list of zero or more of the
311 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 311 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
312 312
313source-filter:: 313source-filter::
314 Specifies a command which will be invoked to format plaintext blobs 314 Specifies a command which will be invoked to format plaintext blobs
315 in the tree view. The command will get the blob content on its STDIN 315 in the tree view. The command will get the blob content on its STDIN
316 and the name of the blob as its only command line argument. The STDOUT 316 and the name of the blob as its only command line argument. The STDOUT
317 from the command will be included verbatim as the blob contents, i.e. 317 from the command will be included verbatim as the blob contents, i.e.
318 this can be used to implement e.g. syntax highlighting. Default value: 318 this can be used to implement e.g. syntax highlighting. Default value:
319 none. 319 none.
320 320
321summary-branches:: 321summary-branches::
322 Specifies the number of branches to display in the repository "summary" 322 Specifies the number of branches to display in the repository "summary"
323 view. Default value: "10". 323 view. Default value: "10".
324 324
325summary-log:: 325summary-log::
326 Specifies the number of log entries to display in the repository 326 Specifies the number of log entries to display in the repository
327 "summary" view. Default value: "10". 327 "summary" view. Default value: "10".
328 328
329summary-tags:: 329summary-tags::
330 Specifies the number of tags to display in the repository "summary" 330 Specifies the number of tags to display in the repository "summary"
331 view. Default value: "10". 331 view. Default value: "10".
332 332
333strict-export:: 333strict-export::
334 Filename which, if specified, needs to be present within the repository 334 Filename which, if specified, needs to be present within the repository
335 for cgit to allow access to that repository. This can be used to emulate 335 for cgit to allow access to that repository. This can be used to emulate
336 gitweb's EXPORT_OK and STRICT_EXPORT functionality and limit cgit's 336 gitweb's EXPORT_OK and STRICT_EXPORT functionality and limit cgit's
337 repositories to match those exported by git-daemon. This option MUST come 337 repositories to match those exported by git-daemon. This option MUST come
338 before 'scan-path'. 338 before 'scan-path'.
339 339
340virtual-root:: 340virtual-root::
341 Url which, if specified, will be used as root for all cgit links. It 341 Url which, if specified, will be used as root for all cgit links. It
342 will also cause cgit to generate 'virtual urls', i.e. urls like 342 will also cause cgit to generate 'virtual urls', i.e. urls like
343 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 343 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
344 value: none. 344 value: none.
345 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 345 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
346 same kind of virtual urls, so this option will probably be deprecated. 346 same kind of virtual urls, so this option will probably be deprecated.
347 347
348REPOSITORY SETTINGS 348REPOSITORY SETTINGS
349------------------- 349-------------------
350repo.about-filter:: 350repo.about-filter::
351 Override the default about-filter. Default value: none. See also: 351 Override the default about-filter. Default value: none. See also:
352 "enable-filter-overrides". 352 "enable-filter-overrides".
353 353
354repo.clone-url:: 354repo.clone-url::
355 A list of space-separated urls which can be used to clone this repo. 355 A list of space-separated urls which can be used to clone this repo.
356 Default value: none. 356 Default value: none.
357 357
358repo.commit-filter:: 358repo.commit-filter::
359 Override the default commit-filter. Default value: none. See also: 359 Override the default commit-filter. Default value: none. See also:
360 "enable-filter-overrides". 360 "enable-filter-overrides".
361 361
362repo.defbranch:: 362repo.defbranch::
363 The name of the default branch for this repository. If no such branch 363 The name of the default branch for this repository. If no such branch
364 exists in the repository, the first branch name (when sorted) is used 364 exists in the repository, the first branch name (when sorted) is used
365 as default instead. Default value: "master". 365 as default instead. Default value: "master".
366 366
367repo.desc:: 367repo.desc::
368 The value to show as repository description. Default value: none. 368 The value to show as repository description. Default value: none.
369 369
370repo.enable-commit-graph:: 370repo.enable-commit-graph::
371 A flag which can be used to disable the global setting 371 A flag which can be used to disable the global setting
372 `enable-commit-graph'. Default value: none. 372 `enable-commit-graph'. Default value: none.
373 373
374repo.enable-log-filecount:: 374repo.enable-log-filecount::
375 A flag which can be used to disable the global setting 375 A flag which can be used to disable the global setting
376 `enable-log-filecount'. Default value: none. 376 `enable-log-filecount'. Default value: none.
377 377
378repo.enable-log-linecount:: 378repo.enable-log-linecount::
379 A flag which can be used to disable the global setting 379 A flag which can be used to disable the global setting
380 `enable-log-linecount'. Default value: none. 380 `enable-log-linecount'. Default value: none.
381 381
382repo.enable-remote-branches:: 382repo.enable-remote-branches::
383 Flag which, when set to "1", will make cgit display remote branches 383 Flag which, when set to "1", will make cgit display remote branches
384 in the summary and refs views. Default value: <enable-remote-branches>. 384 in the summary and refs views. Default value: <enable-remote-branches>.
385 385
386repo.enable-subject-links:: 386repo.enable-subject-links::
387 A flag which can be used to override the global setting 387 A flag which can be used to override the global setting
388 `enable-subject-links'. Default value: none. 388 `enable-subject-links'. Default value: none.
389 389
390repo.logo::
391 Url which specifies the source of an image which will be used as a logo
392 on this repo's pages. Default value: global logo.
393
394repo.logo-link::
395 Url loaded when clicking on the cgit logo image. If unspecified the
396 calculated url of the repository index page will be used. Default
397 value: global logo-link.
398
390repo.max-stats:: 399repo.max-stats::
391 Override the default maximum statistics period. Valid values are equal 400 Override the default maximum statistics period. Valid values are equal
392 to the values specified for the global "max-stats" setting. Default 401 to the values specified for the global "max-stats" setting. Default
393 value: none. 402 value: none.
394 403
395repo.name:: 404repo.name::
396 The value to show as repository name. Default value: <repo.url>. 405 The value to show as repository name. Default value: <repo.url>.
397 406
398repo.owner:: 407repo.owner::
399 A value used to identify the owner of the repository. Default value: 408 A value used to identify the owner of the repository. Default value:
400 none. 409 none.
401 410
402repo.path:: 411repo.path::
403 An absolute path to the repository directory. For non-bare repositories 412 An absolute path to the repository directory. For non-bare repositories
404 this is the .git-directory. Default value: none. 413 this is the .git-directory. Default value: none.
405 414
406repo.readme:: 415repo.readme::
407 A path (relative to <repo.path>) which specifies a file to include 416 A path (relative to <repo.path>) which specifies a file to include
408 verbatim as the "About" page for this repo. You may also specify a 417 verbatim as the "About" page for this repo. You may also specify a
409 git refspec by head or by hash by prepending the refspec followed by 418 git refspec by head or by hash by prepending the refspec followed by
410 a colon. For example, "master:docs/readme.mkd" Default value: <readme>. 419 a colon. For example, "master:docs/readme.mkd" Default value: <readme>.
411 420
412repo.snapshots:: 421repo.snapshots::
413 A mask of allowed snapshot-formats for this repo, restricted by the 422 A mask of allowed snapshot-formats for this repo, restricted by the
414 "snapshots" global setting. Default value: <snapshots>. 423 "snapshots" global setting. Default value: <snapshots>.
415 424
416repo.section:: 425repo.section::
417 Override the current section name for this repository. Default value: 426 Override the current section name for this repository. Default value:
418 none. 427 none.
419 428
420repo.source-filter:: 429repo.source-filter::
421 Override the default source-filter. Default value: none. See also: 430 Override the default source-filter. Default value: none. See also:
422 "enable-filter-overrides". 431 "enable-filter-overrides".
423 432
424repo.url:: 433repo.url::
425 The relative url used to access the repository. This must be the first 434 The relative url used to access the repository. This must be the first
426 setting specified for each repo. Default value: none. 435 setting specified for each repo. Default value: none.
427 436
428 437
429REPOSITORY-SPECIFIC CGITRC FILE 438REPOSITORY-SPECIFIC CGITRC FILE
430------------------------------- 439-------------------------------
431When the option "scan-path" is used to auto-discover git repositories, cgit 440When the option "scan-path" is used to auto-discover git repositories, cgit
432will try to parse the file "cgitrc" within any found repository. Such a 441will try to parse the file "cgitrc" within any found repository. Such a
433repo-specific config file may contain any of the repo-specific options 442repo-specific config file may contain any of the repo-specific options
434described above, except "repo.url" and "repo.path". Additionally, the "filter" 443described above, except "repo.url" and "repo.path". Additionally, the "filter"
435options are only acknowledged in repo-specific config files when 444options are only acknowledged in repo-specific config files when
436"enable-filter-overrides" is set to "1". 445"enable-filter-overrides" is set to "1".
437 446
438Note: the "repo." prefix is dropped from the option names in repo-specific 447Note: the "repo." prefix is dropped from the option names in repo-specific
439config files, e.g. "repo.desc" becomes "desc". 448config files, e.g. "repo.desc" becomes "desc".
440 449
441 450
442EXAMPLE CGITRC FILE 451EXAMPLE CGITRC FILE
443------------------- 452-------------------
444 453
445.... 454....
446# Enable caching of up to 1000 output entriess 455# Enable caching of up to 1000 output entriess
447cache-size=1000 456cache-size=1000
448 457
449 458
450# Specify some default clone prefixes 459# Specify some default clone prefixes
451clone-prefix=git://example.com ssh://example.com/pub/git http://example.com/git 460clone-prefix=git://example.com ssh://example.com/pub/git http://example.com/git
452 461
453# Specify the css url 462# Specify the css url
454css=/css/cgit.css 463css=/css/cgit.css
455 464
456 465
457# Show extra links for each repository on the index page 466# Show extra links for each repository on the index page
458enable-index-links=1 467enable-index-links=1
459 468
460 469
461# Enable ASCII art commit history graph on the log pages 470# Enable ASCII art commit history graph on the log pages
462enable-commit-graph=1 471enable-commit-graph=1
463 472
464 473
465# Show number of affected files per commit on the log pages 474# Show number of affected files per commit on the log pages
466enable-log-filecount=1 475enable-log-filecount=1
467 476
468 477
469# Show number of added/removed lines per commit on the log pages 478# Show number of added/removed lines per commit on the log pages
470enable-log-linecount=1 479enable-log-linecount=1
471 480
472 481
473# Add a cgit favicon 482# Add a cgit favicon
474favicon=/favicon.ico 483favicon=/favicon.ico
475 484
476 485
477# Use a custom logo 486# Use a custom logo
478logo=/img/mylogo.png 487logo=/img/mylogo.png
479 488
480 489
481# Enable statistics per week, month and quarter 490# Enable statistics per week, month and quarter
482max-stats=quarter 491max-stats=quarter
483 492
484 493
485# Set the title and heading of the repository index page 494# Set the title and heading of the repository index page
diff --git a/ui-diff.c b/ui-diff.c
index 7ff7e46..a53425d 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -1,132 +1,143 @@
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#include "ui-ssdiff.h"
13 13
14unsigned char old_rev_sha1[20]; 14unsigned char old_rev_sha1[20];
15unsigned char new_rev_sha1[20]; 15unsigned char new_rev_sha1[20];
16 16
17static int files, slots; 17static int files, slots;
18static int total_adds, total_rems, max_changes; 18static int total_adds, total_rems, max_changes;
19static int lines_added, lines_removed; 19static int lines_added, lines_removed;
20 20
21static struct fileinfo { 21static struct fileinfo {
22 char status; 22 char status;
23 unsigned char old_sha1[20]; 23 unsigned char old_sha1[20];
24 unsigned char new_sha1[20]; 24 unsigned char new_sha1[20];
25 unsigned short old_mode; 25 unsigned short old_mode;
26 unsigned short new_mode; 26 unsigned short new_mode;
27 char *old_path; 27 char *old_path;
28 char *new_path; 28 char *new_path;
29 unsigned int added; 29 unsigned int added;
30 unsigned int removed; 30 unsigned int removed;
31 unsigned long old_size; 31 unsigned long old_size;
32 unsigned long new_size; 32 unsigned long new_size;
33 int binary:1; 33 int binary:1;
34} *items; 34} *items;
35 35
36static int use_ssdiff = 0; 36static int use_ssdiff = 0;
37static struct diff_filepair *current_filepair;
38
39struct diff_filespec *cgit_get_current_old_file(void)
40{
41 return current_filepair->one;
42}
43
44struct diff_filespec *cgit_get_current_new_file(void)
45{
46 return current_filepair->two;
47}
37 48
38static void print_fileinfo(struct fileinfo *info) 49static void print_fileinfo(struct fileinfo *info)
39{ 50{
40 char *class; 51 char *class;
41 52
42 switch (info->status) { 53 switch (info->status) {
43 case DIFF_STATUS_ADDED: 54 case DIFF_STATUS_ADDED:
44 class = "add"; 55 class = "add";
45 break; 56 break;
46 case DIFF_STATUS_COPIED: 57 case DIFF_STATUS_COPIED:
47 class = "cpy"; 58 class = "cpy";
48 break; 59 break;
49 case DIFF_STATUS_DELETED: 60 case DIFF_STATUS_DELETED:
50 class = "del"; 61 class = "del";
51 break; 62 break;
52 case DIFF_STATUS_MODIFIED: 63 case DIFF_STATUS_MODIFIED:
53 class = "upd"; 64 class = "upd";
54 break; 65 break;
55 case DIFF_STATUS_RENAMED: 66 case DIFF_STATUS_RENAMED:
56 class = "mov"; 67 class = "mov";
57 break; 68 break;
58 case DIFF_STATUS_TYPE_CHANGED: 69 case DIFF_STATUS_TYPE_CHANGED:
59 class = "typ"; 70 class = "typ";
60 break; 71 break;
61 case DIFF_STATUS_UNKNOWN: 72 case DIFF_STATUS_UNKNOWN:
62 class = "unk"; 73 class = "unk";
63 break; 74 break;
64 case DIFF_STATUS_UNMERGED: 75 case DIFF_STATUS_UNMERGED:
65 class = "stg"; 76 class = "stg";
66 break; 77 break;
67 default: 78 default:
68 die("bug: unhandled diff status %c", info->status); 79 die("bug: unhandled diff status %c", info->status);
69 } 80 }
70 81
71 html("<tr>"); 82 html("<tr>");
72 htmlf("<td class='mode'>"); 83 htmlf("<td class='mode'>");
73 if (is_null_sha1(info->new_sha1)) { 84 if (is_null_sha1(info->new_sha1)) {
74 cgit_print_filemode(info->old_mode); 85 cgit_print_filemode(info->old_mode);
75 } else { 86 } else {
76 cgit_print_filemode(info->new_mode); 87 cgit_print_filemode(info->new_mode);
77 } 88 }
78 89
79 if (info->old_mode != info->new_mode && 90 if (info->old_mode != info->new_mode &&
80 !is_null_sha1(info->old_sha1) && 91 !is_null_sha1(info->old_sha1) &&
81 !is_null_sha1(info->new_sha1)) { 92 !is_null_sha1(info->new_sha1)) {
82 html("<span class='modechange'>["); 93 html("<span class='modechange'>[");
83 cgit_print_filemode(info->old_mode); 94 cgit_print_filemode(info->old_mode);
84 html("]</span>"); 95 html("]</span>");
85 } 96 }
86 htmlf("</td><td class='%s'>", class); 97 htmlf("</td><td class='%s'>", class);
87 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, 98 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
88 ctx.qry.sha2, info->new_path, 0); 99 ctx.qry.sha2, info->new_path, 0);
89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 100 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
90 htmlf(" (%s from %s)", 101 htmlf(" (%s from %s)",
91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 102 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
92 info->old_path); 103 info->old_path);
93 html("</td><td class='right'>"); 104 html("</td><td class='right'>");
94 if (info->binary) { 105 if (info->binary) {
95 htmlf("bin</td><td class='graph'>%ld -> %ld bytes", 106 htmlf("bin</td><td class='graph'>%ld -> %ld bytes",
96 info->old_size, info->new_size); 107 info->old_size, info->new_size);
97 return; 108 return;
98 } 109 }
99 htmlf("%d", info->added + info->removed); 110 htmlf("%d", info->added + info->removed);
100 html("</td><td class='graph'>"); 111 html("</td><td class='graph'>");
101 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes)); 112 htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
102 htmlf("<td class='add' style='width: %.1f%%;'/>", 113 htmlf("<td class='add' style='width: %.1f%%;'/>",
103 info->added * 100.0 / max_changes); 114 info->added * 100.0 / max_changes);
104 htmlf("<td class='rem' style='width: %.1f%%;'/>", 115 htmlf("<td class='rem' style='width: %.1f%%;'/>",
105 info->removed * 100.0 / max_changes); 116 info->removed * 100.0 / max_changes);
106 htmlf("<td class='none' style='width: %.1f%%;'/>", 117 htmlf("<td class='none' style='width: %.1f%%;'/>",
107 (max_changes - info->removed - info->added) * 100.0 / max_changes); 118 (max_changes - info->removed - info->added) * 100.0 / max_changes);
108 html("</tr></table></td></tr>\n"); 119 html("</tr></table></td></tr>\n");
109} 120}
110 121
111static void count_diff_lines(char *line, int len) 122static void count_diff_lines(char *line, int len)
112{ 123{
113 if (line && (len > 0)) { 124 if (line && (len > 0)) {
114 if (line[0] == '+') 125 if (line[0] == '+')
115 lines_added++; 126 lines_added++;
116 else if (line[0] == '-') 127 else if (line[0] == '-')
117 lines_removed++; 128 lines_removed++;
118 } 129 }
119} 130}
120 131
121static void inspect_filepair(struct diff_filepair *pair) 132static void inspect_filepair(struct diff_filepair *pair)
122{ 133{
123 int binary = 0; 134 int binary = 0;
124 unsigned long old_size = 0; 135 unsigned long old_size = 0;
125 unsigned long new_size = 0; 136 unsigned long new_size = 0;
126 files++; 137 files++;
127 lines_added = 0; 138 lines_added = 0;
128 lines_removed = 0; 139 lines_removed = 0;
129 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, 140 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size,
130 &binary, 0, ctx.qry.ignorews, count_diff_lines); 141 &binary, 0, ctx.qry.ignorews, count_diff_lines);
131 if (files >= slots) { 142 if (files >= slots) {
132 if (slots == 0) 143 if (slots == 0)
@@ -191,181 +202,182 @@ void cgit_print_diffstat(const unsigned char *old_sha1,
191} 202}
192 203
193 204
194/* 205/*
195 * print a single line returned from xdiff 206 * print a single line returned from xdiff
196 */ 207 */
197static void print_line(char *line, int len) 208static void print_line(char *line, int len)
198{ 209{
199 char *class = "ctx"; 210 char *class = "ctx";
200 char c = line[len-1]; 211 char c = line[len-1];
201 212
202 if (line[0] == '+') 213 if (line[0] == '+')
203 class = "add"; 214 class = "add";
204 else if (line[0] == '-') 215 else if (line[0] == '-')
205 class = "del"; 216 class = "del";
206 else if (line[0] == '@') 217 else if (line[0] == '@')
207 class = "hunk"; 218 class = "hunk";
208 219
209 htmlf("<div class='%s'>", class); 220 htmlf("<div class='%s'>", class);
210 line[len-1] = '\0'; 221 line[len-1] = '\0';
211 html_txt(line); 222 html_txt(line);
212 html("</div>"); 223 html("</div>");
213 line[len-1] = c; 224 line[len-1] = c;
214} 225}
215 226
216static void header(unsigned char *sha1, char *path1, int mode1, 227static void header(unsigned char *sha1, char *path1, int mode1,
217 unsigned char *sha2, char *path2, int mode2) 228 unsigned char *sha2, char *path2, int mode2)
218{ 229{
219 char *abbrev1, *abbrev2; 230 char *abbrev1, *abbrev2;
220 int subproject; 231 int subproject;
221 232
222 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); 233 subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
223 html("<div class='head'>"); 234 html("<div class='head'>");
224 html("diff --git a/"); 235 html("diff --git a/");
225 html_txt(path1); 236 html_txt(path1);
226 html(" b/"); 237 html(" b/");
227 html_txt(path2); 238 html_txt(path2);
228 239
229 if (is_null_sha1(sha1)) 240 if (is_null_sha1(sha1))
230 path1 = "dev/null"; 241 path1 = "dev/null";
231 if (is_null_sha1(sha2)) 242 if (is_null_sha1(sha2))
232 path2 = "dev/null"; 243 path2 = "dev/null";
233 244
234 if (mode1 == 0) 245 if (mode1 == 0)
235 htmlf("<br/>new file mode %.6o", mode2); 246 htmlf("<br/>new file mode %.6o", mode2);
236 247
237 if (mode2 == 0) 248 if (mode2 == 0)
238 htmlf("<br/>deleted file mode %.6o", mode1); 249 htmlf("<br/>deleted file mode %.6o", mode1);
239 250
240 if (!subproject) { 251 if (!subproject) {
241 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); 252 abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
242 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV)); 253 abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV));
243 htmlf("<br/>index %s..%s", abbrev1, abbrev2); 254 htmlf("<br/>index %s..%s", abbrev1, abbrev2);
244 free(abbrev1); 255 free(abbrev1);
245 free(abbrev2); 256 free(abbrev2);
246 if (mode1 != 0 && mode2 != 0) { 257 if (mode1 != 0 && mode2 != 0) {
247 htmlf(" %.6o", mode1); 258 htmlf(" %.6o", mode1);
248 if (mode2 != mode1) 259 if (mode2 != mode1)
249 htmlf("..%.6o", mode2); 260 htmlf("..%.6o", mode2);
250 } 261 }
251 html("<br/>--- a/"); 262 html("<br/>--- a/");
252 if (mode1 != 0) 263 if (mode1 != 0)
253 cgit_tree_link(path1, NULL, NULL, ctx.qry.head, 264 cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
254 sha1_to_hex(old_rev_sha1), path1); 265 sha1_to_hex(old_rev_sha1), path1);
255 else 266 else
256 html_txt(path1); 267 html_txt(path1);
257 html("<br/>+++ b/"); 268 html("<br/>+++ b/");
258 if (mode2 != 0) 269 if (mode2 != 0)
259 cgit_tree_link(path2, NULL, NULL, ctx.qry.head, 270 cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
260 sha1_to_hex(new_rev_sha1), path2); 271 sha1_to_hex(new_rev_sha1), path2);
261 else 272 else
262 html_txt(path2); 273 html_txt(path2);
263 } 274 }
264 html("</div>"); 275 html("</div>");
265} 276}
266 277
267static void print_ssdiff_link() 278static void print_ssdiff_link()
268{ 279{
269 if (!strcmp(ctx.qry.page, "diff")) { 280 if (!strcmp(ctx.qry.page, "diff")) {
270 if (use_ssdiff) 281 if (use_ssdiff)
271 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head, 282 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
272 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1); 283 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1);
273 else 284 else
274 cgit_diff_link("Side-by-side diff", NULL, NULL, 285 cgit_diff_link("Side-by-side diff", NULL, NULL,
275 ctx.qry.head, ctx.qry.sha1, 286 ctx.qry.head, ctx.qry.sha1,
276 ctx.qry.sha2, ctx.qry.path, 1); 287 ctx.qry.sha2, ctx.qry.path, 1);
277 } 288 }
278} 289}
279 290
280static void filepair_cb(struct diff_filepair *pair) 291static void filepair_cb(struct diff_filepair *pair)
281{ 292{
282 unsigned long old_size = 0; 293 unsigned long old_size = 0;
283 unsigned long new_size = 0; 294 unsigned long new_size = 0;
284 int binary = 0; 295 int binary = 0;
285 linediff_fn print_line_fn = print_line; 296 linediff_fn print_line_fn = print_line;
286 297
298 current_filepair = pair;
287 if (use_ssdiff) { 299 if (use_ssdiff) {
288 cgit_ssdiff_header_begin(); 300 cgit_ssdiff_header_begin();
289 print_line_fn = cgit_ssdiff_line_cb; 301 print_line_fn = cgit_ssdiff_line_cb;
290 } 302 }
291 header(pair->one->sha1, pair->one->path, pair->one->mode, 303 header(pair->one->sha1, pair->one->path, pair->one->mode,
292 pair->two->sha1, pair->two->path, pair->two->mode); 304 pair->two->sha1, pair->two->path, pair->two->mode);
293 if (use_ssdiff) 305 if (use_ssdiff)
294 cgit_ssdiff_header_end(); 306 cgit_ssdiff_header_end();
295 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 307 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
296 if (S_ISGITLINK(pair->one->mode)) 308 if (S_ISGITLINK(pair->one->mode))
297 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 309 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
298 if (S_ISGITLINK(pair->two->mode)) 310 if (S_ISGITLINK(pair->two->mode))
299 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 311 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
300 if (use_ssdiff) 312 if (use_ssdiff)
301 cgit_ssdiff_footer(); 313 cgit_ssdiff_footer();
302 return; 314 return;
303 } 315 }
304 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 316 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
305 &new_size, &binary, ctx.qry.context, 317 &new_size, &binary, ctx.qry.context,
306 ctx.qry.ignorews, print_line_fn)) 318 ctx.qry.ignorews, print_line_fn))
307 cgit_print_error("Error running diff"); 319 cgit_print_error("Error running diff");
308 if (binary) { 320 if (binary) {
309 if (use_ssdiff) 321 if (use_ssdiff)
310 html("<tr><td colspan='4'>Binary files differ</td></tr>"); 322 html("<tr><td colspan='4'>Binary files differ</td></tr>");
311 else 323 else
312 html("Binary files differ"); 324 html("Binary files differ");
313 } 325 }
314 if (use_ssdiff) 326 if (use_ssdiff)
315 cgit_ssdiff_footer(); 327 cgit_ssdiff_footer();
316} 328}
317 329
318void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) 330void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
319{ 331{
320 enum object_type type; 332 enum object_type type;
321 unsigned long size; 333 unsigned long size;
322 struct commit *commit, *commit2; 334 struct commit *commit, *commit2;
323 335
324 if (!new_rev) 336 if (!new_rev)
325 new_rev = ctx.qry.head; 337 new_rev = ctx.qry.head;
326 get_sha1(new_rev, new_rev_sha1); 338 get_sha1(new_rev, new_rev_sha1);
327 type = sha1_object_info(new_rev_sha1, &size); 339 type = sha1_object_info(new_rev_sha1, &size);
328 if (type == OBJ_BAD) { 340 if (type == OBJ_BAD) {
329 cgit_print_error(fmt("Bad object name: %s", new_rev)); 341 cgit_print_error(fmt("Bad object name: %s", new_rev));
330 return; 342 return;
331 } 343 }
332 commit = lookup_commit_reference(new_rev_sha1); 344 commit = lookup_commit_reference(new_rev_sha1);
333 if (!commit || parse_commit(commit)) 345 if (!commit || parse_commit(commit))
334 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1))); 346 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(new_rev_sha1)));
335 347
336 if (old_rev) 348 if (old_rev)
337 get_sha1(old_rev, old_rev_sha1); 349 get_sha1(old_rev, old_rev_sha1);
338 else if (commit->parents && commit->parents->item) 350 else if (commit->parents && commit->parents->item)
339 hashcpy(old_rev_sha1, commit->parents->item->object.sha1); 351 hashcpy(old_rev_sha1, commit->parents->item->object.sha1);
340 else 352 else
341 hashclr(old_rev_sha1); 353 hashclr(old_rev_sha1);
342 354
343 if (!is_null_sha1(old_rev_sha1)) { 355 if (!is_null_sha1(old_rev_sha1)) {
344 type = sha1_object_info(old_rev_sha1, &size); 356 type = sha1_object_info(old_rev_sha1, &size);
345 if (type == OBJ_BAD) { 357 if (type == OBJ_BAD) {
346 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1))); 358 cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1)));
347 return; 359 return;
348 } 360 }
349 commit2 = lookup_commit_reference(old_rev_sha1); 361 commit2 = lookup_commit_reference(old_rev_sha1);
350 if (!commit2 || parse_commit(commit2)) 362 if (!commit2 || parse_commit(commit2))
351 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 363 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
352 } 364 }
353 365
354 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff)) 366 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
355 use_ssdiff = 1; 367 use_ssdiff = 1;
356 368
357 print_ssdiff_link(); 369 print_ssdiff_link();
358 cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix); 370 cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix);
359 371
360 if (use_ssdiff) { 372 if (use_ssdiff) {
361 html("<table summary='ssdiff' class='ssdiff'>"); 373 html("<table summary='ssdiff' class='ssdiff'>");
362 } else { 374 } else {
363 html("<table summary='diff' class='diff'>"); 375 html("<table summary='diff' class='diff'>");
364 html("<tr><td>"); 376 html("<tr><td>");
365 } 377 }
366 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix, 378 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix,
367 ctx.qry.ignorews); 379 ctx.qry.ignorews);
368 if (!use_ssdiff) 380 if (!use_ssdiff)
369 html("</td></tr>"); 381 html("</td></tr>");
370 html("</table>"); 382 html("</table>");
371} 383}
diff --git a/ui-diff.h b/ui-diff.h
index 70b2926..12d0c62 100644
--- a/ui-diff.h
+++ b/ui-diff.h
@@ -1,10 +1,16 @@
1#ifndef UI_DIFF_H 1#ifndef UI_DIFF_H
2#define UI_DIFF_H 2#define UI_DIFF_H
3 3
4extern void cgit_print_diffstat(const unsigned char *old_sha1, 4extern void cgit_print_diffstat(const unsigned char *old_sha1,
5 const unsigned char *new_sha1); 5 const unsigned char *new_sha1);
6 6
7extern void cgit_print_diff(const char *new_hex, const char *old_hex, 7extern void cgit_print_diff(const char *new_hex, const char *old_hex,
8 const char *prefix); 8 const char *prefix);
9 9
10extern struct diff_filespec *cgit_get_current_old_file(void);
11extern struct diff_filespec *cgit_get_current_new_file(void);
12
13extern unsigned char old_rev_sha1[20];
14extern unsigned char new_rev_sha1[20];
15
10#endif /* UI_DIFF_H */ 16#endif /* UI_DIFF_H */
diff --git a/ui-shared.c b/ui-shared.c
index ae29615..7efae7a 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -663,203 +663,213 @@ int print_archive_ref(const char *refname, const unsigned char *sha1,
663{ 663{
664 struct tag *tag; 664 struct tag *tag;
665 struct taginfo *info; 665 struct taginfo *info;
666 struct object *obj; 666 struct object *obj;
667 char buf[256], *url; 667 char buf[256], *url;
668 unsigned char fileid[20]; 668 unsigned char fileid[20];
669 int *header = (int *)cb_data; 669 int *header = (int *)cb_data;
670 670
671 if (prefixcmp(refname, "refs/archives")) 671 if (prefixcmp(refname, "refs/archives"))
672 return 0; 672 return 0;
673 strncpy(buf, refname+14, sizeof(buf)); 673 strncpy(buf, refname+14, sizeof(buf));
674 obj = parse_object(sha1); 674 obj = parse_object(sha1);
675 if (!obj) 675 if (!obj)
676 return 1; 676 return 1;
677 if (obj->type == OBJ_TAG) { 677 if (obj->type == OBJ_TAG) {
678 tag = lookup_tag(sha1); 678 tag = lookup_tag(sha1);
679 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 679 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
680 return 0; 680 return 0;
681 hashcpy(fileid, tag->tagged->sha1); 681 hashcpy(fileid, tag->tagged->sha1);
682 } else if (obj->type != OBJ_BLOB) { 682 } else if (obj->type != OBJ_BLOB) {
683 return 0; 683 return 0;
684 } else { 684 } else {
685 hashcpy(fileid, sha1); 685 hashcpy(fileid, sha1);
686 } 686 }
687 if (!*header) { 687 if (!*header) {
688 html("<h1>download</h1>\n"); 688 html("<h1>download</h1>\n");
689 *header = 1; 689 *header = 1;
690 } 690 }
691 url = cgit_pageurl(ctx.qry.repo, "blob", 691 url = cgit_pageurl(ctx.qry.repo, "blob",
692 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 692 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
693 buf)); 693 buf));
694 html_link_open(url, NULL, "menu"); 694 html_link_open(url, NULL, "menu");
695 html_txt(strlpart(buf, 20)); 695 html_txt(strlpart(buf, 20));
696 html_link_close(); 696 html_link_close();
697 return 0; 697 return 0;
698} 698}
699 699
700void cgit_add_hidden_formfields(int incl_head, int incl_search, 700void cgit_add_hidden_formfields(int incl_head, int incl_search,
701 const char *page) 701 const char *page)
702{ 702{
703 char *url; 703 char *url;
704 704
705 if (!ctx.cfg.virtual_root) { 705 if (!ctx.cfg.virtual_root) {
706 url = fmt("%s/%s", ctx.qry.repo, page); 706 url = fmt("%s/%s", ctx.qry.repo, page);
707 if (ctx.qry.vpath) 707 if (ctx.qry.vpath)
708 url = fmt("%s/%s", url, ctx.qry.vpath); 708 url = fmt("%s/%s", url, ctx.qry.vpath);
709 html_hidden("url", url); 709 html_hidden("url", url);
710 } 710 }
711 711
712 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 712 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
713 strcmp(ctx.qry.head, ctx.repo->defbranch)) 713 strcmp(ctx.qry.head, ctx.repo->defbranch))
714 html_hidden("h", ctx.qry.head); 714 html_hidden("h", ctx.qry.head);
715 715
716 if (ctx.qry.sha1) 716 if (ctx.qry.sha1)
717 html_hidden("id", ctx.qry.sha1); 717 html_hidden("id", ctx.qry.sha1);
718 if (ctx.qry.sha2) 718 if (ctx.qry.sha2)
719 html_hidden("id2", ctx.qry.sha2); 719 html_hidden("id2", ctx.qry.sha2);
720 if (ctx.qry.showmsg) 720 if (ctx.qry.showmsg)
721 html_hidden("showmsg", "1"); 721 html_hidden("showmsg", "1");
722 722
723 if (incl_search) { 723 if (incl_search) {
724 if (ctx.qry.grep) 724 if (ctx.qry.grep)
725 html_hidden("qt", ctx.qry.grep); 725 html_hidden("qt", ctx.qry.grep);
726 if (ctx.qry.search) 726 if (ctx.qry.search)
727 html_hidden("q", ctx.qry.search); 727 html_hidden("q", ctx.qry.search);
728 } 728 }
729} 729}
730 730
731static const char *hc(struct cgit_context *ctx, const char *page) 731static const char *hc(struct cgit_context *ctx, const char *page)
732{ 732{
733 return strcmp(ctx->qry.page, page) ? NULL : "active"; 733 return strcmp(ctx->qry.page, page) ? NULL : "active";
734} 734}
735 735
736static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path) 736static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path)
737{ 737{
738 char *old_path = ctx->qry.path; 738 char *old_path = ctx->qry.path;
739 char *p = path, *q, *end = path + strlen(path); 739 char *p = path, *q, *end = path + strlen(path);
740 740
741 ctx->qry.path = NULL; 741 ctx->qry.path = NULL;
742 cgit_self_link("root", NULL, NULL, ctx); 742 cgit_self_link("root", NULL, NULL, ctx);
743 ctx->qry.path = p = path; 743 ctx->qry.path = p = path;
744 while (p < end) { 744 while (p < end) {
745 if (!(q = strchr(p, '/'))) 745 if (!(q = strchr(p, '/')))
746 q = end; 746 q = end;
747 *q = '\0'; 747 *q = '\0';
748 html_txt("/"); 748 html_txt("/");
749 cgit_self_link(p, NULL, NULL, ctx); 749 cgit_self_link(p, NULL, NULL, ctx);
750 if (q < end) 750 if (q < end)
751 *q = '/'; 751 *q = '/';
752 p = q + 1; 752 p = q + 1;
753 } 753 }
754 ctx->qry.path = old_path; 754 ctx->qry.path = old_path;
755} 755}
756 756
757static void print_header(struct cgit_context *ctx) 757static void print_header(struct cgit_context *ctx)
758{ 758{
759 char *logo = NULL, *logo_link = NULL;
760
759 html("<table id='header'>\n"); 761 html("<table id='header'>\n");
760 html("<tr>\n"); 762 html("<tr>\n");
761 763
762 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) { 764 if (ctx->repo && ctx->repo->logo && *ctx->repo->logo)
765 logo = ctx->repo->logo;
766 else
767 logo = ctx->cfg.logo;
768 if (ctx->repo && ctx->repo->logo_link && *ctx->repo->logo_link)
769 logo_link = ctx->repo->logo_link;
770 else
771 logo_link = ctx->cfg.logo_link;
772 if (logo && *logo) {
763 html("<td class='logo' rowspan='2'><a href='"); 773 html("<td class='logo' rowspan='2'><a href='");
764 if (ctx->cfg.logo_link) 774 if (logo_link && *logo_link)
765 html_attr(ctx->cfg.logo_link); 775 html_attr(logo_link);
766 else 776 else
767 html_attr(cgit_rooturl()); 777 html_attr(cgit_rooturl());
768 html("'><img src='"); 778 html("'><img src='");
769 html_attr(ctx->cfg.logo); 779 html_attr(logo);
770 html("' alt='cgit logo'/></a></td>\n"); 780 html("' alt='cgit logo'/></a></td>\n");
771 } 781 }
772 782
773 html("<td class='main'>"); 783 html("<td class='main'>");
774 if (ctx->repo) { 784 if (ctx->repo) {
775 cgit_index_link("index", NULL, NULL, NULL, 0); 785 cgit_index_link("index", NULL, NULL, NULL, 0);
776 html(" : "); 786 html(" : ");
777 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 787 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
778 html("</td><td class='form'>"); 788 html("</td><td class='form'>");
779 html("<form method='get' action=''>\n"); 789 html("<form method='get' action=''>\n");
780 cgit_add_hidden_formfields(0, 1, ctx->qry.page); 790 cgit_add_hidden_formfields(0, 1, ctx->qry.page);
781 html("<select name='h' onchange='this.form.submit();'>\n"); 791 html("<select name='h' onchange='this.form.submit();'>\n");
782 for_each_branch_ref(print_branch_option, ctx->qry.head); 792 for_each_branch_ref(print_branch_option, ctx->qry.head);
783 html("</select> "); 793 html("</select> ");
784 html("<input type='submit' name='' value='switch'/>"); 794 html("<input type='submit' name='' value='switch'/>");
785 html("</form>"); 795 html("</form>");
786 } else 796 } else
787 html_txt(ctx->cfg.root_title); 797 html_txt(ctx->cfg.root_title);
788 html("</td></tr>\n"); 798 html("</td></tr>\n");
789 799
790 html("<tr><td class='sub'>"); 800 html("<tr><td class='sub'>");
791 if (ctx->repo) { 801 if (ctx->repo) {
792 html_txt(ctx->repo->desc); 802 html_txt(ctx->repo->desc);
793 html("</td><td class='sub right'>"); 803 html("</td><td class='sub right'>");
794 html_txt(ctx->repo->owner); 804 html_txt(ctx->repo->owner);
795 } else { 805 } else {
796 if (ctx->cfg.root_desc) 806 if (ctx->cfg.root_desc)
797 html_txt(ctx->cfg.root_desc); 807 html_txt(ctx->cfg.root_desc);
798 else if (ctx->cfg.index_info) 808 else if (ctx->cfg.index_info)
799 html_include(ctx->cfg.index_info); 809 html_include(ctx->cfg.index_info);
800 } 810 }
801 html("</td></tr></table>\n"); 811 html("</td></tr></table>\n");
802} 812}
803 813
804void cgit_print_pageheader(struct cgit_context *ctx) 814void cgit_print_pageheader(struct cgit_context *ctx)
805{ 815{
806 html("<div id='cgit'>"); 816 html("<div id='cgit'>");
807 if (!ctx->cfg.noheader) 817 if (!ctx->cfg.noheader)
808 print_header(ctx); 818 print_header(ctx);
809 819
810 html("<table class='tabs'><tr><td>\n"); 820 html("<table class='tabs'><tr><td>\n");
811 if (ctx->repo) { 821 if (ctx->repo) {
812 cgit_summary_link("summary", NULL, hc(ctx, "summary"), 822 cgit_summary_link("summary", NULL, hc(ctx, "summary"),
813 ctx->qry.head); 823 ctx->qry.head);
814 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head, 824 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head,
815 ctx->qry.sha1, NULL); 825 ctx->qry.sha1, NULL);
816 cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head, 826 cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head,
817 NULL, ctx->qry.vpath, 0, NULL, NULL, 827 NULL, ctx->qry.vpath, 0, NULL, NULL,
818 ctx->qry.showmsg); 828 ctx->qry.showmsg);
819 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head, 829 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head,
820 ctx->qry.sha1, ctx->qry.vpath); 830 ctx->qry.sha1, ctx->qry.vpath);
821 cgit_commit_link("commit", NULL, hc(ctx, "commit"), 831 cgit_commit_link("commit", NULL, hc(ctx, "commit"),
822 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0); 832 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0);
823 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head, 833 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head,
824 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0); 834 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0);
825 if (ctx->repo->max_stats) 835 if (ctx->repo->max_stats)
826 cgit_stats_link("stats", NULL, hc(ctx, "stats"), 836 cgit_stats_link("stats", NULL, hc(ctx, "stats"),
827 ctx->qry.head, ctx->qry.vpath); 837 ctx->qry.head, ctx->qry.vpath);
828 if (ctx->repo->readme) 838 if (ctx->repo->readme)
829 reporevlink("about", "about", NULL, 839 reporevlink("about", "about", NULL,
830 hc(ctx, "about"), ctx->qry.head, NULL, 840 hc(ctx, "about"), ctx->qry.head, NULL,
831 NULL); 841 NULL);
832 html("</td><td class='form'>"); 842 html("</td><td class='form'>");
833 html("<form class='right' method='get' action='"); 843 html("<form class='right' method='get' action='");
834 if (ctx->cfg.virtual_root) 844 if (ctx->cfg.virtual_root)
835 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 845 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
836 ctx->qry.vpath, NULL)); 846 ctx->qry.vpath, NULL));
837 html("'>\n"); 847 html("'>\n");
838 cgit_add_hidden_formfields(1, 0, "log"); 848 cgit_add_hidden_formfields(1, 0, "log");
839 html("<select name='qt'>\n"); 849 html("<select name='qt'>\n");
840 html_option("grep", "log msg", ctx->qry.grep); 850 html_option("grep", "log msg", ctx->qry.grep);
841 html_option("author", "author", ctx->qry.grep); 851 html_option("author", "author", ctx->qry.grep);
842 html_option("committer", "committer", ctx->qry.grep); 852 html_option("committer", "committer", ctx->qry.grep);
843 html_option("range", "range", ctx->qry.grep); 853 html_option("range", "range", ctx->qry.grep);
844 html("</select>\n"); 854 html("</select>\n");
845 html("<input class='txt' type='text' size='10' name='q' value='"); 855 html("<input class='txt' type='text' size='10' name='q' value='");
846 html_attr(ctx->qry.search); 856 html_attr(ctx->qry.search);
847 html("'/>\n"); 857 html("'/>\n");
848 html("<input type='submit' value='search'/>\n"); 858 html("<input type='submit' value='search'/>\n");
849 html("</form>\n"); 859 html("</form>\n");
850 } else { 860 } else {
851 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0); 861 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0);
852 if (ctx->cfg.root_readme) 862 if (ctx->cfg.root_readme)
853 site_link("about", "about", NULL, hc(ctx, "about"), 863 site_link("about", "about", NULL, hc(ctx, "about"),
854 NULL, 0); 864 NULL, 0);
855 html("</td><td class='form'>"); 865 html("</td><td class='form'>");
856 html("<form method='get' action='"); 866 html("<form method='get' action='");
857 html_attr(cgit_rooturl()); 867 html_attr(cgit_rooturl());
858 html("'>\n"); 868 html("'>\n");
859 html("<input type='text' name='q' size='10' value='"); 869 html("<input type='text' name='q' size='10' value='");
860 html_attr(ctx->qry.search); 870 html_attr(ctx->qry.search);
861 html("'/>\n"); 871 html("'/>\n");
862 html("<input type='submit' value='search'/>\n"); 872 html("<input type='submit' value='search'/>\n");
863 html("</form>"); 873 html("</form>");
864 } 874 }
865 html("</td></tr></table>\n"); 875 html("</td></tr></table>\n");
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
index 408e620..2481585 100644
--- a/ui-ssdiff.c
+++ b/ui-ssdiff.c
@@ -1,315 +1,329 @@
1#include "cgit.h" 1#include "cgit.h"
2#include "html.h" 2#include "html.h"
3#include "ui-shared.h" 3#include "ui-shared.h"
4#include "ui-diff.h"
4 5
5extern int use_ssdiff; 6extern int use_ssdiff;
6 7
7static int current_old_line, current_new_line; 8static int current_old_line, current_new_line;
8 9
9struct deferred_lines { 10struct deferred_lines {
10 int line_no; 11 int line_no;
11 char *line; 12 char *line;
12 struct deferred_lines *next; 13 struct deferred_lines *next;
13}; 14};
14 15
15static struct deferred_lines *deferred_old, *deferred_old_last; 16static struct deferred_lines *deferred_old, *deferred_old_last;
16static struct deferred_lines *deferred_new, *deferred_new_last; 17static struct deferred_lines *deferred_new, *deferred_new_last;
17 18
18static char *longest_common_subsequence(char *A, char *B) 19static char *longest_common_subsequence(char *A, char *B)
19{ 20{
20 int i, j, ri; 21 int i, j, ri;
21 int m = strlen(A); 22 int m = strlen(A);
22 int n = strlen(B); 23 int n = strlen(B);
23 int L[m + 1][n + 1]; 24 int L[m + 1][n + 1];
24 int tmp1, tmp2; 25 int tmp1, tmp2;
25 int lcs_length; 26 int lcs_length;
26 char *result; 27 char *result;
27 28
28 for (i = m; i >= 0; i--) { 29 for (i = m; i >= 0; i--) {
29 for (j = n; j >= 0; j--) { 30 for (j = n; j >= 0; j--) {
30 if (A[i] == '\0' || B[j] == '\0') { 31 if (A[i] == '\0' || B[j] == '\0') {
31 L[i][j] = 0; 32 L[i][j] = 0;
32 } else if (A[i] == B[j]) { 33 } else if (A[i] == B[j]) {
33 L[i][j] = 1 + L[i + 1][j + 1]; 34 L[i][j] = 1 + L[i + 1][j + 1];
34 } else { 35 } else {
35 tmp1 = L[i + 1][j]; 36 tmp1 = L[i + 1][j];
36 tmp2 = L[i][j + 1]; 37 tmp2 = L[i][j + 1];
37 L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2); 38 L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2);
38 } 39 }
39 } 40 }
40 } 41 }
41 42
42 lcs_length = L[0][0]; 43 lcs_length = L[0][0];
43 result = xmalloc(lcs_length + 2); 44 result = xmalloc(lcs_length + 2);
44 memset(result, 0, sizeof(*result) * (lcs_length + 2)); 45 memset(result, 0, sizeof(*result) * (lcs_length + 2));
45 46
46 ri = 0; 47 ri = 0;
47 i = 0; 48 i = 0;
48 j = 0; 49 j = 0;
49 while (i < m && j < n) { 50 while (i < m && j < n) {
50 if (A[i] == B[j]) { 51 if (A[i] == B[j]) {
51 result[ri] = A[i]; 52 result[ri] = A[i];
52 ri += 1; 53 ri += 1;
53 i += 1; 54 i += 1;
54 j += 1; 55 j += 1;
55 } else if (L[i + 1][j] >= L[i][j + 1]) { 56 } else if (L[i + 1][j] >= L[i][j + 1]) {
56 i += 1; 57 i += 1;
57 } else { 58 } else {
58 j += 1; 59 j += 1;
59 } 60 }
60 } 61 }
61 return result; 62 return result;
62} 63}
63 64
64static int line_from_hunk(char *line, char type) 65static int line_from_hunk(char *line, char type)
65{ 66{
66 char *buf1, *buf2; 67 char *buf1, *buf2;
67 int len; 68 int len;
68 69
69 buf1 = strchr(line, type); 70 buf1 = strchr(line, type);
70 if (buf1 == NULL) 71 if (buf1 == NULL)
71 return 0; 72 return 0;
72 buf1 += 1; 73 buf1 += 1;
73 buf2 = strchr(buf1, ','); 74 buf2 = strchr(buf1, ',');
74 if (buf2 == NULL) 75 if (buf2 == NULL)
75 return 0; 76 return 0;
76 len = buf2 - buf1; 77 len = buf2 - buf1;
77 buf2 = xmalloc(len + 1); 78 buf2 = xmalloc(len + 1);
78 strncpy(buf2, buf1, len); 79 strncpy(buf2, buf1, len);
79 buf2[len] = '\0'; 80 buf2[len] = '\0';
80 int res = atoi(buf2); 81 int res = atoi(buf2);
81 free(buf2); 82 free(buf2);
82 return res; 83 return res;
83} 84}
84 85
85static char *replace_tabs(char *line) 86static char *replace_tabs(char *line)
86{ 87{
87 char *prev_buf = line; 88 char *prev_buf = line;
88 char *cur_buf; 89 char *cur_buf;
89 int linelen = strlen(line); 90 int linelen = strlen(line);
90 int n_tabs = 0; 91 int n_tabs = 0;
91 int i; 92 int i;
92 char *result; 93 char *result;
93 char *spaces = " "; 94 char *spaces = " ";
94 95
95 if (linelen == 0) { 96 if (linelen == 0) {
96 result = xmalloc(1); 97 result = xmalloc(1);
97 result[0] = '\0'; 98 result[0] = '\0';
98 return result; 99 return result;
99 } 100 }
100 101
101 for (i = 0; i < linelen; i++) 102 for (i = 0; i < linelen; i++)
102 if (line[i] == '\t') 103 if (line[i] == '\t')
103 n_tabs += 1; 104 n_tabs += 1;
104 result = xmalloc(linelen + n_tabs * 8 + 1); 105 result = xmalloc(linelen + n_tabs * 8 + 1);
105 result[0] = '\0'; 106 result[0] = '\0';
106 107
107 while (1) { 108 while (1) {
108 cur_buf = strchr(prev_buf, '\t'); 109 cur_buf = strchr(prev_buf, '\t');
109 if (!cur_buf) { 110 if (!cur_buf) {
110 strcat(result, prev_buf); 111 strcat(result, prev_buf);
111 break; 112 break;
112 } else { 113 } else {
113 strcat(result, " "); 114 strcat(result, " ");
114 strncat(result, spaces, 8 - (strlen(result) % 8)); 115 strncat(result, spaces, 8 - (strlen(result) % 8));
115 strncat(result, prev_buf, cur_buf - prev_buf); 116 strncat(result, prev_buf, cur_buf - prev_buf);
116 } 117 }
117 prev_buf = cur_buf + 1; 118 prev_buf = cur_buf + 1;
118 } 119 }
119 return result; 120 return result;
120} 121}
121 122
122static int calc_deferred_lines(struct deferred_lines *start) 123static int calc_deferred_lines(struct deferred_lines *start)
123{ 124{
124 struct deferred_lines *item = start; 125 struct deferred_lines *item = start;
125 int result = 0; 126 int result = 0;
126 while (item) { 127 while (item) {
127 result += 1; 128 result += 1;
128 item = item->next; 129 item = item->next;
129 } 130 }
130 return result; 131 return result;
131} 132}
132 133
133static void deferred_old_add(char *line, int line_no) 134static void deferred_old_add(char *line, int line_no)
134{ 135{
135 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines)); 136 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
136 item->line = xstrdup(line); 137 item->line = xstrdup(line);
137 item->line_no = line_no; 138 item->line_no = line_no;
138 item->next = NULL; 139 item->next = NULL;
139 if (deferred_old) { 140 if (deferred_old) {
140 deferred_old_last->next = item; 141 deferred_old_last->next = item;
141 deferred_old_last = item; 142 deferred_old_last = item;
142 } else { 143 } else {
143 deferred_old = deferred_old_last = item; 144 deferred_old = deferred_old_last = item;
144 } 145 }
145} 146}
146 147
147static void deferred_new_add(char *line, int line_no) 148static void deferred_new_add(char *line, int line_no)
148{ 149{
149 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines)); 150 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
150 item->line = xstrdup(line); 151 item->line = xstrdup(line);
151 item->line_no = line_no; 152 item->line_no = line_no;
152 item->next = NULL; 153 item->next = NULL;
153 if (deferred_new) { 154 if (deferred_new) {
154 deferred_new_last->next = item; 155 deferred_new_last->next = item;
155 deferred_new_last = item; 156 deferred_new_last = item;
156 } else { 157 } else {
157 deferred_new = deferred_new_last = item; 158 deferred_new = deferred_new_last = item;
158 } 159 }
159} 160}
160 161
161static void print_part_with_lcs(char *class, char *line, char *lcs) 162static void print_part_with_lcs(char *class, char *line, char *lcs)
162{ 163{
163 int line_len = strlen(line); 164 int line_len = strlen(line);
164 int i, j; 165 int i, j;
165 char c[2] = " "; 166 char c[2] = " ";
166 int same = 1; 167 int same = 1;
167 168
168 j = 0; 169 j = 0;
169 for (i = 0; i < line_len; i++) { 170 for (i = 0; i < line_len; i++) {
170 c[0] = line[i]; 171 c[0] = line[i];
171 if (same) { 172 if (same) {
172 if (line[i] == lcs[j]) 173 if (line[i] == lcs[j])
173 j += 1; 174 j += 1;
174 else { 175 else {
175 same = 0; 176 same = 0;
176 htmlf("<span class='%s'>", class); 177 htmlf("<span class='%s'>", class);
177 } 178 }
178 } else if (line[i] == lcs[j]) { 179 } else if (line[i] == lcs[j]) {
179 same = 1; 180 same = 1;
180 htmlf("</span>"); 181 htmlf("</span>");
181 j += 1; 182 j += 1;
182 } 183 }
183 html_txt(c); 184 html_txt(c);
184 } 185 }
185} 186}
186 187
187static void print_ssdiff_line(char *class, 188static void print_ssdiff_line(char *class,
188 int old_line_no, 189 int old_line_no,
189 char *old_line, 190 char *old_line,
190 int new_line_no, 191 int new_line_no,
191 char *new_line, int individual_chars) 192 char *new_line, int individual_chars)
192{ 193{
193 char *lcs = NULL; 194 char *lcs = NULL;
195
194 if (old_line) 196 if (old_line)
195 old_line = replace_tabs(old_line + 1); 197 old_line = replace_tabs(old_line + 1);
196 if (new_line) 198 if (new_line)
197 new_line = replace_tabs(new_line + 1); 199 new_line = replace_tabs(new_line + 1);
198 if (individual_chars && old_line && new_line) 200 if (individual_chars && old_line && new_line)
199 lcs = longest_common_subsequence(old_line, new_line); 201 lcs = longest_common_subsequence(old_line, new_line);
200 html("<tr>"); 202 html("<tr>\n");
201 if (old_line_no > 0) 203 if (old_line_no > 0) {
202 htmlf("<td class='lineno'>%d</td><td class='%s'>", 204 struct diff_filespec *old_file = cgit_get_current_old_file();
203 old_line_no, class); 205 char *lineno_str = fmt("n%d", old_line_no);
204 else if (old_line) 206 char *id_str = fmt("%s#%s", is_null_sha1(old_file->sha1)?"HEAD":sha1_to_hex(old_rev_sha1), lineno_str);
207 html("<td class='lineno'><a class='no' href='");
208 html(cgit_fileurl(ctx.repo->url, "tree", old_file->path, id_str));
209 htmlf("' id='%s' name='%s'>%s</a>", lineno_str, lineno_str, lineno_str + 1);
210 html("</td>");
211 htmlf("<td class='%s'>", class);
212 } else if (old_line)
205 htmlf("<td class='lineno'></td><td class='%s'>", class); 213 htmlf("<td class='lineno'></td><td class='%s'>", class);
206 else 214 else
207 htmlf("<td class='lineno'></td><td class='%s_dark'>", class); 215 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
208 if (old_line) { 216 if (old_line) {
209 if (lcs) 217 if (lcs)
210 print_part_with_lcs("del", old_line, lcs); 218 print_part_with_lcs("del", old_line, lcs);
211 else 219 else
212 html_txt(old_line); 220 html_txt(old_line);
213 } 221 }
214 222
215 html("</td>"); 223 html("</td>\n");
216 if (new_line_no > 0) 224 if (new_line_no > 0) {
217 htmlf("<td class='lineno'>%d</td><td class='%s'>", 225 struct diff_filespec *new_file = cgit_get_current_new_file();
218 new_line_no, class); 226 char *lineno_str = fmt("n%d", new_line_no);
219 else if (new_line) 227 char *id_str = fmt("%s#%s", is_null_sha1(new_file->sha1)?"HEAD":sha1_to_hex(new_rev_sha1), lineno_str);
228 html("<td class='lineno'><a class='no' href='");
229 html(cgit_fileurl(ctx.repo->url, "tree", new_file->path, id_str));
230 htmlf("' id='%s' name='%s'>%s</a>", lineno_str, lineno_str, lineno_str + 1);
231 html("</td>");
232 htmlf("<td class='%s'>", class);
233 } else if (new_line)
220 htmlf("<td class='lineno'></td><td class='%s'>", class); 234 htmlf("<td class='lineno'></td><td class='%s'>", class);
221 else 235 else
222 htmlf("<td class='lineno'></td><td class='%s_dark'>", class); 236 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
223 if (new_line) { 237 if (new_line) {
224 if (lcs) 238 if (lcs)
225 print_part_with_lcs("add", new_line, lcs); 239 print_part_with_lcs("add", new_line, lcs);
226 else 240 else
227 html_txt(new_line); 241 html_txt(new_line);
228 } 242 }
229 243
230 html("</td></tr>"); 244 html("</td></tr>");
231 if (lcs) 245 if (lcs)
232 free(lcs); 246 free(lcs);
233 if (new_line) 247 if (new_line)
234 free(new_line); 248 free(new_line);
235 if (old_line) 249 if (old_line)
236 free(old_line); 250 free(old_line);
237} 251}
238 252
239static void print_deferred_old_lines() 253static void print_deferred_old_lines()
240{ 254{
241 struct deferred_lines *iter_old, *tmp; 255 struct deferred_lines *iter_old, *tmp;
242 iter_old = deferred_old; 256 iter_old = deferred_old;
243 while (iter_old) { 257 while (iter_old) {
244 print_ssdiff_line("del", iter_old->line_no, 258 print_ssdiff_line("del", iter_old->line_no,
245 iter_old->line, -1, NULL, 0); 259 iter_old->line, -1, NULL, 0);
246 tmp = iter_old->next; 260 tmp = iter_old->next;
247 free(iter_old); 261 free(iter_old);
248 iter_old = tmp; 262 iter_old = tmp;
249 } 263 }
250} 264}
251 265
252static void print_deferred_new_lines() 266static void print_deferred_new_lines()
253{ 267{
254 struct deferred_lines *iter_new, *tmp; 268 struct deferred_lines *iter_new, *tmp;
255 iter_new = deferred_new; 269 iter_new = deferred_new;
256 while (iter_new) { 270 while (iter_new) {
257 print_ssdiff_line("add", -1, NULL, 271 print_ssdiff_line("add", -1, NULL,
258 iter_new->line_no, iter_new->line, 0); 272 iter_new->line_no, iter_new->line, 0);
259 tmp = iter_new->next; 273 tmp = iter_new->next;
260 free(iter_new); 274 free(iter_new);
261 iter_new = tmp; 275 iter_new = tmp;
262 } 276 }
263} 277}
264 278
265static void print_deferred_changed_lines() 279static void print_deferred_changed_lines()
266{ 280{
267 struct deferred_lines *iter_old, *iter_new, *tmp; 281 struct deferred_lines *iter_old, *iter_new, *tmp;
268 int n_old_lines = calc_deferred_lines(deferred_old); 282 int n_old_lines = calc_deferred_lines(deferred_old);
269 int n_new_lines = calc_deferred_lines(deferred_new); 283 int n_new_lines = calc_deferred_lines(deferred_new);
270 int individual_chars = (n_old_lines == n_new_lines ? 1 : 0); 284 int individual_chars = (n_old_lines == n_new_lines ? 1 : 0);
271 285
272 iter_old = deferred_old; 286 iter_old = deferred_old;
273 iter_new = deferred_new; 287 iter_new = deferred_new;
274 while (iter_old || iter_new) { 288 while (iter_old || iter_new) {
275 if (iter_old && iter_new) 289 if (iter_old && iter_new)
276 print_ssdiff_line("changed", iter_old->line_no, 290 print_ssdiff_line("changed", iter_old->line_no,
277 iter_old->line, 291 iter_old->line,
278 iter_new->line_no, iter_new->line, 292 iter_new->line_no, iter_new->line,
279 individual_chars); 293 individual_chars);
280 else if (iter_old) 294 else if (iter_old)
281 print_ssdiff_line("changed", iter_old->line_no, 295 print_ssdiff_line("changed", iter_old->line_no,
282 iter_old->line, -1, NULL, 0); 296 iter_old->line, -1, NULL, 0);
283 else if (iter_new) 297 else if (iter_new)
284 print_ssdiff_line("changed", -1, NULL, 298 print_ssdiff_line("changed", -1, NULL,
285 iter_new->line_no, iter_new->line, 0); 299 iter_new->line_no, iter_new->line, 0);
286 if (iter_old) { 300 if (iter_old) {
287 tmp = iter_old->next; 301 tmp = iter_old->next;
288 free(iter_old); 302 free(iter_old);
289 iter_old = tmp; 303 iter_old = tmp;
290 } 304 }
291 305
292 if (iter_new) { 306 if (iter_new) {
293 tmp = iter_new->next; 307 tmp = iter_new->next;
294 free(iter_new); 308 free(iter_new);
295 iter_new = tmp; 309 iter_new = tmp;
296 } 310 }
297 } 311 }
298} 312}
299 313
300void cgit_ssdiff_print_deferred_lines() 314void cgit_ssdiff_print_deferred_lines()
301{ 315{
302 if (!deferred_old && !deferred_new) 316 if (!deferred_old && !deferred_new)
303 return; 317 return;
304 if (deferred_old && !deferred_new) 318 if (deferred_old && !deferred_new)
305 print_deferred_old_lines(); 319 print_deferred_old_lines();
306 else if (!deferred_old && deferred_new) 320 else if (!deferred_old && deferred_new)
307 print_deferred_new_lines(); 321 print_deferred_new_lines();
308 else 322 else
309 print_deferred_changed_lines(); 323 print_deferred_changed_lines();
310 deferred_old = deferred_old_last = NULL; 324 deferred_old = deferred_old_last = NULL;
311 deferred_new = deferred_new_last = NULL; 325 deferred_new = deferred_new_last = NULL;
312} 326}
313 327
314/* 328/*
315 * print a single line returned from xdiff 329 * print a single line returned from xdiff