summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--Makefile18
-rw-r--r--cgit.c14
-rw-r--r--cgit.css104
-rw-r--r--cgit.h5
-rw-r--r--cgitrc.5.txt17
-rwxr-xr-xfilters/syntax-highlighting.sh29
m---------git0
-rw-r--r--html.c70
-rw-r--r--html.h18
-rw-r--r--shared.c1
-rw-r--r--ui-commit.c11
-rw-r--r--ui-diff.c62
-rw-r--r--ui-log.c4
-rw-r--r--ui-refs.c4
-rw-r--r--ui-shared.c43
-rw-r--r--ui-shared.h5
-rw-r--r--ui-snapshot.c14
-rw-r--r--ui-ssdiff.c369
-rw-r--r--ui-ssdiff.h13
-rw-r--r--ui-tag.c24
-rw-r--r--ui-tree.c8
21 files changed, 745 insertions, 88 deletions
diff --git a/Makefile b/Makefile
index 7b9ac5f..0a5055b 100644
--- a/Makefile
+++ b/Makefile
@@ -2,18 +2,21 @@ CGIT_VERSION = v0.8.3.1
2CGIT_SCRIPT_NAME = cgit.cgi 2CGIT_SCRIPT_NAME = cgit.cgi
3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit 3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH) 4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
5CGIT_CONFIG = /etc/cgitrc 5CGIT_CONFIG = /etc/cgitrc
6CACHE_ROOT = /var/cache/cgit 6CACHE_ROOT = /var/cache/cgit
7SHA1_HEADER = <openssl/sha.h> 7SHA1_HEADER = <openssl/sha.h>
8GIT_VER = 1.6.4.3 8GIT_VER = 1.7.0
9GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 9GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
10INSTALL = install 10INSTALL = install
11 11
12# Define NO_STRCASESTR if you don't have strcasestr. 12# Define NO_STRCASESTR if you don't have strcasestr.
13# 13#
14# Define NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1
15# implementation (slower).
16#
14# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin). 17# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
15# 18#
16 19
17#-include config.mak 20#-include config.mak
18 21
19# 22#
@@ -65,13 +68,13 @@ endif
65# Define a pattern rule for silent object building 68# Define a pattern rule for silent object building
66# 69#
67%.o: %.c 70%.o: %.c
68 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< 71 $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $<
69 72
70 73
71EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto 74EXTLIBS = git/libgit.a git/xdiff/lib.a -lz
72OBJECTS = 75OBJECTS =
73OBJECTS += cache.o 76OBJECTS += cache.o
74OBJECTS += cgit.o 77OBJECTS += cgit.o
75OBJECTS += cmd.o 78OBJECTS += cmd.o
76OBJECTS += configfile.o 79OBJECTS += configfile.o
77OBJECTS += html.o 80OBJECTS += html.o
@@ -87,12 +90,13 @@ OBJECTS += ui-log.o
87OBJECTS += ui-patch.o 90OBJECTS += ui-patch.o
88OBJECTS += ui-plain.o 91OBJECTS += ui-plain.o
89OBJECTS += ui-refs.o 92OBJECTS += ui-refs.o
90OBJECTS += ui-repolist.o 93OBJECTS += ui-repolist.o
91OBJECTS += ui-shared.o 94OBJECTS += ui-shared.o
92OBJECTS += ui-snapshot.o 95OBJECTS += ui-snapshot.o
96OBJECTS += ui-ssdiff.o
93OBJECTS += ui-stats.o 97OBJECTS += ui-stats.o
94OBJECTS += ui-summary.o 98OBJECTS += ui-summary.o
95OBJECTS += ui-tag.o 99OBJECTS += ui-tag.o
96OBJECTS += ui-tree.o 100OBJECTS += ui-tree.o
97 101
98ifdef NEEDS_LIBICONV 102ifdef NEEDS_LIBICONV
@@ -120,23 +124,29 @@ CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
120ifdef NO_ICONV 124ifdef NO_ICONV
121 CFLAGS += -DNO_ICONV 125 CFLAGS += -DNO_ICONV
122endif 126endif
123ifdef NO_STRCASESTR 127ifdef NO_STRCASESTR
124 CFLAGS += -DNO_STRCASESTR 128 CFLAGS += -DNO_STRCASESTR
125endif 129endif
130ifdef NO_OPENSSL
131 CFLAGS += -DNO_OPENSSL
132 GIT_OPTIONS += NO_OPENSSL=1
133else
134 EXTLIBS += -lcrypto
135endif
126 136
127cgit: $(OBJECTS) libgit 137cgit: $(OBJECTS) libgit
128 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 138 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
129 139
130cgit.o: VERSION 140cgit.o: VERSION
131 141
132-include $(OBJECTS:.o=.d) 142-include $(OBJECTS:.o=.d)
133 143
134libgit: 144libgit:
135 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a 145 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) libgit.a
136 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a 146 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) xdiff/lib.a
137 147
138test: all 148test: all
139 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 149 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
140 150
141install: all 151install: all
142 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH) 152 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
diff --git a/cgit.c b/cgit.c
index 6c7e811..9305d0a 100644
--- a/cgit.c
+++ b/cgit.c
@@ -57,12 +57,14 @@ void repo_config(struct cgit_repo *repo, const char *name, const char *value)
57 else if (!strcmp(name, "snapshots")) 57 else if (!strcmp(name, "snapshots"))
58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
59 else if (!strcmp(name, "enable-log-filecount")) 59 else if (!strcmp(name, "enable-log-filecount"))
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount")) 61 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
63 else if (!strcmp(name, "enable-remote-branches"))
64 repo->enable_remote_branches = atoi(value);
63 else if (!strcmp(name, "max-stats")) 65 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL); 66 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link")) 67 else if (!strcmp(name, "module-link"))
66 repo->module_link= xstrdup(value); 68 repo->module_link= xstrdup(value);
67 else if (!strcmp(name, "section")) 69 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value); 70 repo->section = xstrdup(value);
@@ -134,12 +136,14 @@ void config_cb(const char *name, const char *value)
134 else if (!strcmp(name, "enable-index-links")) 136 else if (!strcmp(name, "enable-index-links"))
135 ctx.cfg.enable_index_links = atoi(value); 137 ctx.cfg.enable_index_links = atoi(value);
136 else if (!strcmp(name, "enable-log-filecount")) 138 else if (!strcmp(name, "enable-log-filecount"))
137 ctx.cfg.enable_log_filecount = atoi(value); 139 ctx.cfg.enable_log_filecount = atoi(value);
138 else if (!strcmp(name, "enable-log-linecount")) 140 else if (!strcmp(name, "enable-log-linecount"))
139 ctx.cfg.enable_log_linecount = atoi(value); 141 ctx.cfg.enable_log_linecount = atoi(value);
142 else if (!strcmp(name, "enable-remote-branches"))
143 ctx.cfg.enable_remote_branches = atoi(value);
140 else if (!strcmp(name, "enable-tree-linenumbers")) 144 else if (!strcmp(name, "enable-tree-linenumbers"))
141 ctx.cfg.enable_tree_linenumbers = atoi(value); 145 ctx.cfg.enable_tree_linenumbers = atoi(value);
142 else if (!strcmp(name, "max-stats")) 146 else if (!strcmp(name, "max-stats"))
143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 147 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
144 else if (!strcmp(name, "cache-size")) 148 else if (!strcmp(name, "cache-size"))
145 ctx.cfg.cache_size = atoi(value); 149 ctx.cfg.cache_size = atoi(value);
@@ -162,12 +166,14 @@ void config_cb(const char *name, const char *value)
162 else if (!strcmp(name, "embedded")) 166 else if (!strcmp(name, "embedded"))
163 ctx.cfg.embedded = atoi(value); 167 ctx.cfg.embedded = atoi(value);
164 else if (!strcmp(name, "max-message-length")) 168 else if (!strcmp(name, "max-message-length"))
165 ctx.cfg.max_msg_len = atoi(value); 169 ctx.cfg.max_msg_len = atoi(value);
166 else if (!strcmp(name, "max-repodesc-length")) 170 else if (!strcmp(name, "max-repodesc-length"))
167 ctx.cfg.max_repodesc_len = atoi(value); 171 ctx.cfg.max_repodesc_len = atoi(value);
172 else if (!strcmp(name, "max-blob-size"))
173 ctx.cfg.max_blob_size = atoi(value);
168 else if (!strcmp(name, "max-repo-count")) 174 else if (!strcmp(name, "max-repo-count"))
169 ctx.cfg.max_repo_count = atoi(value); 175 ctx.cfg.max_repo_count = atoi(value);
170 else if (!strcmp(name, "max-commit-count")) 176 else if (!strcmp(name, "max-commit-count"))
171 ctx.cfg.max_commit_count = atoi(value); 177 ctx.cfg.max_commit_count = atoi(value);
172 else if (!strcmp(name, "scan-path")) 178 else if (!strcmp(name, "scan-path"))
173 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 179 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
@@ -179,12 +185,14 @@ void config_cb(const char *name, const char *value)
179 else if (!strcmp(name, "summary-log")) 185 else if (!strcmp(name, "summary-log"))
180 ctx.cfg.summary_log = atoi(value); 186 ctx.cfg.summary_log = atoi(value);
181 else if (!strcmp(name, "summary-branches")) 187 else if (!strcmp(name, "summary-branches"))
182 ctx.cfg.summary_branches = atoi(value); 188 ctx.cfg.summary_branches = atoi(value);
183 else if (!strcmp(name, "summary-tags")) 189 else if (!strcmp(name, "summary-tags"))
184 ctx.cfg.summary_tags = atoi(value); 190 ctx.cfg.summary_tags = atoi(value);
191 else if (!strcmp(name, "side-by-side-diffs"))
192 ctx.cfg.ssdiff = atoi(value);
185 else if (!strcmp(name, "agefile")) 193 else if (!strcmp(name, "agefile"))
186 ctx.cfg.agefile = xstrdup(value); 194 ctx.cfg.agefile = xstrdup(value);
187 else if (!strcmp(name, "renamelimit")) 195 else if (!strcmp(name, "renamelimit"))
188 ctx.cfg.renamelimit = atoi(value); 196 ctx.cfg.renamelimit = atoi(value);
189 else if (!strcmp(name, "robots")) 197 else if (!strcmp(name, "robots"))
190 ctx.cfg.robots = xstrdup(value); 198 ctx.cfg.robots = xstrdup(value);
@@ -206,12 +214,14 @@ static void querystring_cb(const char *name, const char *value)
206 if (!strcmp(name,"r")) { 214 if (!strcmp(name,"r")) {
207 ctx.qry.repo = xstrdup(value); 215 ctx.qry.repo = xstrdup(value);
208 ctx.repo = cgit_get_repoinfo(value); 216 ctx.repo = cgit_get_repoinfo(value);
209 } else if (!strcmp(name, "p")) { 217 } else if (!strcmp(name, "p")) {
210 ctx.qry.page = xstrdup(value); 218 ctx.qry.page = xstrdup(value);
211 } else if (!strcmp(name, "url")) { 219 } else if (!strcmp(name, "url")) {
220 if (*value == '/')
221 value++;
212 ctx.qry.url = xstrdup(value); 222 ctx.qry.url = xstrdup(value);
213 cgit_parse_url(value); 223 cgit_parse_url(value);
214 } else if (!strcmp(name, "qt")) { 224 } else if (!strcmp(name, "qt")) {
215 ctx.qry.grep = xstrdup(value); 225 ctx.qry.grep = xstrdup(value);
216 } else if (!strcmp(name, "q")) { 226 } else if (!strcmp(name, "q")) {
217 ctx.qry.search = xstrdup(value); 227 ctx.qry.search = xstrdup(value);
@@ -235,12 +245,14 @@ static void querystring_cb(const char *name, const char *value)
235 } else if (!strcmp(name, "s")){ 245 } else if (!strcmp(name, "s")){
236 ctx.qry.sort = xstrdup(value); 246 ctx.qry.sort = xstrdup(value);
237 } else if (!strcmp(name, "showmsg")) { 247 } else if (!strcmp(name, "showmsg")) {
238 ctx.qry.showmsg = atoi(value); 248 ctx.qry.showmsg = atoi(value);
239 } else if (!strcmp(name, "period")) { 249 } else if (!strcmp(name, "period")) {
240 ctx.qry.period = xstrdup(value); 250 ctx.qry.period = xstrdup(value);
251 } else if (!strcmp(name, "ss")) {
252 ctx.qry.ssdiff = atoi(value);
241 } 253 }
242} 254}
243 255
244char *xstrdupn(const char *str) 256char *xstrdupn(const char *str)
245{ 257{
246 return (str ? xstrdup(str) : NULL); 258 return (str ? xstrdup(str) : NULL);
@@ -265,23 +277,25 @@ static void prepare_context(struct cgit_context *ctx)
265 ctx->cfg.enable_tree_linenumbers = 1; 277 ctx->cfg.enable_tree_linenumbers = 1;
266 ctx->cfg.max_repo_count = 50; 278 ctx->cfg.max_repo_count = 50;
267 ctx->cfg.max_commit_count = 50; 279 ctx->cfg.max_commit_count = 50;
268 ctx->cfg.max_lock_attempts = 5; 280 ctx->cfg.max_lock_attempts = 5;
269 ctx->cfg.max_msg_len = 80; 281 ctx->cfg.max_msg_len = 80;
270 ctx->cfg.max_repodesc_len = 80; 282 ctx->cfg.max_repodesc_len = 80;
283 ctx->cfg.max_blob_size = 0;
271 ctx->cfg.max_stats = 0; 284 ctx->cfg.max_stats = 0;
272 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 285 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
273 ctx->cfg.renamelimit = -1; 286 ctx->cfg.renamelimit = -1;
274 ctx->cfg.robots = "index, nofollow"; 287 ctx->cfg.robots = "index, nofollow";
275 ctx->cfg.root_title = "Git repository browser"; 288 ctx->cfg.root_title = "Git repository browser";
276 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 289 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
277 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 290 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
278 ctx->cfg.section = ""; 291 ctx->cfg.section = "";
279 ctx->cfg.summary_branches = 10; 292 ctx->cfg.summary_branches = 10;
280 ctx->cfg.summary_log = 10; 293 ctx->cfg.summary_log = 10;
281 ctx->cfg.summary_tags = 10; 294 ctx->cfg.summary_tags = 10;
295 ctx->cfg.ssdiff = 0;
282 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 296 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
283 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 297 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
284 ctx->env.https = xstrdupn(getenv("HTTPS")); 298 ctx->env.https = xstrdupn(getenv("HTTPS"));
285 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 299 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
286 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 300 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
287 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 301 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
diff --git a/cgit.css b/cgit.css
index c47ebc9..0cb894a 100644
--- a/cgit.css
+++ b/cgit.css
@@ -159,12 +159,17 @@ table.list td.logmsg {
159} 159}
160 160
161table.list td a { 161table.list td a {
162 color: black; 162 color: black;
163} 163}
164 164
165table.list td a.ls-dir {
166 font-weight: bold;
167 color: #00f;
168}
169
165table.list td a:hover { 170table.list td a:hover {
166 color: #00f; 171 color: #00f;
167} 172}
168 173
169img { 174img {
170 border: none; 175 border: none;
@@ -598,6 +603,105 @@ table.hgraph td {
598} 603}
599 604
600table.hgraph div.bar { 605table.hgraph div.bar {
601 background-color: #eee; 606 background-color: #eee;
602 height: 1em; 607 height: 1em;
603} 608}
609
610table.ssdiff {
611 width: 100%;
612}
613
614table.ssdiff td {
615 font-size: 75%;
616 font-family: monospace;
617 white-space: pre;
618 padding: 1px 4px 1px 4px;
619 border-left: solid 1px #aaa;
620 border-right: solid 1px #aaa;
621}
622
623table.ssdiff td.add {
624 color: black;
625 background: #cfc;
626 min-width: 50%;
627}
628
629table.ssdiff td.add_dark {
630 color: black;
631 background: #aca;
632 min-width: 50%;
633}
634
635table.ssdiff span.add {
636 background: #cfc;
637 font-weight: bold;
638}
639
640table.ssdiff td.del {
641 color: black;
642 background: #fcc;
643 min-width: 50%;
644}
645
646table.ssdiff td.del_dark {
647 color: black;
648 background: #caa;
649 min-width: 50%;
650}
651
652table.ssdiff span.del {
653 background: #fcc;
654 font-weight: bold;
655}
656
657table.ssdiff td.changed {
658 color: black;
659 background: #ffc;
660 min-width: 50%;
661}
662
663table.ssdiff td.changed_dark {
664 color: black;
665 background: #cca;
666 min-width: 50%;
667}
668
669table.ssdiff td.lineno {
670 color: black;
671 background: #eee;
672 text-align: right;
673 width: 3em;
674 min-width: 3em;
675}
676
677table.ssdiff td.hunk {
678 color: #black;
679 background: #ccf;
680 border-top: solid 1px #aaa;
681 border-bottom: solid 1px #aaa;
682}
683
684table.ssdiff td.head {
685 border-top: solid 1px #aaa;
686 border-bottom: solid 1px #aaa;
687}
688
689table.ssdiff td.head div.head {
690 font-weight: bold;
691 color: black;
692}
693
694table.ssdiff td.foot {
695 border-top: solid 1px #aaa;
696 border-left: none;
697 border-right: none;
698 border-bottom: none;
699}
700
701table.ssdiff td.space {
702 border: none;
703}
704
705table.ssdiff td.space div {
706 min-height: 3em;
707} \ No newline at end of file
diff --git a/cgit.h b/cgit.h
index 6c6c460..cd4af72 100644
--- a/cgit.h
+++ b/cgit.h
@@ -69,12 +69,13 @@ struct cgit_repo {
69 char *readme; 69 char *readme;
70 char *section; 70 char *section;
71 char *clone_url; 71 char *clone_url;
72 int snapshots; 72 int snapshots;
73 int enable_log_filecount; 73 int enable_log_filecount;
74 int enable_log_linecount; 74 int enable_log_linecount;
75 int enable_remote_branches;
75 int max_stats; 76 int max_stats;
76 time_t mtime; 77 time_t mtime;
77 struct cgit_filter *about_filter; 78 struct cgit_filter *about_filter;
78 struct cgit_filter *commit_filter; 79 struct cgit_filter *commit_filter;
79 struct cgit_filter *source_filter; 80 struct cgit_filter *source_filter;
80}; 81};
@@ -140,12 +141,13 @@ struct cgit_query {
140 char *url; 141 char *url;
141 char *period; 142 char *period;
142 int ofs; 143 int ofs;
143 int nohead; 144 int nohead;
144 char *sort; 145 char *sort;
145 int showmsg; 146 int showmsg;
147 int ssdiff;
146}; 148};
147 149
148struct cgit_config { 150struct cgit_config {
149 char *agefile; 151 char *agefile;
150 char *cache_root; 152 char *cache_root;
151 char *clone_prefix; 153 char *clone_prefix;
@@ -175,28 +177,31 @@ struct cgit_config {
175 int cache_static_ttl; 177 int cache_static_ttl;
176 int embedded; 178 int embedded;
177 int enable_filter_overrides; 179 int enable_filter_overrides;
178 int enable_index_links; 180 int enable_index_links;
179 int enable_log_filecount; 181 int enable_log_filecount;
180 int enable_log_linecount; 182 int enable_log_linecount;
183 int enable_remote_branches;
181 int enable_tree_linenumbers; 184 int enable_tree_linenumbers;
182 int local_time; 185 int local_time;
183 int max_repo_count; 186 int max_repo_count;
184 int max_commit_count; 187 int max_commit_count;
185 int max_lock_attempts; 188 int max_lock_attempts;
186 int max_msg_len; 189 int max_msg_len;
187 int max_repodesc_len; 190 int max_repodesc_len;
191 int max_blob_size;
188 int max_stats; 192 int max_stats;
189 int nocache; 193 int nocache;
190 int noplainemail; 194 int noplainemail;
191 int noheader; 195 int noheader;
192 int renamelimit; 196 int renamelimit;
193 int snapshots; 197 int snapshots;
194 int summary_branches; 198 int summary_branches;
195 int summary_log; 199 int summary_log;
196 int summary_tags; 200 int summary_tags;
201 int ssdiff;
197 struct string_list mimetypes; 202 struct string_list mimetypes;
198 struct cgit_filter *about_filter; 203 struct cgit_filter *about_filter;
199 struct cgit_filter *commit_filter; 204 struct cgit_filter *commit_filter;
200 struct cgit_filter *source_filter; 205 struct cgit_filter *source_filter;
201}; 206};
202 207
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 0c13485..d74d9e7 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -107,12 +107,17 @@ enable-log-filecount::
107 107
108enable-log-linecount:: 108enable-log-linecount::
109 Flag which, when set to "1", will make cgit print the number of added 109 Flag which, when set to "1", will make cgit print the number of added
110 and removed lines for each commit on the repository log page. Default 110 and removed lines for each commit on the repository log page. Default
111 value: "0". 111 value: "0".
112 112
113enable-remote-branches::
114 Flag which, when set to "1", will make cgit display remote branches
115 in the summary and refs views. Default value: "0". See also:
116 "repo.enable-remote-branches".
117
113enable-tree-linenumbers:: 118enable-tree-linenumbers::
114 Flag which, when set to "1", will make cgit generate linenumber links 119 Flag which, when set to "1", will make cgit generate linenumber links
115 for plaintext blobs printed in the tree view. Default value: "1". 120 for plaintext blobs printed in the tree view. Default value: "1".
116 121
117favicon:: 122favicon::
118 Url used as link to a shortcut icon for cgit. If specified, it is 123 Url used as link to a shortcut icon for cgit. If specified, it is
@@ -174,12 +179,16 @@ max-repo-count::
174 index page. Default value: "50". 179 index page. Default value: "50".
175 180
176max-repodesc-length:: 181max-repodesc-length::
177 Specifies the maximum number of repo description characters to display 182 Specifies the maximum number of repo description characters to display
178 on the repository index page. Default value: "80". 183 on the repository index page. Default value: "80".
179 184
185max-blob-size::
186 Specifies the maximum size of a blob to display HTML for in KBytes.
187 Default value: "0" (limit disabled).
188
180max-stats:: 189max-stats::
181 Set the default maximum statistics period. Valid values are "week", 190 Set the default maximum statistics period. Valid values are "week",
182 "month", "quarter" and "year". If unspecified, statistics are 191 "month", "quarter" and "year". If unspecified, statistics are
183 disabled. Default value: none. See also: "repo.max-stats". 192 disabled. Default value: none. See also: "repo.max-stats".
184 193
185mimetype.<ext>:: 194mimetype.<ext>::
@@ -238,12 +247,16 @@ scan-path::
238 247
239section:: 248section::
240 The name of the current repository section - all repositories defined 249 The name of the current repository section - all repositories defined
241 after this option will inherit the current section name. Default value: 250 after this option will inherit the current section name. Default value:
242 none. 251 none.
243 252
253side-by-side-diffs::
254 If set to "1" shows side-by-side diffs instead of unidiffs per
255 default. Default value: "0".
256
244snapshots:: 257snapshots::
245 Text which specifies the default set of snapshot formats generated by 258 Text which specifies the default set of snapshot formats generated by
246 cgit. The value is a space-separated list of zero or more of the 259 cgit. The value is a space-separated list of zero or more of the
247 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 260 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
248 261
249source-filter:: 262source-filter::
@@ -301,12 +314,16 @@ repo.enable-log-filecount::
301 `enable-log-filecount'. Default value: none. 314 `enable-log-filecount'. Default value: none.
302 315
303repo.enable-log-linecount:: 316repo.enable-log-linecount::
304 A flag which can be used to disable the global setting 317 A flag which can be used to disable the global setting
305 `enable-log-linecount'. Default value: none. 318 `enable-log-linecount'. Default value: none.
306 319
320repo.enable-remote-branches::
321 Flag which, when set to "1", will make cgit display remote branches
322 in the summary and refs views. Default value: <enable-remote-branches>.
323
307repo.max-stats:: 324repo.max-stats::
308 Override the default maximum statistics period. Valid values are equal 325 Override the default maximum statistics period. Valid values are equal
309 to the values specified for the global "max-stats" setting. Default 326 to the values specified for the global "max-stats" setting. Default
310 value: none. 327 value: none.
311 328
312repo.name:: 329repo.name::
diff --git a/filters/syntax-highlighting.sh b/filters/syntax-highlighting.sh
index 999ad0c..6b1c576 100755
--- a/filters/syntax-highlighting.sh
+++ b/filters/syntax-highlighting.sh
@@ -1,11 +1,15 @@
1#!/bin/sh 1#!/bin/sh
2# This script can be used to implement syntax highlighting in the cgit 2# This script can be used to implement syntax highlighting in the cgit
3# tree-view by refering to this file with the source-filter or repo.source- 3# tree-view by refering to this file with the source-filter or repo.source-
4# filter options in cgitrc. 4# filter options in cgitrc.
5# 5#
6# This script requires a shell supporting the ${var##pattern} syntax.
7# It is supported by at least dash and bash, however busybox environments
8# might have to use an external call to sed instead.
9#
6# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax 10# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
7# highlighting, so you'll probably want something like the following included 11# highlighting, so you'll probably want something like the following included
8# in your css file (generated by highlight 2.4.8 and adapted for cgit): 12# in your css file (generated by highlight 2.4.8 and adapted for cgit):
9# 13#
10# table.blob .num { color:#2928ff; } 14# table.blob .num { color:#2928ff; }
11# table.blob .esc { color:#ff00ff; } 15# table.blob .esc { color:#ff00ff; }
@@ -17,23 +21,14 @@
17# table.blob .sym { color:#000000; } 21# table.blob .sym { color:#000000; }
18# table.blob .kwa { color:#000000; font-weight:bold; } 22# table.blob .kwa { color:#000000; font-weight:bold; }
19# table.blob .kwb { color:#830000; } 23# table.blob .kwb { color:#830000; }
20# table.blob .kwc { color:#000000; font-weight:bold; } 24# table.blob .kwc { color:#000000; font-weight:bold; }
21# table.blob .kwd { color:#010181; } 25# table.blob .kwd { color:#010181; }
22 26
23case "$1" in 27# store filename and extension in local vars
24 *.c) 28BASENAME="$1"
25 highlight -f -I -X -S c 29EXTENSION="${BASENAME##*.}"
26 ;; 30
27 *.h) 31# map Makefile and Makefile.* to .mk
28 highlight -f -I -X -S c 32[ "${BASENAME%%.*}" == "Makefile" ] && EXTENSION=mk
29 ;; 33
30 *.sh) 34exec highlight --force -f -I -X -S $EXTENSION 2>/dev/null
31 highlight -f -I -X -S sh
32 ;;
33 *.css)
34 highlight -f -I -X -S css
35 ;;
36 *)
37 highlight -f -I -X -S txt
38 ;;
39esac
diff --git a/git b/git
Subproject 7fb6bcff2dece2ff9fbc5ebfe526d9b2a7e764c Subproject e923eaeb901ff056421b9007adcbbce271caa7b
diff --git a/html.c b/html.c
index 66ba65d..337baeb 100644
--- a/html.c
+++ b/html.c
@@ -10,12 +10,38 @@
10#include <stdio.h> 10#include <stdio.h>
11#include <stdlib.h> 11#include <stdlib.h>
12#include <stdarg.h> 12#include <stdarg.h>
13#include <string.h> 13#include <string.h>
14#include <errno.h> 14#include <errno.h>
15 15
16/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */
17static const char* url_escape_table[256] = {
18 "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09",
19 "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13",
20 "%14", "%15", "%16", "%17", "%18", "%19", "%1a", "%1b", "%1c", "%1d",
21 "%1e", "%1f", "%20", 0, "%22", "%23", 0, "%25", "%26", "%27", 0, 0, 0,
22 "%2b", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%3c", "%3d",
23 "%3e", "%3f", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
24 0, 0, 0, 0, 0, 0, 0, 0, 0, "%5c", 0, "%5e", 0, "%60", 0, 0, 0, 0, 0,
25 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%7b",
26 "%7c", "%7d", 0, "%7f", "%80", "%81", "%82", "%83", "%84", "%85",
27 "%86", "%87", "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
28 "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99",
29 "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", "%a0", "%a1", "%a2", "%a3",
30 "%a4", "%a5", "%a6", "%a7", "%a8", "%a9", "%aa", "%ab", "%ac", "%ad",
31 "%ae", "%af", "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
32 "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", "%c0", "%c1",
33 "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", "%c8", "%c9", "%ca", "%cb",
34 "%cc", "%cd", "%ce", "%cf", "%d0", "%d1", "%d2", "%d3", "%d4", "%d5",
35 "%d6", "%d7", "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
36 "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", "%e8", "%e9",
37 "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", "%f0", "%f1", "%f2", "%f3",
38 "%f4", "%f5", "%f6", "%f7", "%f8", "%f9", "%fa", "%fb", "%fc", "%fd",
39 "%fe", "%ff"
40};
41
16int htmlfd = STDOUT_FILENO; 42int htmlfd = STDOUT_FILENO;
17 43
18char *fmt(const char *format, ...) 44char *fmt(const char *format, ...)
19{ 45{
20 static char buf[8][1024]; 46 static char buf[8][1024];
21 static int bufidx; 47 static int bufidx;
@@ -60,15 +86,15 @@ void html_status(int code, const char *msg, int more_headers)
60{ 86{
61 htmlf("Status: %d %s\n", code, msg); 87 htmlf("Status: %d %s\n", code, msg);
62 if (!more_headers) 88 if (!more_headers)
63 html("\n"); 89 html("\n");
64} 90}
65 91
66void html_txt(char *txt) 92void html_txt(const char *txt)
67{ 93{
68 char *t = txt; 94 const char *t = txt;
69 while(t && *t){ 95 while(t && *t){
70 int c = *t; 96 int c = *t;
71 if (c=='<' || c=='>' || c=='&') { 97 if (c=='<' || c=='>' || c=='&') {
72 write(htmlfd, txt, t - txt); 98 write(htmlfd, txt, t - txt);
73 if (c=='>') 99 if (c=='>')
74 html("&gt;"); 100 html("&gt;");
@@ -81,15 +107,15 @@ void html_txt(char *txt)
81 t++; 107 t++;
82 } 108 }
83 if (t!=txt) 109 if (t!=txt)
84 html(txt); 110 html(txt);
85} 111}
86 112
87void html_ntxt(int len, char *txt) 113void html_ntxt(int len, const char *txt)
88{ 114{
89 char *t = txt; 115 const char *t = txt;
90 while(t && *t && len--){ 116 while(t && *t && len--){
91 int c = *t; 117 int c = *t;
92 if (c=='<' || c=='>' || c=='&') { 118 if (c=='<' || c=='>' || c=='&') {
93 write(htmlfd, txt, t - txt); 119 write(htmlfd, txt, t - txt);
94 if (c=='>') 120 if (c=='>')
95 html("&gt;"); 121 html("&gt;");
@@ -104,15 +130,15 @@ void html_ntxt(int len, char *txt)
104 if (t!=txt) 130 if (t!=txt)
105 write(htmlfd, txt, t - txt); 131 write(htmlfd, txt, t - txt);
106 if (len<0) 132 if (len<0)
107 html("..."); 133 html("...");
108} 134}
109 135
110void html_attr(char *txt) 136void html_attr(const char *txt)
111{ 137{
112 char *t = txt; 138 const char *t = txt;
113 while(t && *t){ 139 while(t && *t){
114 int c = *t; 140 int c = *t;
115 if (c=='<' || c=='>' || c=='\'' || c=='\"') { 141 if (c=='<' || c=='>' || c=='\'' || c=='\"') {
116 write(htmlfd, txt, t - txt); 142 write(htmlfd, txt, t - txt);
117 if (c=='>') 143 if (c=='>')
118 html("&gt;"); 144 html("&gt;");
@@ -127,66 +153,68 @@ void html_attr(char *txt)
127 t++; 153 t++;
128 } 154 }
129 if (t!=txt) 155 if (t!=txt)
130 html(txt); 156 html(txt);
131} 157}
132 158
133void html_url_path(char *txt) 159void html_url_path(const char *txt)
134{ 160{
135 char *t = txt; 161 const char *t = txt;
136 while(t && *t){ 162 while(t && *t){
137 int c = *t; 163 int c = *t;
138 if (c=='"' || c=='#' || c=='\'' || c=='?') { 164 const char *e = url_escape_table[c];
165 if (e && c!='+' && c!='&' && c!='+') {
139 write(htmlfd, txt, t - txt); 166 write(htmlfd, txt, t - txt);
140 write(htmlfd, fmt("%%%2x", c), 3); 167 write(htmlfd, e, 3);
141 txt = t+1; 168 txt = t+1;
142 } 169 }
143 t++; 170 t++;
144 } 171 }
145 if (t!=txt) 172 if (t!=txt)
146 html(txt); 173 html(txt);
147} 174}
148 175
149void html_url_arg(char *txt) 176void html_url_arg(const char *txt)
150{ 177{
151 char *t = txt; 178 const char *t = txt;
152 while(t && *t){ 179 while(t && *t){
153 int c = *t; 180 int c = *t;
154 if (c=='"' || c=='#' || c=='%' || c=='&' || c=='\'' || c=='+' || c=='?') { 181 const char *e = url_escape_table[c];
182 if (e) {
155 write(htmlfd, txt, t - txt); 183 write(htmlfd, txt, t - txt);
156 write(htmlfd, fmt("%%%2x", c), 3); 184 write(htmlfd, e, 3);
157 txt = t+1; 185 txt = t+1;
158 } 186 }
159 t++; 187 t++;
160 } 188 }
161 if (t!=txt) 189 if (t!=txt)
162 html(txt); 190 html(txt);
163} 191}
164 192
165void html_hidden(char *name, char *value) 193void html_hidden(const char *name, const char *value)
166{ 194{
167 html("<input type='hidden' name='"); 195 html("<input type='hidden' name='");
168 html_attr(name); 196 html_attr(name);
169 html("' value='"); 197 html("' value='");
170 html_attr(value); 198 html_attr(value);
171 html("'/>"); 199 html("'/>");
172} 200}
173 201
174void html_option(char *value, char *text, char *selected_value) 202void html_option(const char *value, const char *text, const char *selected_value)
175{ 203{
176 html("<option value='"); 204 html("<option value='");
177 html_attr(value); 205 html_attr(value);
178 html("'"); 206 html("'");
179 if (selected_value && !strcmp(selected_value, value)) 207 if (selected_value && !strcmp(selected_value, value))
180 html(" selected='selected'"); 208 html(" selected='selected'");
181 html(">"); 209 html(">");
182 html_txt(text); 210 html_txt(text);
183 html("</option>\n"); 211 html("</option>\n");
184} 212}
185 213
186void html_link_open(char *url, char *title, char *class) 214void html_link_open(const char *url, const char *title, const char *class)
187{ 215{
188 html("<a href='"); 216 html("<a href='");
189 html_attr(url); 217 html_attr(url);
190 if (title) { 218 if (title) {
191 html("' title='"); 219 html("' title='");
192 html_attr(title); 220 html_attr(title);
@@ -254,20 +282,20 @@ char *convert_query_hexchar(char *txt)
254 *txt = d1 * 16 + d2; 282 *txt = d1 * 16 + d2;
255 strcpy(txt+1, txt+3); 283 strcpy(txt+1, txt+3);
256 return txt; 284 return txt;
257 } 285 }
258} 286}
259 287
260int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)) 288int http_parse_querystring(const char *txt_, void (*fn)(const char *name, const char *value))
261{ 289{
262 char *t, *value = NULL, c; 290 char *t, *txt, *value = NULL, c;
263 291
264 if (!txt) 292 if (!txt_)
265 return 0; 293 return 0;
266 294
267 t = txt = strdup(txt); 295 t = txt = strdup(txt_);
268 if (t == NULL) { 296 if (t == NULL) {
269 printf("Out of memory\n"); 297 printf("Out of memory\n");
270 exit(1); 298 exit(1);
271 } 299 }
272 while((c=*t) != '\0') { 300 while((c=*t) != '\0') {
273 if (c=='=') { 301 if (c=='=') {
diff --git a/html.h b/html.h
index a55d4b2..16d55ec 100644
--- a/html.h
+++ b/html.h
@@ -4,21 +4,21 @@
4extern int htmlfd; 4extern int htmlfd;
5 5
6extern void html_raw(const char *txt, size_t size); 6extern void html_raw(const char *txt, size_t size);
7extern void html(const char *txt); 7extern void html(const char *txt);
8extern void htmlf(const char *format,...); 8extern void htmlf(const char *format,...);
9extern void html_status(int code, const char *msg, int more_headers); 9extern void html_status(int code, const char *msg, int more_headers);
10extern void html_txt(char *txt); 10extern void html_txt(const char *txt);
11extern void html_ntxt(int len, char *txt); 11extern void html_ntxt(int len, const char *txt);
12extern void html_attr(char *txt); 12extern void html_attr(const char *txt);
13extern void html_url_path(char *txt); 13extern void html_url_path(const char *txt);
14extern void html_url_arg(char *txt); 14extern void html_url_arg(const char *txt);
15extern void html_hidden(char *name, char *value); 15extern void html_hidden(const char *name, const char *value);
16extern void html_option(char *value, char *text, char *selected_value); 16extern void html_option(const char *value, const char *text, const char *selected_value);
17extern void html_link_open(char *url, char *title, char *class); 17extern void html_link_open(const char *url, const char *title, const char *class);
18extern void html_link_close(void); 18extern void html_link_close(void);
19extern void html_fileperm(unsigned short mode); 19extern void html_fileperm(unsigned short mode);
20extern int html_include(const char *filename); 20extern int html_include(const char *filename);
21 21
22extern int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)); 22extern int http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value));
23 23
24#endif /* HTML_H */ 24#endif /* HTML_H */
diff --git a/shared.c b/shared.c
index 6adf2b6..83b71e6 100644
--- a/shared.c
+++ b/shared.c
@@ -56,12 +56,13 @@ struct cgit_repo *cgit_add_repo(const char *url)
56 ret->owner = NULL; 56 ret->owner = NULL;
57 ret->section = ctx.cfg.section; 57 ret->section = ctx.cfg.section;
58 ret->defbranch = "master"; 58 ret->defbranch = "master";
59 ret->snapshots = ctx.cfg.snapshots; 59 ret->snapshots = ctx.cfg.snapshots;
60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
62 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
62 ret->max_stats = ctx.cfg.max_stats; 63 ret->max_stats = ctx.cfg.max_stats;
63 ret->module_link = ctx.cfg.module_link; 64 ret->module_link = ctx.cfg.module_link;
64 ret->readme = NULL; 65 ret->readme = NULL;
65 ret->mtime = -1; 66 ret->mtime = -1;
66 ret->about_filter = ctx.cfg.about_filter; 67 ret->about_filter = ctx.cfg.about_filter;
67 ret->commit_filter = ctx.cfg.commit_filter; 68 ret->commit_filter = ctx.cfg.commit_filter;
diff --git a/ui-commit.c b/ui-commit.c
index f5b0ae5..b5e3c01 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -55,15 +55,20 @@ void cgit_print_commit(char *hex)
55 } 55 }
56 html("</td><td class='right'>"); 56 html("</td><td class='right'>");
57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
58 html("</td></tr>\n"); 58 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 59 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 60 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); 61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, 0);
62 html(" ("); 62 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 63 cgit_patch_link("patch", NULL, NULL, NULL, tmp);
64 html(") (");
65 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
66 cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, 1);
67 else
68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, 1);
64 html(")</td></tr>\n"); 69 html(")</td></tr>\n");
65 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 70 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
66 tmp = xstrdup(hex); 71 tmp = xstrdup(hex);
67 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 72 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
68 ctx.qry.head, tmp, NULL); 73 ctx.qry.head, tmp, NULL);
69 html("</td></tr>\n"); 74 html("</td></tr>\n");
@@ -75,16 +80,16 @@ void cgit_print_commit(char *hex)
75 html("</td></tr>"); 80 html("</td></tr>");
76 continue; 81 continue;
77 } 82 }
78 html("<tr><th>parent</th>" 83 html("<tr><th>parent</th>"
79 "<td colspan='2' class='sha1'>"); 84 "<td colspan='2' class='sha1'>");
80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 85 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL,
81 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 86 ctx.qry.head, sha1_to_hex(p->item->object.sha1), 0);
82 html(" ("); 87 html(" (");
83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 88 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
84 sha1_to_hex(p->item->object.sha1), NULL); 89 sha1_to_hex(p->item->object.sha1), NULL, 0);
85 html(")</td></tr>"); 90 html(")</td></tr>");
86 parents++; 91 parents++;
87 } 92 }
88 if (ctx.repo->snapshots) { 93 if (ctx.repo->snapshots) {
89 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 94 html("<tr><th>download</th><td colspan='2' class='sha1'>");
90 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 95 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
diff --git a/ui-diff.c b/ui-diff.c
index 2196745..a92a768 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -6,12 +6,13 @@
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-ssdiff.h"
12 13
13unsigned char old_rev_sha1[20]; 14unsigned char old_rev_sha1[20];
14unsigned char new_rev_sha1[20]; 15unsigned char new_rev_sha1[20];
15 16
16static int files, slots; 17static int files, slots;
17static int total_adds, total_rems, max_changes; 18static int total_adds, total_rems, max_changes;
@@ -29,12 +30,13 @@ static struct fileinfo {
29 unsigned int removed; 30 unsigned int removed;
30 unsigned long old_size; 31 unsigned long old_size;
31 unsigned long new_size; 32 unsigned long new_size;
32 int binary:1; 33 int binary:1;
33} *items; 34} *items;
34 35
36static int use_ssdiff = 0;
35 37
36static void print_fileinfo(struct fileinfo *info) 38static void print_fileinfo(struct fileinfo *info)
37{ 39{
38 char *class; 40 char *class;
39 41
40 switch (info->status) { 42 switch (info->status) {
@@ -80,13 +82,13 @@ static void print_fileinfo(struct fileinfo *info)
80 html("<span class='modechange'>["); 82 html("<span class='modechange'>[");
81 cgit_print_filemode(info->old_mode); 83 cgit_print_filemode(info->old_mode);
82 html("]</span>"); 84 html("]</span>");
83 } 85 }
84 htmlf("</td><td class='%s'>", class); 86 htmlf("</td><td class='%s'>", class);
85 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, 87 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
86 ctx.qry.sha2, info->new_path); 88 ctx.qry.sha2, info->new_path, 0);
87 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
88 htmlf(" (%s from %s)", 90 htmlf(" (%s from %s)",
89 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
90 info->old_path); 92 info->old_path);
91 html("</td><td class='right'>"); 93 html("</td><td class='right'>");
92 if (info->binary) { 94 if (info->binary) {
@@ -155,13 +157,13 @@ void cgit_print_diffstat(const unsigned char *old_sha1,
155 const unsigned char *new_sha1) 157 const unsigned char *new_sha1)
156{ 158{
157 int i; 159 int i;
158 160
159 html("<div class='diffstat-header'>"); 161 html("<div class='diffstat-header'>");
160 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, 162 cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
161 ctx.qry.sha2, NULL); 163 ctx.qry.sha2, NULL, 0);
162 html("</div>"); 164 html("</div>");
163 html("<table summary='diffstat' class='diffstat'>"); 165 html("<table summary='diffstat' class='diffstat'>");
164 max_changes = 0; 166 max_changes = 0;
165 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); 167 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL);
166 for(i = 0; i<files; i++) 168 for(i = 0; i<files; i++)
167 print_fileinfo(&items[i]); 169 print_fileinfo(&items[i]);
@@ -243,32 +245,60 @@ static void header(unsigned char *sha1, char *path1, int mode1,
243 else 245 else
244 html_txt(path2); 246 html_txt(path2);
245 } 247 }
246 html("</div>"); 248 html("</div>");
247} 249}
248 250
251static void print_ssdiff_link()
252{
253 if (!strcmp(ctx.qry.page, "diff")) {
254 if (use_ssdiff)
255 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
256 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1);
257 else
258 cgit_diff_link("Side-by-side diff", NULL, NULL,
259 ctx.qry.head, ctx.qry.sha1,
260 ctx.qry.sha2, ctx.qry.path, 1);
261 }
262}
263
249static void filepair_cb(struct diff_filepair *pair) 264static void filepair_cb(struct diff_filepair *pair)
250{ 265{
251 unsigned long old_size = 0; 266 unsigned long old_size = 0;
252 unsigned long new_size = 0; 267 unsigned long new_size = 0;
253 int binary = 0; 268 int binary = 0;
269 linediff_fn print_line_fn = print_line;
254 270
271 if (use_ssdiff) {
272 cgit_ssdiff_header_begin();
273 print_line_fn = cgit_ssdiff_line_cb;
274 }
255 header(pair->one->sha1, pair->one->path, pair->one->mode, 275 header(pair->one->sha1, pair->one->path, pair->one->mode,
256 pair->two->sha1, pair->two->path, pair->two->mode); 276 pair->two->sha1, pair->two->path, pair->two->mode);
277 if (use_ssdiff)
278 cgit_ssdiff_header_end();
257 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 279 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
258 if (S_ISGITLINK(pair->one->mode)) 280 if (S_ISGITLINK(pair->one->mode))
259 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 281 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
260 if (S_ISGITLINK(pair->two->mode)) 282 if (S_ISGITLINK(pair->two->mode))
261 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 283 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
284 if (use_ssdiff)
285 cgit_ssdiff_footer();
262 return; 286 return;
263 } 287 }
264 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 288 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
265 &new_size, &binary, print_line)) 289 &new_size, &binary, print_line_fn))
266 cgit_print_error("Error running diff"); 290 cgit_print_error("Error running diff");
267 if (binary) 291 if (binary) {
268 html("Binary files differ"); 292 if (use_ssdiff)
293 html("<tr><td colspan='4'>Binary files differ</td></tr>");
294 else
295 html("Binary files differ");
296 }
297 if (use_ssdiff)
298 cgit_ssdiff_footer();
269} 299}
270 300
271void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) 301void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
272{ 302{
273 enum object_type type; 303 enum object_type type;
274 unsigned long size; 304 unsigned long size;
@@ -300,14 +330,24 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefi
300 return; 330 return;
301 } 331 }
302 commit2 = lookup_commit_reference(old_rev_sha1); 332 commit2 = lookup_commit_reference(old_rev_sha1);
303 if (!commit2 || parse_commit(commit2)) 333 if (!commit2 || parse_commit(commit2))
304 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 334 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
305 } 335 }
336
337 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
338 use_ssdiff = 1;
339
340 print_ssdiff_link();
306 cgit_print_diffstat(old_rev_sha1, new_rev_sha1); 341 cgit_print_diffstat(old_rev_sha1, new_rev_sha1);
307 342
308 html("<table summary='diff' class='diff'>"); 343 if (use_ssdiff) {
309 html("<tr><td>"); 344 html("<table summary='ssdiff' class='ssdiff'>");
345 } else {
346 html("<table summary='diff' class='diff'>");
347 html("<tr><td>");
348 }
310 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); 349 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix);
311 html("</td></tr>"); 350 if (!use_ssdiff)
351 html("</td></tr>");
312 html("</table>"); 352 html("</table>");
313} 353}
diff --git a/ui-log.c b/ui-log.c
index f3132c9..0947604 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -63,13 +63,13 @@ void show_commit_decorations(struct commit *commit)
63 sha1_to_hex(commit->object.sha1), NULL, 63 sha1_to_hex(commit->object.sha1), NULL,
64 0, NULL, NULL, ctx.qry.showmsg); 64 0, NULL, NULL, ctx.qry.showmsg);
65 } 65 }
66 else { 66 else {
67 strncpy(buf, deco->name, sizeof(buf) - 1); 67 strncpy(buf, deco->name, sizeof(buf) - 1);
68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
69 sha1_to_hex(commit->object.sha1)); 69 sha1_to_hex(commit->object.sha1), 0);
70 } 70 }
71 deco = deco->next; 71 deco = deco->next;
72 } 72 }
73} 73}
74 74
75void print_commit(struct commit *commit) 75void print_commit(struct commit *commit)
@@ -86,13 +86,13 @@ void print_commit(struct commit *commit)
86 html_link_open(tmp, NULL, NULL); 86 html_link_open(tmp, NULL, NULL);
87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
88 html_link_close(); 88 html_link_close();
89 htmlf("</td><td%s>", 89 htmlf("</td><td%s>",
90 ctx.qry.showmsg ? " class='logsubject'" : ""); 90 ctx.qry.showmsg ? " class='logsubject'" : "");
91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
92 sha1_to_hex(commit->object.sha1)); 92 sha1_to_hex(commit->object.sha1), 0);
93 show_commit_decorations(commit); 93 show_commit_decorations(commit);
94 html("</td><td>"); 94 html("</td><td>");
95 html_txt(info->author); 95 html_txt(info->author);
96 if (ctx.repo->enable_log_filecount) { 96 if (ctx.repo->enable_log_filecount) {
97 files = 0; 97 files = 0;
98 add_lines = 0; 98 add_lines = 0;
diff --git a/ui-refs.c b/ui-refs.c
index d3b4f6e..98738db 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -71,13 +71,13 @@ static int print_branch(struct refinfo *ref)
71 html("<tr><td>"); 71 html("<tr><td>");
72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
73 ctx.qry.showmsg); 73 ctx.qry.showmsg);
74 html("</td><td>"); 74 html("</td><td>");
75 75
76 if (ref->object->type == OBJ_COMMIT) { 76 if (ref->object->type == OBJ_COMMIT) {
77 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, 0);
78 html("</td><td>"); 78 html("</td><td>");
79 html_txt(info->author); 79 html_txt(info->author);
80 html("</td><td colspan='2'>"); 80 html("</td><td colspan='2'>");
81 cgit_print_age(info->commit->date, -1, NULL); 81 cgit_print_age(info->commit->date, -1, NULL);
82 } else { 82 } else {
83 html("</td><td></td><td>"); 83 html("</td><td></td><td>");
@@ -184,12 +184,14 @@ void cgit_print_branches(int maxcount)
184 "<th class='left'>Author</th>" 184 "<th class='left'>Author</th>"
185 "<th class='left' colspan='2'>Age</th></tr>\n"); 185 "<th class='left' colspan='2'>Age</th></tr>\n");
186 186
187 list.refs = NULL; 187 list.refs = NULL;
188 list.alloc = list.count = 0; 188 list.alloc = list.count = 0;
189 for_each_branch_ref(cgit_refs_cb, &list); 189 for_each_branch_ref(cgit_refs_cb, &list);
190 if (ctx.repo->enable_remote_branches)
191 for_each_remote_ref(cgit_refs_cb, &list);
190 192
191 if (maxcount == 0 || maxcount > list.count) 193 if (maxcount == 0 || maxcount > list.count)
192 maxcount = list.count; 194 maxcount = list.count;
193 195
194 if (maxcount < list.count) { 196 if (maxcount < list.count) {
195 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); 197 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
diff --git a/ui-shared.c b/ui-shared.c
index 8a7cc32..8827fff 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -314,21 +314,37 @@ void cgit_log_link(char *name, char *title, char *class, char *head,
314 html("'>"); 314 html("'>");
315 html_txt(name); 315 html_txt(name);
316 html("</a>"); 316 html("</a>");
317} 317}
318 318
319void cgit_commit_link(char *name, char *title, char *class, char *head, 319void cgit_commit_link(char *name, char *title, char *class, char *head,
320 char *rev) 320 char *rev, int toggle_ssdiff)
321{ 321{
322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
323 name[ctx.cfg.max_msg_len] = '\0'; 323 name[ctx.cfg.max_msg_len] = '\0';
324 name[ctx.cfg.max_msg_len - 1] = '.'; 324 name[ctx.cfg.max_msg_len - 1] = '.';
325 name[ctx.cfg.max_msg_len - 2] = '.'; 325 name[ctx.cfg.max_msg_len - 2] = '.';
326 name[ctx.cfg.max_msg_len - 3] = '.'; 326 name[ctx.cfg.max_msg_len - 3] = '.';
327 } 327 }
328 reporevlink("commit", name, title, class, head, rev, NULL); 328
329 char *delim;
330
331 delim = repolink(title, class, "commit", head, NULL);
332 if (rev && strcmp(rev, ctx.qry.head)) {
333 html(delim);
334 html("id=");
335 html_url_arg(rev);
336 delim = "&amp;";
337 }
338 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
339 html(delim);
340 html("ss=1");
341 }
342 html("'>");
343 html_txt(name);
344 html("</a>");
329} 345}
330 346
331void cgit_refs_link(char *name, char *title, char *class, char *head, 347void cgit_refs_link(char *name, char *title, char *class, char *head,
332 char *rev, char *path) 348 char *rev, char *path)
333{ 349{
334 reporevlink("refs", name, title, class, head, rev, path); 350 reporevlink("refs", name, title, class, head, rev, path);
@@ -338,13 +354,14 @@ void cgit_snapshot_link(char *name, char *title, char *class, char *head,
338 char *rev, char *archivename) 354 char *rev, char *archivename)
339{ 355{
340 reporevlink("snapshot", name, title, class, head, rev, archivename); 356 reporevlink("snapshot", name, title, class, head, rev, archivename);
341} 357}
342 358
343void cgit_diff_link(char *name, char *title, char *class, char *head, 359void cgit_diff_link(char *name, char *title, char *class, char *head,
344 char *new_rev, char *old_rev, char *path) 360 char *new_rev, char *old_rev, char *path,
361 int toggle_ssdiff)
345{ 362{
346 char *delim; 363 char *delim;
347 364
348 delim = repolink(title, class, "diff", head, path); 365 delim = repolink(title, class, "diff", head, path);
349 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) { 366 if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) {
350 html(delim); 367 html(delim);
@@ -353,12 +370,17 @@ void cgit_diff_link(char *name, char *title, char *class, char *head,
353 delim = "&amp;"; 370 delim = "&amp;";
354 } 371 }
355 if (old_rev) { 372 if (old_rev) {
356 html(delim); 373 html(delim);
357 html("id2="); 374 html("id2=");
358 html_url_arg(old_rev); 375 html_url_arg(old_rev);
376 delim = "&amp;";
377 }
378 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
379 html(delim);
380 html("ss=1");
359 } 381 }
360 html("'>"); 382 html("'>");
361 html_txt(name); 383 html_txt(name);
362 html("</a>"); 384 html("</a>");
363} 385}
364 386
@@ -380,13 +402,13 @@ void cgit_object_link(struct object *obj)
380 402
381 fullrev = sha1_to_hex(obj->sha1); 403 fullrev = sha1_to_hex(obj->sha1);
382 shortrev = xstrdup(fullrev); 404 shortrev = xstrdup(fullrev);
383 shortrev[10] = '\0'; 405 shortrev[10] = '\0';
384 if (obj->type == OBJ_COMMIT) { 406 if (obj->type == OBJ_COMMIT) {
385 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 407 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
386 ctx.qry.head, fullrev); 408 ctx.qry.head, fullrev, 0);
387 return; 409 return;
388 } else if (obj->type == OBJ_TREE) 410 } else if (obj->type == OBJ_TREE)
389 page = "tree"; 411 page = "tree";
390 else if (obj->type == OBJ_TAG) 412 else if (obj->type == OBJ_TAG)
391 page = "tag"; 413 page = "tag";
392 else 414 else
@@ -692,15 +714,15 @@ void cgit_print_pageheader(struct cgit_context *ctx)
692 ctx->qry.sha1, NULL); 714 ctx->qry.sha1, NULL);
693 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 715 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
694 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); 716 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
695 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 717 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
696 ctx->qry.sha1, NULL); 718 ctx->qry.sha1, NULL);
697 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 719 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
698 ctx->qry.head, ctx->qry.sha1); 720 ctx->qry.head, ctx->qry.sha1, 0);
699 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 721 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
700 ctx->qry.sha1, ctx->qry.sha2, NULL); 722 ctx->qry.sha1, ctx->qry.sha2, NULL, 0);
701 if (ctx->repo->max_stats) 723 if (ctx->repo->max_stats)
702 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 724 cgit_stats_link("stats", NULL, hc(cmd, "stats"),
703 ctx->qry.head, NULL); 725 ctx->qry.head, NULL);
704 if (ctx->repo->readme) 726 if (ctx->repo->readme)
705 reporevlink("about", "about", NULL, 727 reporevlink("about", "about", NULL,
706 hc(cmd, "about"), ctx->qry.head, NULL, 728 hc(cmd, "about"), ctx->qry.head, NULL,
@@ -757,17 +779,22 @@ void cgit_print_filemode(unsigned short mode)
757} 779}
758 780
759void cgit_print_snapshot_links(const char *repo, const char *head, 781void cgit_print_snapshot_links(const char *repo, const char *head,
760 const char *hex, int snapshots) 782 const char *hex, int snapshots)
761{ 783{
762 const struct cgit_snapshot_format* f; 784 const struct cgit_snapshot_format* f;
785 char *prefix;
763 char *filename; 786 char *filename;
787 unsigned char sha1[20];
764 788
789 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 &&
790 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1]))
791 hex++;
792 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex));
765 for (f = cgit_snapshot_formats; f->suffix; f++) { 793 for (f = cgit_snapshot_formats; f->suffix; f++) {
766 if (!(snapshots & f->bit)) 794 if (!(snapshots & f->bit))
767 continue; 795 continue;
768 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 796 filename = fmt("%s%s", prefix, f->suffix);
769 f->suffix);
770 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 797 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
771 html("<br/>"); 798 html("<br/>");
772 } 799 }
773} 800}
diff --git a/ui-shared.h b/ui-shared.h
index b12aa89..9ebc1f9 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -20,21 +20,22 @@ extern void cgit_tree_link(char *name, char *title, char *class, char *head,
20extern void cgit_plain_link(char *name, char *title, char *class, char *head, 20extern void cgit_plain_link(char *name, char *title, char *class, char *head,
21 char *rev, char *path); 21 char *rev, char *path);
22extern void cgit_log_link(char *name, char *title, char *class, char *head, 22extern void cgit_log_link(char *name, char *title, char *class, char *head,
23 char *rev, char *path, int ofs, char *grep, 23 char *rev, char *path, int ofs, char *grep,
24 char *pattern, int showmsg); 24 char *pattern, int showmsg);
25extern void cgit_commit_link(char *name, char *title, char *class, char *head, 25extern void cgit_commit_link(char *name, char *title, char *class, char *head,
26 char *rev); 26 char *rev, int toggle_ssdiff);
27extern void cgit_patch_link(char *name, char *title, char *class, char *head, 27extern void cgit_patch_link(char *name, char *title, char *class, char *head,
28 char *rev); 28 char *rev);
29extern void cgit_refs_link(char *name, char *title, char *class, char *head, 29extern void cgit_refs_link(char *name, char *title, char *class, char *head,
30 char *rev, char *path); 30 char *rev, char *path);
31extern void cgit_snapshot_link(char *name, char *title, char *class, 31extern void cgit_snapshot_link(char *name, char *title, char *class,
32 char *head, char *rev, char *archivename); 32 char *head, char *rev, char *archivename);
33extern void cgit_diff_link(char *name, char *title, char *class, char *head, 33extern void cgit_diff_link(char *name, char *title, char *class, char *head,
34 char *new_rev, char *old_rev, char *path); 34 char *new_rev, char *old_rev, char *path,
35 int toggle_ssdiff);
35extern void cgit_stats_link(char *name, char *title, char *class, char *head, 36extern void cgit_stats_link(char *name, char *title, char *class, char *head,
36 char *path); 37 char *path);
37extern void cgit_object_link(struct object *obj); 38extern void cgit_object_link(struct object *obj);
38 39
39extern void cgit_print_error(char *msg); 40extern void cgit_print_error(char *msg);
40extern void cgit_print_date(time_t secs, char *format, int local_time); 41extern void cgit_print_date(time_t secs, char *format, int local_time);
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 4136b3e..1b25dca 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -32,17 +32,23 @@ static int write_tar_gzip_archive(struct archiver_args *args)
32 32
33static int write_tar_bzip2_archive(struct archiver_args *args) 33static int write_tar_bzip2_archive(struct archiver_args *args)
34{ 34{
35 return write_compressed_tar_archive(args,"bzip2"); 35 return write_compressed_tar_archive(args,"bzip2");
36} 36}
37 37
38static int write_tar_xz_archive(struct archiver_args *args)
39{
40 return write_compressed_tar_archive(args,"xz");
41}
42
38const struct cgit_snapshot_format cgit_snapshot_formats[] = { 43const struct cgit_snapshot_format cgit_snapshot_formats[] = {
39 { ".zip", "application/x-zip", write_zip_archive, 0x1 }, 44 { ".zip", "application/x-zip", write_zip_archive, 0x01 },
40 { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x2 }, 45 { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x02 },
41 { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x4 }, 46 { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x04 },
42 { ".tar", "application/x-tar", write_tar_archive, 0x8 }, 47 { ".tar", "application/x-tar", write_tar_archive, 0x08 },
48 { ".tar.xz", "application/x-xz", write_tar_xz_archive, 0x10 },
43 {} 49 {}
44}; 50};
45 51
46static const struct cgit_snapshot_format *get_format(const char *filename) 52static const struct cgit_snapshot_format *get_format(const char *filename)
47{ 53{
48 const struct cgit_snapshot_format *fmt; 54 const struct cgit_snapshot_format *fmt;
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
new file mode 100644
index 0000000..408e620
--- a/dev/null
+++ b/ui-ssdiff.c
@@ -0,0 +1,369 @@
1#include "cgit.h"
2#include "html.h"
3#include "ui-shared.h"
4
5extern int use_ssdiff;
6
7static int current_old_line, current_new_line;
8
9struct deferred_lines {
10 int line_no;
11 char *line;
12 struct deferred_lines *next;
13};
14
15static struct deferred_lines *deferred_old, *deferred_old_last;
16static struct deferred_lines *deferred_new, *deferred_new_last;
17
18static char *longest_common_subsequence(char *A, char *B)
19{
20 int i, j, ri;
21 int m = strlen(A);
22 int n = strlen(B);
23 int L[m + 1][n + 1];
24 int tmp1, tmp2;
25 int lcs_length;
26 char *result;
27
28 for (i = m; i >= 0; i--) {
29 for (j = n; j >= 0; j--) {
30 if (A[i] == '\0' || B[j] == '\0') {
31 L[i][j] = 0;
32 } else if (A[i] == B[j]) {
33 L[i][j] = 1 + L[i + 1][j + 1];
34 } else {
35 tmp1 = L[i + 1][j];
36 tmp2 = L[i][j + 1];
37 L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2);
38 }
39 }
40 }
41
42 lcs_length = L[0][0];
43 result = xmalloc(lcs_length + 2);
44 memset(result, 0, sizeof(*result) * (lcs_length + 2));
45
46 ri = 0;
47 i = 0;
48 j = 0;
49 while (i < m && j < n) {
50 if (A[i] == B[j]) {
51 result[ri] = A[i];
52 ri += 1;
53 i += 1;
54 j += 1;
55 } else if (L[i + 1][j] >= L[i][j + 1]) {
56 i += 1;
57 } else {
58 j += 1;
59 }
60 }
61 return result;
62}
63
64static int line_from_hunk(char *line, char type)
65{
66 char *buf1, *buf2;
67 int len;
68
69 buf1 = strchr(line, type);
70 if (buf1 == NULL)
71 return 0;
72 buf1 += 1;
73 buf2 = strchr(buf1, ',');
74 if (buf2 == NULL)
75 return 0;
76 len = buf2 - buf1;
77 buf2 = xmalloc(len + 1);
78 strncpy(buf2, buf1, len);
79 buf2[len] = '\0';
80 int res = atoi(buf2);
81 free(buf2);
82 return res;
83}
84
85static char *replace_tabs(char *line)
86{
87 char *prev_buf = line;
88 char *cur_buf;
89 int linelen = strlen(line);
90 int n_tabs = 0;
91 int i;
92 char *result;
93 char *spaces = " ";
94
95 if (linelen == 0) {
96 result = xmalloc(1);
97 result[0] = '\0';
98 return result;
99 }
100
101 for (i = 0; i < linelen; i++)
102 if (line[i] == '\t')
103 n_tabs += 1;
104 result = xmalloc(linelen + n_tabs * 8 + 1);
105 result[0] = '\0';
106
107 while (1) {
108 cur_buf = strchr(prev_buf, '\t');
109 if (!cur_buf) {
110 strcat(result, prev_buf);
111 break;
112 } else {
113 strcat(result, " ");
114 strncat(result, spaces, 8 - (strlen(result) % 8));
115 strncat(result, prev_buf, cur_buf - prev_buf);
116 }
117 prev_buf = cur_buf + 1;
118 }
119 return result;
120}
121
122static int calc_deferred_lines(struct deferred_lines *start)
123{
124 struct deferred_lines *item = start;
125 int result = 0;
126 while (item) {
127 result += 1;
128 item = item->next;
129 }
130 return result;
131}
132
133static void deferred_old_add(char *line, int line_no)
134{
135 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
136 item->line = xstrdup(line);
137 item->line_no = line_no;
138 item->next = NULL;
139 if (deferred_old) {
140 deferred_old_last->next = item;
141 deferred_old_last = item;
142 } else {
143 deferred_old = deferred_old_last = item;
144 }
145}
146
147static void deferred_new_add(char *line, int line_no)
148{
149 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
150 item->line = xstrdup(line);
151 item->line_no = line_no;
152 item->next = NULL;
153 if (deferred_new) {
154 deferred_new_last->next = item;
155 deferred_new_last = item;
156 } else {
157 deferred_new = deferred_new_last = item;
158 }
159}
160
161static void print_part_with_lcs(char *class, char *line, char *lcs)
162{
163 int line_len = strlen(line);
164 int i, j;
165 char c[2] = " ";
166 int same = 1;
167
168 j = 0;
169 for (i = 0; i < line_len; i++) {
170 c[0] = line[i];
171 if (same) {
172 if (line[i] == lcs[j])
173 j += 1;
174 else {
175 same = 0;
176 htmlf("<span class='%s'>", class);
177 }
178 } else if (line[i] == lcs[j]) {
179 same = 1;
180 htmlf("</span>");
181 j += 1;
182 }
183 html_txt(c);
184 }
185}
186
187static void print_ssdiff_line(char *class,
188 int old_line_no,
189 char *old_line,
190 int new_line_no,
191 char *new_line, int individual_chars)
192{
193 char *lcs = NULL;
194 if (old_line)
195 old_line = replace_tabs(old_line + 1);
196 if (new_line)
197 new_line = replace_tabs(new_line + 1);
198 if (individual_chars && old_line && new_line)
199 lcs = longest_common_subsequence(old_line, new_line);
200 html("<tr>");
201 if (old_line_no > 0)
202 htmlf("<td class='lineno'>%d</td><td class='%s'>",
203 old_line_no, class);
204 else if (old_line)
205 htmlf("<td class='lineno'></td><td class='%s'>", class);
206 else
207 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
208 if (old_line) {
209 if (lcs)
210 print_part_with_lcs("del", old_line, lcs);
211 else
212 html_txt(old_line);
213 }
214
215 html("</td>");
216 if (new_line_no > 0)
217 htmlf("<td class='lineno'>%d</td><td class='%s'>",
218 new_line_no, class);
219 else if (new_line)
220 htmlf("<td class='lineno'></td><td class='%s'>", class);
221 else
222 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
223 if (new_line) {
224 if (lcs)
225 print_part_with_lcs("add", new_line, lcs);
226 else
227 html_txt(new_line);
228 }
229
230 html("</td></tr>");
231 if (lcs)
232 free(lcs);
233 if (new_line)
234 free(new_line);
235 if (old_line)
236 free(old_line);
237}
238
239static void print_deferred_old_lines()
240{
241 struct deferred_lines *iter_old, *tmp;
242 iter_old = deferred_old;
243 while (iter_old) {
244 print_ssdiff_line("del", iter_old->line_no,
245 iter_old->line, -1, NULL, 0);
246 tmp = iter_old->next;
247 free(iter_old);
248 iter_old = tmp;
249 }
250}
251
252static void print_deferred_new_lines()
253{
254 struct deferred_lines *iter_new, *tmp;
255 iter_new = deferred_new;
256 while (iter_new) {
257 print_ssdiff_line("add", -1, NULL,
258 iter_new->line_no, iter_new->line, 0);
259 tmp = iter_new->next;
260 free(iter_new);
261 iter_new = tmp;
262 }
263}
264
265static void print_deferred_changed_lines()
266{
267 struct deferred_lines *iter_old, *iter_new, *tmp;
268 int n_old_lines = calc_deferred_lines(deferred_old);
269 int n_new_lines = calc_deferred_lines(deferred_new);
270 int individual_chars = (n_old_lines == n_new_lines ? 1 : 0);
271
272 iter_old = deferred_old;
273 iter_new = deferred_new;
274 while (iter_old || iter_new) {
275 if (iter_old && iter_new)
276 print_ssdiff_line("changed", iter_old->line_no,
277 iter_old->line,
278 iter_new->line_no, iter_new->line,
279 individual_chars);
280 else if (iter_old)
281 print_ssdiff_line("changed", iter_old->line_no,
282 iter_old->line, -1, NULL, 0);
283 else if (iter_new)
284 print_ssdiff_line("changed", -1, NULL,
285 iter_new->line_no, iter_new->line, 0);
286 if (iter_old) {
287 tmp = iter_old->next;
288 free(iter_old);
289 iter_old = tmp;
290 }
291
292 if (iter_new) {
293 tmp = iter_new->next;
294 free(iter_new);
295 iter_new = tmp;
296 }
297 }
298}
299
300void cgit_ssdiff_print_deferred_lines()
301{
302 if (!deferred_old && !deferred_new)
303 return;
304 if (deferred_old && !deferred_new)
305 print_deferred_old_lines();
306 else if (!deferred_old && deferred_new)
307 print_deferred_new_lines();
308 else
309 print_deferred_changed_lines();
310 deferred_old = deferred_old_last = NULL;
311 deferred_new = deferred_new_last = NULL;
312}
313
314/*
315 * print a single line returned from xdiff
316 */
317void cgit_ssdiff_line_cb(char *line, int len)
318{
319 char c = line[len - 1];
320 line[len - 1] = '\0';
321 if (line[0] == '@') {
322 current_old_line = line_from_hunk(line, '-');
323 current_new_line = line_from_hunk(line, '+');
324 }
325
326 if (line[0] == ' ') {
327 if (deferred_old || deferred_new)
328 cgit_ssdiff_print_deferred_lines();
329 print_ssdiff_line("ctx", current_old_line, line,
330 current_new_line, line, 0);
331 current_old_line += 1;
332 current_new_line += 1;
333 } else if (line[0] == '+') {
334 deferred_new_add(line, current_new_line);
335 current_new_line += 1;
336 } else if (line[0] == '-') {
337 deferred_old_add(line, current_old_line);
338 current_old_line += 1;
339 } else if (line[0] == '@') {
340 html("<tr><td colspan='4' class='hunk'>");
341 html_txt(line);
342 html("</td></tr>");
343 } else {
344 html("<tr><td colspan='4' class='ctx'>");
345 html_txt(line);
346 html("</td></tr>");
347 }
348 line[len - 1] = c;
349}
350
351void cgit_ssdiff_header_begin()
352{
353 current_old_line = -1;
354 current_new_line = -1;
355 html("<tr><td class='space' colspan='4'><div></div></td></tr>");
356 html("<tr><td class='head' colspan='4'>");
357}
358
359void cgit_ssdiff_header_end()
360{
361 html("</td><tr>");
362}
363
364void cgit_ssdiff_footer()
365{
366 if (deferred_old || deferred_new)
367 cgit_ssdiff_print_deferred_lines();
368 html("<tr><td class='foot' colspan='4'></td></tr>");
369}
diff --git a/ui-ssdiff.h b/ui-ssdiff.h
new file mode 100644
index 0000000..64b4b12
--- a/dev/null
+++ b/ui-ssdiff.h
@@ -0,0 +1,13 @@
1#ifndef UI_SSDIFF_H
2#define UI_SSDIFF_H
3
4extern void cgit_ssdiff_print_deferred_lines();
5
6extern void cgit_ssdiff_line_cb(char *line, int len);
7
8extern void cgit_ssdiff_header_begin();
9extern void cgit_ssdiff_header_end();
10
11extern void cgit_ssdiff_footer();
12
13#endif /* UI_SSDIFF_H */
diff --git a/ui-tag.c b/ui-tag.c
index c2d72af..39e4cb8 100644
--- a/ui-tag.c
+++ b/ui-tag.c
@@ -27,12 +27,20 @@ static void print_tag_content(char *buf)
27 html("<div class='commit-msg'>"); 27 html("<div class='commit-msg'>");
28 html_txt(++p); 28 html_txt(++p);
29 html("</div>"); 29 html("</div>");
30 } 30 }
31} 31}
32 32
33void print_download_links(char *revname)
34{
35 html("<tr><th>download</th><td class='sha1'>");
36 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
37 revname, ctx.repo->snapshots);
38 html("</td></tr>");
39}
40
33void cgit_print_tag(char *revname) 41void cgit_print_tag(char *revname)
34{ 42{
35 unsigned char sha1[20]; 43 unsigned char sha1[20];
36 struct object *obj; 44 struct object *obj;
37 struct tag *tag; 45 struct tag *tag;
38 struct taginfo *info; 46 struct taginfo *info;
@@ -53,40 +61,44 @@ void cgit_print_tag(char *revname)
53 tag = lookup_tag(sha1); 61 tag = lookup_tag(sha1);
54 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) { 62 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) {
55 cgit_print_error(fmt("Bad tag object: %s", revname)); 63 cgit_print_error(fmt("Bad tag object: %s", revname));
56 return; 64 return;
57 } 65 }
58 html("<table class='commit-info'>\n"); 66 html("<table class='commit-info'>\n");
59 htmlf("<tr><td>Tag name</td><td>"); 67 htmlf("<tr><td>tag name</td><td>");
60 html_txt(revname); 68 html_txt(revname);
61 htmlf(" (%s)</td></tr>\n", sha1_to_hex(sha1)); 69 htmlf(" (%s)</td></tr>\n", sha1_to_hex(sha1));
62 if (info->tagger_date > 0) { 70 if (info->tagger_date > 0) {
63 html("<tr><td>Tag date</td><td>"); 71 html("<tr><td>tag date</td><td>");
64 cgit_print_date(info->tagger_date, FMT_LONGDATE, ctx.cfg.local_time); 72 cgit_print_date(info->tagger_date, FMT_LONGDATE, ctx.cfg.local_time);
65 html("</td></tr>\n"); 73 html("</td></tr>\n");
66 } 74 }
67 if (info->tagger) { 75 if (info->tagger) {
68 html("<tr><td>Tagged by</td><td>"); 76 html("<tr><td>tagged by</td><td>");
69 html_txt(info->tagger); 77 html_txt(info->tagger);
70 if (info->tagger_email && !ctx.cfg.noplainemail) { 78 if (info->tagger_email && !ctx.cfg.noplainemail) {
71 html(" "); 79 html(" ");
72 html_txt(info->tagger_email); 80 html_txt(info->tagger_email);
73 } 81 }
74 html("</td></tr>\n"); 82 html("</td></tr>\n");
75 } 83 }
76 html("<tr><td>Tagged object</td><td>"); 84 html("<tr><td>tagged object</td><td class='sha1'>");
77 cgit_object_link(tag->tagged); 85 cgit_object_link(tag->tagged);
78 html("</td></tr>\n"); 86 html("</td></tr>\n");
87 if (ctx.repo->snapshots)
88 print_download_links(revname);
79 html("</table>\n"); 89 html("</table>\n");
80 print_tag_content(info->msg); 90 print_tag_content(info->msg);
81 } else { 91 } else {
82 html("<table class='commit-info'>\n"); 92 html("<table class='commit-info'>\n");
83 htmlf("<tr><td>Tag name</td><td>"); 93 htmlf("<tr><td>tag name</td><td>");
84 html_txt(revname); 94 html_txt(revname);
85 html("</td></tr>\n"); 95 html("</td></tr>\n");
86 html("<tr><td>Tagged object</td><td>"); 96 html("<tr><td>Tagged object</td><td class='sha1'>");
87 cgit_object_link(obj); 97 cgit_object_link(obj);
88 html("</td></tr>\n"); 98 html("</td></tr>\n");
99 if (ctx.repo->snapshots)
100 print_download_links(revname);
89 html("</table>\n"); 101 html("</table>\n");
90 } 102 }
91 return; 103 return;
92} 104}
diff --git a/ui-tree.c b/ui-tree.c
index a164767..0ee38f2 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -104,12 +104,18 @@ static void print_object(const unsigned char *sha1, char *path, const char *base
104 104
105 html(" ("); 105 html(" (");
106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
107 curr_rev, path); 107 curr_rev, path);
108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1));
109 109
110 if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
111 htmlf("<div class='error'>blob size (%dKB) exceeds display size limit (%dKB).</div>",
112 size / 1024, ctx.cfg.max_blob_size);
113 return;
114 }
115
110 if (buffer_is_binary(buf, size)) 116 if (buffer_is_binary(buf, size))
111 print_binary_buffer(buf, size); 117 print_binary_buffer(buf, size);
112 else 118 else
113 print_text_buffer(basename, buf, size); 119 print_text_buffer(basename, buf, size);
114} 120}
115 121
@@ -166,12 +172,14 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
166 html("<td>"); 172 html("<td>");
167 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 173 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
168 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 174 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
169 if (ctx.repo->max_stats) 175 if (ctx.repo->max_stats)
170 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 176 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
171 fullpath); 177 fullpath);
178 cgit_plain_link("plain", NULL, "button", ctx.qry.head, curr_rev,
179 fullpath);
172 html("</td></tr>\n"); 180 html("</td></tr>\n");
173 free(name); 181 free(name);
174 return 0; 182 return 0;
175} 183}
176 184
177static void ls_head() 185static void ls_head()