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
@@ -12,131 +12,135 @@
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"))
diff --git a/cgit.css b/cgit.css
index 008cff8..1d90057 100644
--- a/cgit.css
+++ b/cgit.css
@@ -232,129 +232,129 @@ td#content {
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;
diff --git a/cgit.h b/cgit.h
index 74aa340..b5f00fc 100644
--- a/cgit.h
+++ b/cgit.h
@@ -10,128 +10,130 @@
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;
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
@@ -326,128 +326,137 @@ summary-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
diff --git a/ui-diff.c b/ui-diff.c
index 7ff7e46..a53425d 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -1,100 +1,111 @@
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'>");
@@ -223,128 +234,129 @@ static void header(unsigned char *sha1, char *path1, int mode1,
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))
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
@@ -695,139 +695,149 @@ int print_archive_ref(const char *refname, const unsigned char *sha1,
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='");
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
index 408e620..2481585 100644
--- a/ui-ssdiff.c
+++ b/ui-ssdiff.c
@@ -1,67 +1,68 @@
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;
@@ -130,154 +131,167 @@ static int calc_deferred_lines(struct deferred_lines *start)
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)