aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile18
-rw-r--r--cgit.c31
-rw-r--r--cgit.css119
-rw-r--r--cgit.h13
-rw-r--r--cgitrc.5.txt33
-rw-r--r--cmd.c48
-rw-r--r--cmd.h3
-rwxr-xr-xfilters/syntax-highlighting.sh29
m---------git0
-rw-r--r--html.c70
-rw-r--r--html.h18
-rw-r--r--scan-tree.c2
-rw-r--r--shared.c8
-rw-r--r--ui-atom.c4
-rw-r--r--ui-commit.c31
-rw-r--r--ui-commit.h2
-rw-r--r--ui-diff.c82
-rw-r--r--ui-log.c40
-rw-r--r--ui-patch.c8
-rw-r--r--ui-patch.h2
-rw-r--r--ui-plain.c68
-rw-r--r--ui-refs.c4
-rw-r--r--ui-shared.c260
-rw-r--r--ui-shared.h71
-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.c23
29 files changed, 1148 insertions, 259 deletions
diff --git a/Makefile b/Makefile
index 1dc44d5..3a4d974 100644
--- a/Makefile
+++ b/Makefile
@@ -5,12 +5,15 @@ CGIT_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
@@ -68,7 +71,7 @@ endif
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
@@ -90,6 +93,7 @@ OBJECTS += 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
@@ -123,6 +127,12 @@ endif
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)
@@ -132,8 +142,8 @@ cgit.o: VERSION
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
diff --git a/cgit.c b/cgit.c
index 6c7e811..ab25b6a 100644
--- a/cgit.c
+++ b/cgit.c
@@ -60,6 +60,10 @@ void repo_config(struct cgit_repo *repo, const char *name, const char *value)
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);
65 else if (!strcmp(name, "enable-subject-links"))
66 repo->enable_subject_links = atoi(value);
63 else if (!strcmp(name, "max-stats")) 67 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL); 68 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link")) 69 else if (!strcmp(name, "module-link"))
@@ -137,6 +141,10 @@ void config_cb(const char *name, const char *value)
137 ctx.cfg.enable_log_filecount = atoi(value); 141 ctx.cfg.enable_log_filecount = atoi(value);
138 else if (!strcmp(name, "enable-log-linecount")) 142 else if (!strcmp(name, "enable-log-linecount"))
139 ctx.cfg.enable_log_linecount = atoi(value); 143 ctx.cfg.enable_log_linecount = atoi(value);
144 else if (!strcmp(name, "enable-remote-branches"))
145 ctx.cfg.enable_remote_branches = atoi(value);
146 else if (!strcmp(name, "enable-subject-links"))
147 ctx.cfg.enable_subject_links = atoi(value);
140 else if (!strcmp(name, "enable-tree-linenumbers")) 148 else if (!strcmp(name, "enable-tree-linenumbers"))
141 ctx.cfg.enable_tree_linenumbers = atoi(value); 149 ctx.cfg.enable_tree_linenumbers = atoi(value);
142 else if (!strcmp(name, "max-stats")) 150 else if (!strcmp(name, "max-stats"))
@@ -161,10 +169,14 @@ void config_cb(const char *name, const char *value)
161 ctx.cfg.commit_filter = new_filter(value, 0); 169 ctx.cfg.commit_filter = new_filter(value, 0);
162 else if (!strcmp(name, "embedded")) 170 else if (!strcmp(name, "embedded"))
163 ctx.cfg.embedded = atoi(value); 171 ctx.cfg.embedded = atoi(value);
172 else if (!strcmp(name, "max-atom-items"))
173 ctx.cfg.max_atom_items = atoi(value);
164 else if (!strcmp(name, "max-message-length")) 174 else if (!strcmp(name, "max-message-length"))
165 ctx.cfg.max_msg_len = atoi(value); 175 ctx.cfg.max_msg_len = atoi(value);
166 else if (!strcmp(name, "max-repodesc-length")) 176 else if (!strcmp(name, "max-repodesc-length"))
167 ctx.cfg.max_repodesc_len = atoi(value); 177 ctx.cfg.max_repodesc_len = atoi(value);
178 else if (!strcmp(name, "max-blob-size"))
179 ctx.cfg.max_blob_size = atoi(value);
168 else if (!strcmp(name, "max-repo-count")) 180 else if (!strcmp(name, "max-repo-count"))
169 ctx.cfg.max_repo_count = atoi(value); 181 ctx.cfg.max_repo_count = atoi(value);
170 else if (!strcmp(name, "max-commit-count")) 182 else if (!strcmp(name, "max-commit-count"))
@@ -182,6 +194,8 @@ void config_cb(const char *name, const char *value)
182 ctx.cfg.summary_branches = atoi(value); 194 ctx.cfg.summary_branches = atoi(value);
183 else if (!strcmp(name, "summary-tags")) 195 else if (!strcmp(name, "summary-tags"))
184 ctx.cfg.summary_tags = atoi(value); 196 ctx.cfg.summary_tags = atoi(value);
197 else if (!strcmp(name, "side-by-side-diffs"))
198 ctx.cfg.ssdiff = atoi(value);
185 else if (!strcmp(name, "agefile")) 199 else if (!strcmp(name, "agefile"))
186 ctx.cfg.agefile = xstrdup(value); 200 ctx.cfg.agefile = xstrdup(value);
187 else if (!strcmp(name, "renamelimit")) 201 else if (!strcmp(name, "renamelimit"))
@@ -209,6 +223,8 @@ static void querystring_cb(const char *name, const char *value)
209 } else if (!strcmp(name, "p")) { 223 } else if (!strcmp(name, "p")) {
210 ctx.qry.page = xstrdup(value); 224 ctx.qry.page = xstrdup(value);
211 } else if (!strcmp(name, "url")) { 225 } else if (!strcmp(name, "url")) {
226 if (*value == '/')
227 value++;
212 ctx.qry.url = xstrdup(value); 228 ctx.qry.url = xstrdup(value);
213 cgit_parse_url(value); 229 cgit_parse_url(value);
214 } else if (!strcmp(name, "qt")) { 230 } else if (!strcmp(name, "qt")) {
@@ -238,6 +254,12 @@ static void querystring_cb(const char *name, const char *value)
238 ctx.qry.showmsg = atoi(value); 254 ctx.qry.showmsg = atoi(value);
239 } else if (!strcmp(name, "period")) { 255 } else if (!strcmp(name, "period")) {
240 ctx.qry.period = xstrdup(value); 256 ctx.qry.period = xstrdup(value);
257 } else if (!strcmp(name, "ss")) {
258 ctx.qry.ssdiff = atoi(value);
259 } else if (!strcmp(name, "all")) {
260 ctx.qry.show_all = atoi(value);
261 } else if (!strcmp(name, "context")) {
262 ctx.qry.context = atoi(value);
241 } 263 }
242} 264}
243 265
@@ -268,6 +290,7 @@ static void prepare_context(struct cgit_context *ctx)
268 ctx->cfg.max_lock_attempts = 5; 290 ctx->cfg.max_lock_attempts = 5;
269 ctx->cfg.max_msg_len = 80; 291 ctx->cfg.max_msg_len = 80;
270 ctx->cfg.max_repodesc_len = 80; 292 ctx->cfg.max_repodesc_len = 80;
293 ctx->cfg.max_blob_size = 0;
271 ctx->cfg.max_stats = 0; 294 ctx->cfg.max_stats = 0;
272 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 295 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
273 ctx->cfg.renamelimit = -1; 296 ctx->cfg.renamelimit = -1;
@@ -279,6 +302,8 @@ static void prepare_context(struct cgit_context *ctx)
279 ctx->cfg.summary_branches = 10; 302 ctx->cfg.summary_branches = 10;
280 ctx->cfg.summary_log = 10; 303 ctx->cfg.summary_log = 10;
281 ctx->cfg.summary_tags = 10; 304 ctx->cfg.summary_tags = 10;
305 ctx->cfg.max_atom_items = 10;
306 ctx->cfg.ssdiff = 0;
282 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 307 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
283 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 308 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
284 ctx->env.https = xstrdupn(getenv("HTTPS")); 309 ctx->env.https = xstrdupn(getenv("HTTPS"));
@@ -410,6 +435,12 @@ static void process_request(void *cbdata)
410 return; 435 return;
411 } 436 }
412 437
438 /* If cmd->want_vpath is set, assume ctx->qry.path contains a "virtual"
439 * in-project path limit to be made available at ctx->qry.vpath.
440 * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL).
441 */
442 ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL;
443
413 if (cmd->want_repo && !ctx->repo) { 444 if (cmd->want_repo && !ctx->repo) {
414 cgit_print_http_headers(ctx); 445 cgit_print_http_headers(ctx);
415 cgit_print_docstart(ctx); 446 cgit_print_docstart(ctx);
diff --git a/cgit.css b/cgit.css
index c47ebc9..6e47eb3 100644
--- a/cgit.css
+++ b/cgit.css
@@ -64,7 +64,7 @@ table#header td.sub {
64} 64}
65 65
66table.tabs { 66table.tabs {
67 /* border-bottom: solid 2px #ccc; */ 67 border-bottom: solid 3px #ccc;
68 border-collapse: collapse; 68 border-collapse: collapse;
69 margin-top: 2em; 69 margin-top: 2em;
70 margin-bottom: 0px; 70 margin-bottom: 0px;
@@ -102,10 +102,16 @@ table.tabs td.form select {
102 font-size: 90%; 102 font-size: 90%;
103} 103}
104 104
105div.path {
106 margin: 0px;
107 padding: 5px 2em 2px 2em;
108 color: #000;
109 background-color: #eee;
110}
111
105div.content { 112div.content {
106 margin: 0px; 113 margin: 0px;
107 padding: 2em; 114 padding: 2em;
108 border-top: solid 3px #ccc;
109 border-bottom: solid 3px #ccc; 115 border-bottom: solid 3px #ccc;
110} 116}
111 117
@@ -162,6 +168,11 @@ table.list td a {
162 color: black; 168 color: black;
163} 169}
164 170
171table.list td a.ls-dir {
172 font-weight: bold;
173 color: #00f;
174}
175
165table.list td a:hover { 176table.list td a:hover {
166 color: #00f; 177 color: #00f;
167} 178}
@@ -520,7 +531,10 @@ a.deco {
520 border: solid 1px #770000; 531 border: solid 1px #770000;
521} 532}
522 533
523div.commit-subject a { 534div.commit-subject a.branch-deco,
535div.commit-subject a.tag-deco,
536div.commit-subject a.remote-deco,
537div.commit-subject a.deco {
524 margin-left: 1em; 538 margin-left: 1em;
525 font-size: 75%; 539 font-size: 75%;
526} 540}
@@ -601,3 +615,102 @@ table.hgraph div.bar {
601 background-color: #eee; 615 background-color: #eee;
602 height: 1em; 616 height: 1em;
603} 617}
618
619table.ssdiff {
620 width: 100%;
621}
622
623table.ssdiff td {
624 font-size: 75%;
625 font-family: monospace;
626 white-space: pre;
627 padding: 1px 4px 1px 4px;
628 border-left: solid 1px #aaa;
629 border-right: solid 1px #aaa;
630}
631
632table.ssdiff td.add {
633 color: black;
634 background: #cfc;
635 min-width: 50%;
636}
637
638table.ssdiff td.add_dark {
639 color: black;
640 background: #aca;
641 min-width: 50%;
642}
643
644table.ssdiff span.add {
645 background: #cfc;
646 font-weight: bold;
647}
648
649table.ssdiff td.del {
650 color: black;
651 background: #fcc;
652 min-width: 50%;
653}
654
655table.ssdiff td.del_dark {
656 color: black;
657 background: #caa;
658 min-width: 50%;
659}
660
661table.ssdiff span.del {
662 background: #fcc;
663 font-weight: bold;
664}
665
666table.ssdiff td.changed {
667 color: black;
668 background: #ffc;
669 min-width: 50%;
670}
671
672table.ssdiff td.changed_dark {
673 color: black;
674 background: #cca;
675 min-width: 50%;
676}
677
678table.ssdiff td.lineno {
679 color: black;
680 background: #eee;
681 text-align: right;
682 width: 3em;
683 min-width: 3em;
684}
685
686table.ssdiff td.hunk {
687 color: #black;
688 background: #ccf;
689 border-top: solid 1px #aaa;
690 border-bottom: solid 1px #aaa;
691}
692
693table.ssdiff td.head {
694 border-top: solid 1px #aaa;
695 border-bottom: solid 1px #aaa;
696}
697
698table.ssdiff td.head div.head {
699 font-weight: bold;
700 color: black;
701}
702
703table.ssdiff td.foot {
704 border-top: solid 1px #aaa;
705 border-left: none;
706 border-right: none;
707 border-bottom: none;
708}
709
710table.ssdiff td.space {
711 border: none;
712}
713
714table.ssdiff td.space div {
715 min-height: 3em;
716} \ No newline at end of file
diff --git a/cgit.h b/cgit.h
index 6c6c460..2b28d63 100644
--- a/cgit.h
+++ b/cgit.h
@@ -72,6 +72,8 @@ struct cgit_repo {
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;
76 int enable_subject_links;
75 int max_stats; 77 int max_stats;
76 time_t mtime; 78 time_t mtime;
77 struct cgit_filter *about_filter; 79 struct cgit_filter *about_filter;
@@ -143,6 +145,10 @@ struct cgit_query {
143 int nohead; 145 int nohead;
144 char *sort; 146 char *sort;
145 int showmsg; 147 int showmsg;
148 int ssdiff;
149 int show_all;
150 int context;
151 char *vpath;
146}; 152};
147 153
148struct cgit_config { 154struct cgit_config {
@@ -178,13 +184,17 @@ struct cgit_config {
178 int enable_index_links; 184 int enable_index_links;
179 int enable_log_filecount; 185 int enable_log_filecount;
180 int enable_log_linecount; 186 int enable_log_linecount;
187 int enable_remote_branches;
188 int enable_subject_links;
181 int enable_tree_linenumbers; 189 int enable_tree_linenumbers;
182 int local_time; 190 int local_time;
191 int max_atom_items;
183 int max_repo_count; 192 int max_repo_count;
184 int max_commit_count; 193 int max_commit_count;
185 int max_lock_attempts; 194 int max_lock_attempts;
186 int max_msg_len; 195 int max_msg_len;
187 int max_repodesc_len; 196 int max_repodesc_len;
197 int max_blob_size;
188 int max_stats; 198 int max_stats;
189 int nocache; 199 int nocache;
190 int noplainemail; 200 int noplainemail;
@@ -194,6 +204,7 @@ struct cgit_config {
194 int summary_branches; 204 int summary_branches;
195 int summary_log; 205 int summary_log;
196 int summary_tags; 206 int summary_tags;
207 int ssdiff;
197 struct string_list mimetypes; 208 struct string_list mimetypes;
198 struct cgit_filter *about_filter; 209 struct cgit_filter *about_filter;
199 struct cgit_filter *commit_filter; 210 struct cgit_filter *commit_filter;
@@ -268,7 +279,7 @@ extern void *cgit_free_commitinfo(struct commitinfo *info);
268extern int cgit_diff_files(const unsigned char *old_sha1, 279extern int cgit_diff_files(const unsigned char *old_sha1,
269 const unsigned char *new_sha1, 280 const unsigned char *new_sha1,
270 unsigned long *old_size, unsigned long *new_size, 281 unsigned long *old_size, unsigned long *new_size,
271 int *binary, linediff_fn fn); 282 int *binary, int context, linediff_fn fn);
272 283
273extern void cgit_diff_tree(const unsigned char *old_sha1, 284extern void cgit_diff_tree(const unsigned char *old_sha1,
274 const unsigned char *new_sha1, 285 const unsigned char *new_sha1,
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 0c13485..a853522 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -110,6 +110,17 @@ enable-log-linecount::
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
118enable-subject-links::
119 Flag which, when set to "1", will make cgit use the subject of the
120 parent commit as link text when generating links to parent commits
121 in commit view. Default value: "0". See also:
122 "repo.enable-subject-links".
123
113enable-tree-linenumbers:: 124enable-tree-linenumbers::
114 Flag which, when set to "1", will make cgit generate linenumber links 125 Flag which, when set to "1", will make cgit generate linenumber links
115 for plaintext blobs printed in the tree view. Default value: "1". 126 for plaintext blobs printed in the tree view. Default value: "1".
@@ -161,6 +172,10 @@ logo-link::
161 calculated url of the repository index page will be used. Default 172 calculated url of the repository index page will be used. Default
162 value: none. 173 value: none.
163 174
175max-atom-items::
176 Specifies the number of items to display in atom feeds view. Default
177 value: "10".
178
164max-commit-count:: 179max-commit-count::
165 Specifies the number of entries to list per page in "log" view. Default 180 Specifies the number of entries to list per page in "log" view. Default
166 value: "50". 181 value: "50".
@@ -177,6 +192,10 @@ max-repodesc-length::
177 Specifies the maximum number of repo description characters to display 192 Specifies the maximum number of repo description characters to display
178 on the repository index page. Default value: "80". 193 on the repository index page. Default value: "80".
179 194
195max-blob-size::
196 Specifies the maximum size of a blob to display HTML for in KBytes.
197 Default value: "0" (limit disabled).
198
180max-stats:: 199max-stats::
181 Set the default maximum statistics period. Valid values are "week", 200 Set the default maximum statistics period. Valid values are "week",
182 "month", "quarter" and "year". If unspecified, statistics are 201 "month", "quarter" and "year". If unspecified, statistics are
@@ -241,6 +260,10 @@ section::
241 after this option will inherit the current section name. Default value: 260 after this option will inherit the current section name. Default value:
242 none. 261 none.
243 262
263side-by-side-diffs::
264 If set to "1" shows side-by-side diffs instead of unidiffs per
265 default. Default value: "0".
266
244snapshots:: 267snapshots::
245 Text which specifies the default set of snapshot formats generated by 268 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 269 cgit. The value is a space-separated list of zero or more of the
@@ -304,6 +327,14 @@ repo.enable-log-linecount::
304 A flag which can be used to disable the global setting 327 A flag which can be used to disable the global setting
305 `enable-log-linecount'. Default value: none. 328 `enable-log-linecount'. Default value: none.
306 329
330repo.enable-remote-branches::
331 Flag which, when set to "1", will make cgit display remote branches
332 in the summary and refs views. Default value: <enable-remote-branches>.
333
334repo.enable-subject-links::
335 A flag which can be used to override the global setting
336 `enable-subject-links'. Default value: none.
337
307repo.max-stats:: 338repo.max-stats::
308 Override the default maximum statistics period. Valid values are equal 339 Override the default maximum statistics period. Valid values are equal
309 to the values specified for the global "max-stats" setting. Default 340 to the values specified for the global "max-stats" setting. Default
@@ -413,7 +444,7 @@ snapshots=tar.gz tar.bz2 zip
413## List of common mimetypes 444## List of common mimetypes
414## 445##
415 446
416mimetype.git=image/git 447mimetype.gif=image/gif
417mimetype.html=text/html 448mimetype.html=text/html
418mimetype.jpg=image/jpeg 449mimetype.jpg=image/jpeg
419mimetype.jpeg=image/jpeg 450mimetype.jpeg=image/jpeg
diff --git a/cmd.c b/cmd.c
index 766f903..6dc9f5e 100644
--- a/cmd.c
+++ b/cmd.c
@@ -33,7 +33,7 @@ static void HEAD_fn(struct cgit_context *ctx)
33 33
34static void atom_fn(struct cgit_context *ctx) 34static void atom_fn(struct cgit_context *ctx)
35{ 35{
36 cgit_print_atom(ctx->qry.head, ctx->qry.path, 10); 36 cgit_print_atom(ctx->qry.head, ctx->qry.path, ctx->cfg.max_atom_items);
37} 37}
38 38
39static void about_fn(struct cgit_context *ctx) 39static void about_fn(struct cgit_context *ctx)
@@ -51,7 +51,7 @@ static void blob_fn(struct cgit_context *ctx)
51 51
52static void commit_fn(struct cgit_context *ctx) 52static void commit_fn(struct cgit_context *ctx)
53{ 53{
54 cgit_print_commit(ctx->qry.sha1); 54 cgit_print_commit(ctx->qry.sha1, ctx->qry.path);
55} 55}
56 56
57static void diff_fn(struct cgit_context *ctx) 57static void diff_fn(struct cgit_context *ctx)
@@ -90,7 +90,7 @@ static void repolist_fn(struct cgit_context *ctx)
90 90
91static void patch_fn(struct cgit_context *ctx) 91static void patch_fn(struct cgit_context *ctx)
92{ 92{
93 cgit_print_patch(ctx->qry.sha1); 93 cgit_print_patch(ctx->qry.sha1, ctx->qry.path);
94} 94}
95 95
96static void plain_fn(struct cgit_context *ctx) 96static void plain_fn(struct cgit_context *ctx)
@@ -129,31 +129,31 @@ static void tree_fn(struct cgit_context *ctx)
129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path); 129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
130} 130}
131 131
132#define def_cmd(name, want_repo, want_layout) \ 132#define def_cmd(name, want_repo, want_layout, want_vpath) \
133 {#name, name##_fn, want_repo, want_layout} 133 {#name, name##_fn, want_repo, want_layout, want_vpath}
134 134
135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) 135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
136{ 136{
137 static struct cgit_cmd cmds[] = { 137 static struct cgit_cmd cmds[] = {
138 def_cmd(HEAD, 1, 0), 138 def_cmd(HEAD, 1, 0, 0),
139 def_cmd(atom, 1, 0), 139 def_cmd(atom, 1, 0, 0),
140 def_cmd(about, 0, 1), 140 def_cmd(about, 0, 1, 0),
141 def_cmd(blob, 1, 0), 141 def_cmd(blob, 1, 0, 0),
142 def_cmd(commit, 1, 1), 142 def_cmd(commit, 1, 1, 1),
143 def_cmd(diff, 1, 1), 143 def_cmd(diff, 1, 1, 1),
144 def_cmd(info, 1, 0), 144 def_cmd(info, 1, 0, 0),
145 def_cmd(log, 1, 1), 145 def_cmd(log, 1, 1, 1),
146 def_cmd(ls_cache, 0, 0), 146 def_cmd(ls_cache, 0, 0, 0),
147 def_cmd(objects, 1, 0), 147 def_cmd(objects, 1, 0, 0),
148 def_cmd(patch, 1, 0), 148 def_cmd(patch, 1, 0, 1),
149 def_cmd(plain, 1, 0), 149 def_cmd(plain, 1, 0, 0),
150 def_cmd(refs, 1, 1), 150 def_cmd(refs, 1, 1, 0),
151 def_cmd(repolist, 0, 0), 151 def_cmd(repolist, 0, 0, 0),
152 def_cmd(snapshot, 1, 0), 152 def_cmd(snapshot, 1, 0, 0),
153 def_cmd(stats, 1, 1), 153 def_cmd(stats, 1, 1, 1),
154 def_cmd(summary, 1, 1), 154 def_cmd(summary, 1, 1, 0),
155 def_cmd(tag, 1, 1), 155 def_cmd(tag, 1, 1, 0),
156 def_cmd(tree, 1, 1), 156 def_cmd(tree, 1, 1, 1),
157 }; 157 };
158 int i; 158 int i;
159 159
diff --git a/cmd.h b/cmd.h
index ec9e691..8dc01bd 100644
--- a/cmd.h
+++ b/cmd.h
@@ -7,7 +7,8 @@ struct cgit_cmd {
7 const char *name; 7 const char *name;
8 cgit_cmd_fn fn; 8 cgit_cmd_fn fn;
9 unsigned int want_repo:1, 9 unsigned int want_repo:1,
10 want_layout:1; 10 want_layout:1,
11 want_vpath:1;
11}; 12};
12 13
13extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx); 14extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx);
diff --git a/filters/syntax-highlighting.sh b/filters/syntax-highlighting.sh
index 999ad0c..6b1c576 100755
--- a/filters/syntax-highlighting.sh
+++ b/filters/syntax-highlighting.sh
@@ -3,6 +3,10 @@
3# tree-view by refering to this file with the source-filter or repo.source- 3# tree-view by refering to this file with the source-filter or repo.source-
4# filter options in cgitrc. 4# filter options in cgitrc.
5# 5#
6# This script requires a shell supporting the ${var##pattern} syntax.
7# It is supported by at least dash and bash, however busybox environments
8# might have to use an external call to sed instead.
9#
6# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax 10# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
7# highlighting, so you'll probably want something like the following included 11# highlighting, so you'll probably want something like the following included
8# in your css file (generated by highlight 2.4.8 and adapted for cgit): 12# in your css file (generated by highlight 2.4.8 and adapted for cgit):
@@ -20,20 +24,11 @@
20# table.blob .kwc { color:#000000; font-weight:bold; } 24# table.blob .kwc { color:#000000; font-weight:bold; }
21# table.blob .kwd { color:#010181; } 25# table.blob .kwd { color:#010181; }
22 26
23case "$1" in 27# store filename and extension in local vars
24 *.c) 28BASENAME="$1"
25 highlight -f -I -X -S c 29EXTENSION="${BASENAME##*.}"
26 ;; 30
27 *.h) 31# map Makefile and Makefile.* to .mk
28 highlight -f -I -X -S c 32[ "${BASENAME%%.*}" == "Makefile" ] && EXTENSION=mk
29 ;; 33
30 *.sh) 34exec highlight --force -f -I -X -S $EXTENSION 2>/dev/null
31 highlight -f -I -X -S sh
32 ;;
33 *.css)
34 highlight -f -I -X -S css
35 ;;
36 *)
37 highlight -f -I -X -S txt
38 ;;
39esac
diff --git a/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
@@ -13,6 +13,32 @@
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, ...)
@@ -63,9 +89,9 @@ void html_status(int code, const char *msg, int 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=='&') {
@@ -84,9 +110,9 @@ void html_txt(char *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=='&') {
@@ -107,9 +133,9 @@ void html_ntxt(int len, char *txt)
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=='\"') {
@@ -130,14 +156,15 @@ void html_attr(char *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++;
@@ -146,14 +173,15 @@ void html_url_path(char *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++;
@@ -162,7 +190,7 @@ void html_url_arg(char *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);
@@ -171,7 +199,7 @@ void html_hidden(char *name, char *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);
@@ -183,7 +211,7 @@ void html_option(char *value, char *text, char *selected_value)
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);
@@ -257,14 +285,14 @@ char *convert_query_hexchar(char *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);
diff --git a/html.h b/html.h
index a55d4b2..16d55ec 100644
--- a/html.h
+++ b/html.h
@@ -7,18 +7,18 @@ extern 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/scan-tree.c b/scan-tree.c
index dbca797..1e18f3c 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -56,6 +56,8 @@ static void add_repo(const char *base, const char *path, repo_config_fn fn)
56 path, strerror(errno), errno); 56 path, strerror(errno), errno);
57 return; 57 return;
58 } 58 }
59 if (!stat(fmt("%s/noweb", path), &st))
60 return;
59 if ((pwd = getpwuid(st.st_uid)) == NULL) { 61 if ((pwd = getpwuid(st.st_uid)) == NULL) {
60 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", 62 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
61 path, strerror(errno), errno); 63 path, strerror(errno), errno);
diff --git a/shared.c b/shared.c
index 6adf2b6..06f70bb 100644
--- a/shared.c
+++ b/shared.c
@@ -10,7 +10,6 @@
10 10
11struct cgit_repolist cgit_repolist; 11struct cgit_repolist cgit_repolist;
12struct cgit_context ctx; 12struct cgit_context ctx;
13int cgit_cmd;
14 13
15int chk_zero(int result, char *msg) 14int chk_zero(int result, char *msg)
16{ 15{
@@ -59,6 +58,8 @@ struct cgit_repo *cgit_add_repo(const char *url)
59 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
62 ret->enable_subject_links = ctx.cfg.enable_subject_links;
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;
@@ -262,7 +263,8 @@ int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
262 263
263int cgit_diff_files(const unsigned char *old_sha1, 264int cgit_diff_files(const unsigned char *old_sha1,
264 const unsigned char *new_sha1, unsigned long *old_size, 265 const unsigned char *new_sha1, unsigned long *old_size,
265 unsigned long *new_size, int *binary, linediff_fn fn) 266 unsigned long *new_size, int *binary, int context,
267 linediff_fn fn)
266{ 268{
267 mmfile_t file1, file2; 269 mmfile_t file1, file2;
268 xpparam_t diff_params; 270 xpparam_t diff_params;
@@ -289,7 +291,7 @@ int cgit_diff_files(const unsigned char *old_sha1,
289 memset(&emit_params, 0, sizeof(emit_params)); 291 memset(&emit_params, 0, sizeof(emit_params));
290 memset(&emit_cb, 0, sizeof(emit_cb)); 292 memset(&emit_cb, 0, sizeof(emit_cb));
291 diff_params.flags = XDF_NEED_MINIMAL; 293 diff_params.flags = XDF_NEED_MINIMAL;
292 emit_params.ctxlen = 3; 294 emit_params.ctxlen = context > 0 ? context : 3;
293 emit_params.flags = XDL_EMIT_FUNCNAMES; 295 emit_params.flags = XDL_EMIT_FUNCNAMES;
294 emit_cb.outf = filediff_cb; 296 emit_cb.outf = filediff_cb;
295 emit_cb.priv = fn; 297 emit_cb.priv = fn;
diff --git a/ui-atom.c b/ui-atom.c
index 808b2d0..9f049ae 100644
--- a/ui-atom.c
+++ b/ui-atom.c
@@ -85,7 +85,9 @@ void cgit_print_atom(char *tip, char *path, int max_count)
85 struct rev_info rev; 85 struct rev_info rev;
86 int argc = 2; 86 int argc = 2;
87 87
88 if (!tip) 88 if (ctx.qry.show_all)
89 argv[1] = "--all";
90 else if (!tip)
89 argv[1] = ctx.qry.head; 91 argv[1] = ctx.qry.head;
90 92
91 if (path) { 93 if (path) {
diff --git a/ui-commit.c b/ui-commit.c
index f5b0ae5..a11bc5f 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -12,13 +12,13 @@
12#include "ui-diff.h" 12#include "ui-diff.h"
13#include "ui-log.h" 13#include "ui-log.h"
14 14
15void cgit_print_commit(char *hex) 15void cgit_print_commit(char *hex, const char *prefix)
16{ 16{
17 struct commit *commit, *parent; 17 struct commit *commit, *parent;
18 struct commitinfo *info; 18 struct commitinfo *info, *parent_info;
19 struct commit_list *p; 19 struct commit_list *p;
20 unsigned char sha1[20]; 20 unsigned char sha1[20];
21 char *tmp; 21 char *tmp, *tmp2;
22 int parents = 0; 22 int parents = 0;
23 23
24 if (!hex) 24 if (!hex)
@@ -58,14 +58,23 @@ void cgit_print_commit(char *hex)
58 html("</td></tr>\n"); 58 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 59 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 60 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); 61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
62 html(" ("); 62 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 63 cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix);
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, prefix, 1);
67 else
68 cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, prefix, 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);
74 if (prefix) {
75 html(" /");
76 cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix);
77 }
69 html("</td></tr>\n"); 78 html("</td></tr>\n");
70 for (p = commit->parents; p ; p = p->next) { 79 for (p = commit->parents; p ; p = p->next) {
71 parent = lookup_commit_reference(p->item->object.sha1); 80 parent = lookup_commit_reference(p->item->object.sha1);
@@ -77,11 +86,15 @@ void cgit_print_commit(char *hex)
77 } 86 }
78 html("<tr><th>parent</th>" 87 html("<tr><th>parent</th>"
79 "<td colspan='2' class='sha1'>"); 88 "<td colspan='2' class='sha1'>");
80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 89 tmp = tmp2 = sha1_to_hex(p->item->object.sha1);
81 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 90 if (ctx.repo->enable_subject_links) {
91 parent_info = cgit_parse_commit(parent);
92 tmp2 = parent_info->subject;
93 }
94 cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix, 0);
82 html(" ("); 95 html(" (");
83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 96 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
84 sha1_to_hex(p->item->object.sha1), NULL); 97 sha1_to_hex(p->item->object.sha1), prefix, 0);
85 html(")</td></tr>"); 98 html(")</td></tr>");
86 parents++; 99 parents++;
87 } 100 }
@@ -112,7 +125,7 @@ void cgit_print_commit(char *hex)
112 tmp = sha1_to_hex(commit->parents->item->object.sha1); 125 tmp = sha1_to_hex(commit->parents->item->object.sha1);
113 else 126 else
114 tmp = NULL; 127 tmp = NULL;
115 cgit_print_diff(ctx.qry.sha1, tmp, NULL); 128 cgit_print_diff(ctx.qry.sha1, tmp, prefix);
116 } 129 }
117 cgit_free_commitinfo(info); 130 cgit_free_commitinfo(info);
118} 131}
diff --git a/ui-commit.h b/ui-commit.h
index 40bcb31..8198b4b 100644
--- a/ui-commit.h
+++ b/ui-commit.h
@@ -1,6 +1,6 @@
1#ifndef UI_COMMIT_H 1#ifndef UI_COMMIT_H
2#define UI_COMMIT_H 2#define UI_COMMIT_H
3 3
4extern void cgit_print_commit(char *hex); 4extern void cgit_print_commit(char *hex, const char *prefix);
5 5
6#endif /* UI_COMMIT_H */ 6#endif /* UI_COMMIT_H */
diff --git a/ui-diff.c b/ui-diff.c
index 2196745..e0a72f7 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -9,6 +9,7 @@
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12#include "ui-ssdiff.h"
12 13
13unsigned char old_rev_sha1[20]; 14unsigned char old_rev_sha1[20];
14unsigned char new_rev_sha1[20]; 15unsigned char new_rev_sha1[20];
@@ -32,6 +33,7 @@ static struct fileinfo {
32 int binary:1; 33 int binary:1;
33} *items; 34} *items;
34 35
36static int use_ssdiff = 0;
35 37
36static void print_fileinfo(struct fileinfo *info) 38static void print_fileinfo(struct fileinfo *info)
37{ 39{
@@ -83,7 +85,7 @@ static void print_fileinfo(struct fileinfo *info)
83 } 85 }
84 htmlf("</td><td class='%s'>", class); 86 htmlf("</td><td class='%s'>", class);
85 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, 87 cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
86 ctx.qry.sha2, info->new_path); 88 ctx.qry.sha2, info->new_path, 0);
87 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) 89 if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED)
88 htmlf(" (%s from %s)", 90 htmlf(" (%s from %s)",
89 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", 91 info->status == DIFF_STATUS_COPIED ? "copied" : "renamed",
@@ -125,7 +127,7 @@ static void inspect_filepair(struct diff_filepair *pair)
125 lines_added = 0; 127 lines_added = 0;
126 lines_removed = 0; 128 lines_removed = 0;
127 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, 129 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size,
128 &binary, count_diff_lines); 130 &binary, 0, count_diff_lines);
129 if (files >= slots) { 131 if (files >= slots) {
130 if (slots == 0) 132 if (slots == 0)
131 slots = 4; 133 slots = 4;
@@ -152,17 +154,27 @@ static void inspect_filepair(struct diff_filepair *pair)
152} 154}
153 155
154void cgit_print_diffstat(const unsigned char *old_sha1, 156void cgit_print_diffstat(const unsigned char *old_sha1,
155 const unsigned char *new_sha1) 157 const unsigned char *new_sha1, const char *prefix)
156{ 158{
157 int i; 159 int i, save_context = ctx.qry.context;
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);
164 if (prefix)
165 htmlf(" (limited to '%s')", prefix);
166 html(" (");
167 ctx.qry.context = (save_context > 0 ? save_context : 3) << 1;
168 cgit_self_link("more", NULL, NULL, &ctx);
169 html("/");
170 ctx.qry.context = (save_context > 3 ? save_context : 3) >> 1;
171 cgit_self_link("less", NULL, NULL, &ctx);
172 ctx.qry.context = save_context;
173 html(" context)");
162 html("</div>"); 174 html("</div>");
163 html("<table summary='diffstat' class='diffstat'>"); 175 html("<table summary='diffstat' class='diffstat'>");
164 max_changes = 0; 176 max_changes = 0;
165 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); 177 cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix);
166 for(i = 0; i<files; i++) 178 for(i = 0; i<files; i++)
167 print_fileinfo(&items[i]); 179 print_fileinfo(&items[i]);
168 html("</table>"); 180 html("</table>");
@@ -246,26 +258,54 @@ static void header(unsigned char *sha1, char *path1, int mode1,
246 html("</div>"); 258 html("</div>");
247} 259}
248 260
261static void print_ssdiff_link()
262{
263 if (!strcmp(ctx.qry.page, "diff")) {
264 if (use_ssdiff)
265 cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head,
266 ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1);
267 else
268 cgit_diff_link("Side-by-side diff", NULL, NULL,
269 ctx.qry.head, ctx.qry.sha1,
270 ctx.qry.sha2, ctx.qry.path, 1);
271 }
272}
273
249static void filepair_cb(struct diff_filepair *pair) 274static void filepair_cb(struct diff_filepair *pair)
250{ 275{
251 unsigned long old_size = 0; 276 unsigned long old_size = 0;
252 unsigned long new_size = 0; 277 unsigned long new_size = 0;
253 int binary = 0; 278 int binary = 0;
279 linediff_fn print_line_fn = print_line;
254 280
281 if (use_ssdiff) {
282 cgit_ssdiff_header_begin();
283 print_line_fn = cgit_ssdiff_line_cb;
284 }
255 header(pair->one->sha1, pair->one->path, pair->one->mode, 285 header(pair->one->sha1, pair->one->path, pair->one->mode,
256 pair->two->sha1, pair->two->path, pair->two->mode); 286 pair->two->sha1, pair->two->path, pair->two->mode);
287 if (use_ssdiff)
288 cgit_ssdiff_header_end();
257 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { 289 if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
258 if (S_ISGITLINK(pair->one->mode)) 290 if (S_ISGITLINK(pair->one->mode))
259 print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); 291 print_line_fn(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52);
260 if (S_ISGITLINK(pair->two->mode)) 292 if (S_ISGITLINK(pair->two->mode))
261 print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); 293 print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52);
294 if (use_ssdiff)
295 cgit_ssdiff_footer();
262 return; 296 return;
263 } 297 }
264 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 298 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
265 &new_size, &binary, print_line)) 299 &new_size, &binary, ctx.qry.context, print_line_fn))
266 cgit_print_error("Error running diff"); 300 cgit_print_error("Error running diff");
267 if (binary) 301 if (binary) {
268 html("Binary files differ"); 302 if (use_ssdiff)
303 html("<tr><td colspan='4'>Binary files differ</td></tr>");
304 else
305 html("Binary files differ");
306 }
307 if (use_ssdiff)
308 cgit_ssdiff_footer();
269} 309}
270 310
271void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) 311void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix)
@@ -303,11 +343,21 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefi
303 if (!commit2 || parse_commit(commit2)) 343 if (!commit2 || parse_commit(commit2))
304 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); 344 cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1)));
305 } 345 }
306 cgit_print_diffstat(old_rev_sha1, new_rev_sha1);
307 346
308 html("<table summary='diff' class='diff'>"); 347 if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff))
309 html("<tr><td>"); 348 use_ssdiff = 1;
349
350 print_ssdiff_link();
351 cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix);
352
353 if (use_ssdiff) {
354 html("<table summary='ssdiff' class='ssdiff'>");
355 } else {
356 html("<table summary='diff' class='diff'>");
357 html("<tr><td>");
358 }
310 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); 359 cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix);
311 html("</td></tr>"); 360 if (!use_ssdiff)
361 html("</td></tr>");
312 html("</table>"); 362 html("</table>");
313} 363}
diff --git a/ui-log.c b/ui-log.c
index f3132c9..33ec8a9 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -33,7 +33,7 @@ void inspect_files(struct diff_filepair *pair)
33 files++; 33 files++;
34 if (ctx.repo->enable_log_linecount) 34 if (ctx.repo->enable_log_linecount)
35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
36 &new_size, &binary, count_lines); 36 &new_size, &binary, 0, count_lines);
37} 37}
38 38
39void show_commit_decorations(struct commit *commit) 39void show_commit_decorations(struct commit *commit)
@@ -46,8 +46,9 @@ void show_commit_decorations(struct commit *commit)
46 while (deco) { 46 while (deco) {
47 if (!prefixcmp(deco->name, "refs/heads/")) { 47 if (!prefixcmp(deco->name, "refs/heads/")) {
48 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 48 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, 49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
50 0, NULL, NULL, ctx.qry.showmsg); 50 ctx.qry.vpath, 0, NULL, NULL,
51 ctx.qry.showmsg);
51 } 52 }
52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 53 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
53 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 54 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
@@ -60,13 +61,15 @@ void show_commit_decorations(struct commit *commit)
60 else if (!prefixcmp(deco->name, "refs/remotes/")) { 61 else if (!prefixcmp(deco->name, "refs/remotes/")) {
61 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 62 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
62 cgit_log_link(buf, NULL, "remote-deco", NULL, 63 cgit_log_link(buf, NULL, "remote-deco", NULL,
63 sha1_to_hex(commit->object.sha1), NULL, 64 sha1_to_hex(commit->object.sha1),
64 0, NULL, NULL, ctx.qry.showmsg); 65 ctx.qry.vpath, 0, NULL, NULL,
66 ctx.qry.showmsg);
65 } 67 }
66 else { 68 else {
67 strncpy(buf, deco->name, sizeof(buf) - 1); 69 strncpy(buf, deco->name, sizeof(buf) - 1);
68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 70 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
69 sha1_to_hex(commit->object.sha1)); 71 sha1_to_hex(commit->object.sha1),
72 ctx.qry.vpath, 0);
70 } 73 }
71 deco = deco->next; 74 deco = deco->next;
72 } 75 }
@@ -82,14 +85,14 @@ void print_commit(struct commit *commit)
82 htmlf("<tr%s><td>", 85 htmlf("<tr%s><td>",
83 ctx.qry.showmsg ? " class='logheader'" : ""); 86 ctx.qry.showmsg ? " class='logheader'" : "");
84 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); 87 tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1));
85 tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); 88 tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, tmp);
86 html_link_open(tmp, NULL, NULL); 89 html_link_open(tmp, NULL, NULL);
87 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); 90 cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE);
88 html_link_close(); 91 html_link_close();
89 htmlf("</td><td%s>", 92 htmlf("</td><td%s>",
90 ctx.qry.showmsg ? " class='logsubject'" : ""); 93 ctx.qry.showmsg ? " class='logsubject'" : "");
91 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, 94 cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
92 sha1_to_hex(commit->object.sha1)); 95 sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0);
93 show_commit_decorations(commit); 96 show_commit_decorations(commit);
94 html("</td><td>"); 97 html("</td><td>");
95 html_txt(info->author); 98 html_txt(info->author);
@@ -146,10 +149,13 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
146 149
147 argv[1] = disambiguate_ref(tip); 150 argv[1] = disambiguate_ref(tip);
148 151
149 if (grep && pattern && (!strcmp(grep, "grep") || 152 if (grep && pattern) {
150 !strcmp(grep, "author") || 153 if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
151 !strcmp(grep, "committer"))) 154 !strcmp(grep, "committer"))
152 argv[argc++] = fmt("--%s=%s", grep, pattern); 155 argv[argc++] = fmt("--%s=%s", grep, pattern);
156 if (!strcmp(grep, "range"))
157 argv[1] = pattern;
158 }
153 159
154 if (path) { 160 if (path) {
155 argv[argc++] = "--"; 161 argv[argc++] = "--";
@@ -176,7 +182,7 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
176 html(" ("); 182 html(" (");
177 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 183 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
178 NULL, ctx.qry.head, ctx.qry.sha1, 184 NULL, ctx.qry.head, ctx.qry.sha1,
179 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, 185 ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
180 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 186 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
181 html(")"); 187 html(")");
182 } 188 }
@@ -213,22 +219,22 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
213 columns); 219 columns);
214 if (ofs > 0) { 220 if (ofs > 0) {
215 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, 221 cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
216 ctx.qry.sha1, ctx.qry.path, 222 ctx.qry.sha1, ctx.qry.vpath,
217 ofs - cnt, ctx.qry.grep, 223 ofs - cnt, ctx.qry.grep,
218 ctx.qry.search, ctx.qry.showmsg); 224 ctx.qry.search, ctx.qry.showmsg);
219 html("&nbsp;"); 225 html("&nbsp;");
220 } 226 }
221 if ((commit = get_revision(&rev)) != NULL) { 227 if ((commit = get_revision(&rev)) != NULL) {
222 cgit_log_link("[next]", NULL, NULL, ctx.qry.head, 228 cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
223 ctx.qry.sha1, ctx.qry.path, 229 ctx.qry.sha1, ctx.qry.vpath,
224 ofs + cnt, ctx.qry.grep, 230 ofs + cnt, ctx.qry.grep,
225 ctx.qry.search, ctx.qry.showmsg); 231 ctx.qry.search, ctx.qry.showmsg);
226 } 232 }
227 html("</div>"); 233 html("</div>");
228 } else if ((commit = get_revision(&rev)) != NULL) { 234 } else if ((commit = get_revision(&rev)) != NULL) {
229 html("<tr class='nohover'><td colspan='3'>"); 235 html("<tr class='nohover'><td colspan='3'>");
230 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, 236 cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
231 NULL, NULL, ctx.qry.showmsg); 237 ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg);
232 html("</td></tr>\n"); 238 html("</td></tr>\n");
233 } 239 }
234} 240}
diff --git a/ui-patch.c b/ui-patch.c
index 2a8f7a5..d13104c 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -71,13 +71,13 @@ static void filepair_cb(struct diff_filepair *pair)
71 return; 71 return;
72 } 72 }
73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 73 if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
74 &new_size, &binary, print_line)) 74 &new_size, &binary, 0, print_line))
75 html("Error running diff"); 75 html("Error running diff");
76 if (binary) 76 if (binary)
77 html("Binary files differ\n"); 77 html("Binary files differ\n");
78} 78}
79 79
80void cgit_print_patch(char *hex) 80void cgit_print_patch(char *hex, const char *prefix)
81{ 81{
82 struct commit *commit; 82 struct commit *commit;
83 struct commitinfo *info; 83 struct commitinfo *info;
@@ -122,7 +122,9 @@ void cgit_print_patch(char *hex)
122 html("\n"); 122 html("\n");
123 } 123 }
124 html("---\n"); 124 html("---\n");
125 cgit_diff_tree(old_sha1, sha1, filepair_cb, NULL); 125 if (prefix)
126 htmlf("(limited to '%s')\n\n", prefix);
127 cgit_diff_tree(old_sha1, sha1, filepair_cb, prefix);
126 html("--\n"); 128 html("--\n");
127 htmlf("cgit %s\n", CGIT_VERSION); 129 htmlf("cgit %s\n", CGIT_VERSION);
128 cgit_free_commitinfo(info); 130 cgit_free_commitinfo(info);
diff --git a/ui-patch.h b/ui-patch.h
index 9f68212..1641cea 100644
--- a/ui-patch.h
+++ b/ui-patch.h
@@ -1,6 +1,6 @@
1#ifndef UI_PATCH_H 1#ifndef UI_PATCH_H
2#define UI_PATCH_H 2#define UI_PATCH_H
3 3
4extern void cgit_print_patch(char *hex); 4extern void cgit_print_patch(char *hex, const char *prefix);
5 5
6#endif /* UI_PATCH_H */ 6#endif /* UI_PATCH_H */
diff --git a/ui-plain.c b/ui-plain.c
index 66cb19c..da76406 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -10,8 +10,7 @@
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12 12
13char *curr_rev; 13int match_baselen;
14char *match_path;
15int match; 14int match;
16 15
17static void print_object(const unsigned char *sha1, const char *path) 16static void print_object(const unsigned char *sha1, const char *path)
@@ -53,17 +52,63 @@ static void print_object(const unsigned char *sha1, const char *path)
53 match = 1; 52 match = 1;
54} 53}
55 54
55static void print_dir(const unsigned char *sha1, const char *path,
56 const char *base)
57{
58 char *fullpath;
59 if (path[0] || base[0])
60 fullpath = fmt("/%s%s/", base, path);
61 else
62 fullpath = "/";
63 ctx.page.etag = sha1_to_hex(sha1);
64 cgit_print_http_headers(&ctx);
65 htmlf("<html><head><title>%s</title></head>\n<body>\n"
66 " <h2>%s</h2>\n <ul>\n", fullpath, fullpath);
67 if (path[0] || base[0])
68 html(" <li><a href=\"../\">../</a></li>\n");
69 match = 2;
70}
71
72static void print_dir_entry(const unsigned char *sha1, const char *path,
73 unsigned mode)
74{
75 const char *sep = "";
76 if (S_ISDIR(mode))
77 sep = "/";
78 htmlf(" <li><a href=\"%s%s\">%s%s</a></li>\n", path, sep, path, sep);
79 match = 2;
80}
81
82static void print_dir_tail(void)
83{
84 html(" </ul>\n</body></html>\n");
85}
86
56static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 87static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
57 const char *pathname, unsigned mode, int stage, 88 const char *pathname, unsigned mode, int stage,
58 void *cbdata) 89 void *cbdata)
59{ 90{
60 if (S_ISDIR(mode)) 91 if (baselen == match_baselen) {
92 if (S_ISREG(mode))
93 print_object(sha1, pathname);
94 else if (S_ISDIR(mode)) {
95 print_dir(sha1, pathname, base);
96 return READ_TREE_RECURSIVE;
97 }
98 }
99 else if (baselen > match_baselen)
100 print_dir_entry(sha1, pathname, mode);
101 else if (S_ISDIR(mode))
61 return READ_TREE_RECURSIVE; 102 return READ_TREE_RECURSIVE;
62 103
63 if (S_ISREG(mode) && !strncmp(base, match_path, baselen) && 104 return 0;
64 !strcmp(pathname, match_path + baselen)) 105}
65 print_object(sha1, pathname);
66 106
107static int basedir_len(const char *path)
108{
109 char *p = strrchr(path, '/');
110 if (p)
111 return p - path + 1;
67 return 0; 112 return 0;
68} 113}
69 114
@@ -77,7 +122,6 @@ void cgit_print_plain(struct cgit_context *ctx)
77 if (!rev) 122 if (!rev)
78 rev = ctx->qry.head; 123 rev = ctx->qry.head;
79 124
80 curr_rev = xstrdup(rev);
81 if (get_sha1(rev, sha1)) { 125 if (get_sha1(rev, sha1)) {
82 html_status(404, "Not found", 0); 126 html_status(404, "Not found", 0);
83 return; 127 return;
@@ -87,8 +131,16 @@ void cgit_print_plain(struct cgit_context *ctx)
87 html_status(404, "Not found", 0); 131 html_status(404, "Not found", 0);
88 return; 132 return;
89 } 133 }
90 match_path = ctx->qry.path; 134 if (!paths[0]) {
135 paths[0] = "";
136 match_baselen = -1;
137 print_dir(commit->tree->object.sha1, "", "");
138 }
139 else
140 match_baselen = basedir_len(paths[0]);
91 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); 141 read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL);
92 if (!match) 142 if (!match)
93 html_status(404, "Not found", 0); 143 html_status(404, "Not found", 0);
144 else if (match == 2)
145 print_dir_tail();
94} 146}
diff --git a/ui-refs.c b/ui-refs.c
index d3b4f6e..94ff6be 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -74,7 +74,7 @@ static int print_branch(struct refinfo *ref)
74 html("</td><td>"); 74 html("</td><td>");
75 75
76 if (ref->object->type == OBJ_COMMIT) { 76 if (ref->object->type == OBJ_COMMIT) {
77 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 77 cgit_commit_link(info->subject, NULL, NULL, name, NULL, 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'>");
@@ -187,6 +187,8 @@ void cgit_print_branches(int maxcount)
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;
diff --git a/ui-shared.c b/ui-shared.c
index 8a7cc32..c398d7a 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -27,7 +27,7 @@ static char *http_date(time_t t)
27 tm->tm_hour, tm->tm_min, tm->tm_sec); 27 tm->tm_hour, tm->tm_min, tm->tm_sec);
28} 28}
29 29
30void cgit_print_error(char *msg) 30void cgit_print_error(const char *msg)
31{ 31{
32 html("<div class='error'>"); 32 html("<div class='error'>");
33 html_txt(msg); 33 html_txt(msg);
@@ -133,7 +133,7 @@ char *cgit_currurl()
133 return fmt("%s/", ctx.cfg.virtual_root); 133 return fmt("%s/", ctx.cfg.virtual_root);
134} 134}
135 135
136static void site_url(char *page, char *search, int ofs) 136static void site_url(const char *page, const char *search, int ofs)
137{ 137{
138 char *delim = "?"; 138 char *delim = "?";
139 139
@@ -160,8 +160,8 @@ static void site_url(char *page, char *search, int ofs)
160 } 160 }
161} 161}
162 162
163static void site_link(char *page, char *name, char *title, char *class, 163static void site_link(const char *page, const char *name, const char *title,
164 char *search, int ofs) 164 const char *class, const char *search, int ofs)
165{ 165{
166 html("<a"); 166 html("<a");
167 if (title) { 167 if (title) {
@@ -181,14 +181,14 @@ static void site_link(char *page, char *name, char *title, char *class,
181 html("</a>"); 181 html("</a>");
182} 182}
183 183
184void cgit_index_link(char *name, char *title, char *class, char *pattern, 184void cgit_index_link(const char *name, const char *title, const char *class,
185 int ofs) 185 const char *pattern, int ofs)
186{ 186{
187 site_link(NULL, name, title, class, pattern, ofs); 187 site_link(NULL, name, title, class, pattern, ofs);
188} 188}
189 189
190static char *repolink(char *title, char *class, char *page, char *head, 190static char *repolink(const char *title, const char *class, const char *page,
191 char *path) 191 const char *head, const char *path)
192{ 192{
193 char *delim = "?"; 193 char *delim = "?";
194 194
@@ -240,8 +240,9 @@ static char *repolink(char *title, char *class, char *page, char *head,
240 return fmt("%s", delim); 240 return fmt("%s", delim);
241} 241}
242 242
243static void reporevlink(char *page, char *name, char *title, char *class, 243static void reporevlink(const char *page, const char *name, const char *title,
244 char *head, char *rev, char *path) 244 const char *class, const char *head, const char *rev,
245 const char *path)
245{ 246{
246 char *delim; 247 char *delim;
247 248
@@ -256,32 +257,33 @@ static void reporevlink(char *page, char *name, char *title, char *class,
256 html("</a>"); 257 html("</a>");
257} 258}
258 259
259void cgit_summary_link(char *name, char *title, char *class, char *head) 260void cgit_summary_link(const char *name, const char *title, const char *class,
261 const char *head)
260{ 262{
261 reporevlink(NULL, name, title, class, head, NULL, NULL); 263 reporevlink(NULL, name, title, class, head, NULL, NULL);
262} 264}
263 265
264void cgit_tag_link(char *name, char *title, char *class, char *head, 266void cgit_tag_link(const char *name, const char *title, const char *class,
265 char *rev) 267 const char *head, const char *rev)
266{ 268{
267 reporevlink("tag", name, title, class, head, rev, NULL); 269 reporevlink("tag", name, title, class, head, rev, NULL);
268} 270}
269 271
270void cgit_tree_link(char *name, char *title, char *class, char *head, 272void cgit_tree_link(const char *name, const char *title, const char *class,
271 char *rev, char *path) 273 const char *head, const char *rev, const char *path)
272{ 274{
273 reporevlink("tree", name, title, class, head, rev, path); 275 reporevlink("tree", name, title, class, head, rev, path);
274} 276}
275 277
276void cgit_plain_link(char *name, char *title, char *class, char *head, 278void cgit_plain_link(const char *name, const char *title, const char *class,
277 char *rev, char *path) 279 const char *head, const char *rev, const char *path)
278{ 280{
279 reporevlink("plain", name, title, class, head, rev, path); 281 reporevlink("plain", name, title, class, head, rev, path);
280} 282}
281 283
282void cgit_log_link(char *name, char *title, char *class, char *head, 284void cgit_log_link(const char *name, const char *title, const char *class,
283 char *rev, char *path, int ofs, char *grep, char *pattern, 285 const char *head, const char *rev, const char *path,
284 int showmsg) 286 int ofs, const char *grep, const char *pattern, int showmsg)
285{ 287{
286 char *delim; 288 char *delim;
287 289
@@ -316,8 +318,9 @@ void cgit_log_link(char *name, char *title, char *class, char *head,
316 html("</a>"); 318 html("</a>");
317} 319}
318 320
319void cgit_commit_link(char *name, char *title, char *class, char *head, 321void cgit_commit_link(char *name, const char *title, const char *class,
320 char *rev) 322 const char *head, const char *rev, const char *path,
323 int toggle_ssdiff)
321{ 324{
322 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 325 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
323 name[ctx.cfg.max_msg_len] = '\0'; 326 name[ctx.cfg.max_msg_len] = '\0';
@@ -325,23 +328,48 @@ void cgit_commit_link(char *name, char *title, char *class, char *head,
325 name[ctx.cfg.max_msg_len - 2] = '.'; 328 name[ctx.cfg.max_msg_len - 2] = '.';
326 name[ctx.cfg.max_msg_len - 3] = '.'; 329 name[ctx.cfg.max_msg_len - 3] = '.';
327 } 330 }
328 reporevlink("commit", name, title, class, head, rev, NULL); 331
332 char *delim;
333
334 delim = repolink(title, class, "commit", head, path);
335 if (rev && strcmp(rev, ctx.qry.head)) {
336 html(delim);
337 html("id=");
338 html_url_arg(rev);
339 delim = "&amp;";
340 }
341 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
342 html(delim);
343 html("ss=1");
344 delim = "&amp;";
345 }
346 if (ctx.qry.context > 0 && ctx.qry.context != 3) {
347 html(delim);
348 html("context=");
349 htmlf("%d", ctx.qry.context);
350 delim = "&amp;";
351 }
352 html("'>");
353 html_txt(name);
354 html("</a>");
329} 355}
330 356
331void cgit_refs_link(char *name, char *title, char *class, char *head, 357void cgit_refs_link(const char *name, const char *title, const char *class,
332 char *rev, char *path) 358 const char *head, const char *rev, const char *path)
333{ 359{
334 reporevlink("refs", name, title, class, head, rev, path); 360 reporevlink("refs", name, title, class, head, rev, path);
335} 361}
336 362
337void cgit_snapshot_link(char *name, char *title, char *class, char *head, 363void cgit_snapshot_link(const char *name, const char *title, const char *class,
338 char *rev, char *archivename) 364 const char *head, const char *rev,
365 const char *archivename)
339{ 366{
340 reporevlink("snapshot", name, title, class, head, rev, archivename); 367 reporevlink("snapshot", name, title, class, head, rev, archivename);
341} 368}
342 369
343void cgit_diff_link(char *name, char *title, char *class, char *head, 370void cgit_diff_link(const char *name, const char *title, const char *class,
344 char *new_rev, char *old_rev, char *path) 371 const char *head, const char *new_rev, const char *old_rev,
372 const char *path, int toggle_ssdiff)
345{ 373{
346 char *delim; 374 char *delim;
347 375
@@ -356,24 +384,94 @@ void cgit_diff_link(char *name, char *title, char *class, char *head,
356 html(delim); 384 html(delim);
357 html("id2="); 385 html("id2=");
358 html_url_arg(old_rev); 386 html_url_arg(old_rev);
387 delim = "&amp;";
388 }
389 if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) {
390 html(delim);
391 html("ss=1");
392 delim = "&amp;";
393 }
394 if (ctx.qry.context > 0 && ctx.qry.context != 3) {
395 html(delim);
396 html("context=");
397 htmlf("%d", ctx.qry.context);
398 delim = "&amp;";
359 } 399 }
360 html("'>"); 400 html("'>");
361 html_txt(name); 401 html_txt(name);
362 html("</a>"); 402 html("</a>");
363} 403}
364 404
365void cgit_patch_link(char *name, char *title, char *class, char *head, 405void cgit_patch_link(const char *name, const char *title, const char *class,
366 char *rev) 406 const char *head, const char *rev, const char *path)
367{ 407{
368 reporevlink("patch", name, title, class, head, rev, NULL); 408 reporevlink("patch", name, title, class, head, rev, path);
369} 409}
370 410
371void cgit_stats_link(char *name, char *title, char *class, char *head, 411void cgit_stats_link(const char *name, const char *title, const char *class,
372 char *path) 412 const char *head, const char *path)
373{ 413{
374 reporevlink("stats", name, title, class, head, NULL, path); 414 reporevlink("stats", name, title, class, head, NULL, path);
375} 415}
376 416
417void cgit_self_link(char *name, const char *title, const char *class,
418 struct cgit_context *ctx)
419{
420 if (!strcmp(ctx->qry.page, "repolist"))
421 return cgit_index_link(name, title, class, ctx->qry.search,
422 ctx->qry.ofs);
423 else if (!strcmp(ctx->qry.page, "summary"))
424 return cgit_summary_link(name, title, class, ctx->qry.head);
425 else if (!strcmp(ctx->qry.page, "tag"))
426 return cgit_tag_link(name, title, class, ctx->qry.head,
427 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL);
428 else if (!strcmp(ctx->qry.page, "tree"))
429 return cgit_tree_link(name, title, class, ctx->qry.head,
430 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
431 ctx->qry.path);
432 else if (!strcmp(ctx->qry.page, "plain"))
433 return cgit_plain_link(name, title, class, ctx->qry.head,
434 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
435 ctx->qry.path);
436 else if (!strcmp(ctx->qry.page, "log"))
437 return cgit_log_link(name, title, class, ctx->qry.head,
438 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
439 ctx->qry.path, ctx->qry.ofs,
440 ctx->qry.grep, ctx->qry.search,
441 ctx->qry.showmsg);
442 else if (!strcmp(ctx->qry.page, "commit"))
443 return cgit_commit_link(name, title, class, ctx->qry.head,
444 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
445 ctx->qry.path, 0);
446 else if (!strcmp(ctx->qry.page, "patch"))
447 return cgit_patch_link(name, title, class, ctx->qry.head,
448 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
449 ctx->qry.path);
450 else if (!strcmp(ctx->qry.page, "refs"))
451 return cgit_refs_link(name, title, class, ctx->qry.head,
452 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
453 ctx->qry.path);
454 else if (!strcmp(ctx->qry.page, "snapshot"))
455 return cgit_snapshot_link(name, title, class, ctx->qry.head,
456 ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL,
457 ctx->qry.path);
458 else if (!strcmp(ctx->qry.page, "diff"))
459 return cgit_diff_link(name, title, class, ctx->qry.head,
460 ctx->qry.sha1, ctx->qry.sha2,
461 ctx->qry.path, 0);
462 else if (!strcmp(ctx->qry.page, "stats"))
463 return cgit_stats_link(name, title, class, ctx->qry.head,
464 ctx->qry.path);
465
466 /* Don't known how to make link for this page */
467 repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path);
468 html("><!-- cgit_self_link() doesn't know how to make link for page '");
469 html_txt(ctx->qry.page);
470 html("' -->");
471 html_txt(name);
472 html("</a>");
473}
474
377void cgit_object_link(struct object *obj) 475void cgit_object_link(struct object *obj)
378{ 476{
379 char *page, *shortrev, *fullrev, *name; 477 char *page, *shortrev, *fullrev, *name;
@@ -383,7 +481,7 @@ void cgit_object_link(struct object *obj)
383 shortrev[10] = '\0'; 481 shortrev[10] = '\0';
384 if (obj->type == OBJ_COMMIT) { 482 if (obj->type == OBJ_COMMIT) {
385 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 483 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
386 ctx.qry.head, fullrev); 484 ctx.qry.head, fullrev, NULL, 0);
387 return; 485 return;
388 } else if (obj->type == OBJ_TREE) 486 } else if (obj->type == OBJ_TREE)
389 page = "tree"; 487 page = "tree";
@@ -395,7 +493,7 @@ void cgit_object_link(struct object *obj)
395 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 493 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
396} 494}
397 495
398void cgit_print_date(time_t secs, char *format, int local_time) 496void cgit_print_date(time_t secs, const char *format, int local_time)
399{ 497{
400 char buf[64]; 498 char buf[64];
401 struct tm *time; 499 struct tm *time;
@@ -410,7 +508,7 @@ void cgit_print_date(time_t secs, char *format, int local_time)
410 html_txt(buf); 508 html_txt(buf);
411} 509}
412 510
413void cgit_print_age(time_t t, time_t max_relative, char *format) 511void cgit_print_age(time_t t, time_t max_relative, const char *format)
414{ 512{
415 time_t now, secs; 513 time_t now, secs;
416 514
@@ -509,7 +607,7 @@ void cgit_print_docstart(struct cgit_context *ctx)
509 html("<link rel='alternate' title='Atom feed' href='"); 607 html("<link rel='alternate' title='Atom feed' href='");
510 html(cgit_httpscheme()); 608 html(cgit_httpscheme());
511 html_attr(cgit_hosturl()); 609 html_attr(cgit_hosturl());
512 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 610 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath,
513 fmt("h=%s", ctx->qry.head))); 611 fmt("h=%s", ctx->qry.head)));
514 html("' type='application/atom+xml'/>\n"); 612 html("' type='application/atom+xml'/>\n");
515 } 613 }
@@ -589,14 +687,15 @@ int print_archive_ref(const char *refname, const unsigned char *sha1,
589 return 0; 687 return 0;
590} 688}
591 689
592void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page) 690void cgit_add_hidden_formfields(int incl_head, int incl_search,
691 const char *page)
593{ 692{
594 char *url; 693 char *url;
595 694
596 if (!ctx.cfg.virtual_root) { 695 if (!ctx.cfg.virtual_root) {
597 url = fmt("%s/%s", ctx.qry.repo, page); 696 url = fmt("%s/%s", ctx.qry.repo, page);
598 if (ctx.qry.path) 697 if (ctx.qry.vpath)
599 url = fmt("%s/%s", url, ctx.qry.path); 698 url = fmt("%s/%s", url, ctx.qry.vpath);
600 html_hidden("url", url); 699 html_hidden("url", url);
601 } 700 }
602 701
@@ -619,11 +718,30 @@ void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page)
619 } 718 }
620} 719}
621 720
622const char *fallback_cmd = "repolist"; 721static const char *hc(struct cgit_context *ctx, const char *page)
722{
723 return strcmp(ctx->qry.page, page) ? NULL : "active";
724}
623 725
624char *hc(struct cgit_cmd *cmd, const char *page) 726static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path)
625{ 727{
626 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); 728 char *old_path = ctx->qry.path;
729 char *p = path, *q, *end = path + strlen(path);
730
731 ctx->qry.path = NULL;
732 cgit_self_link("root", NULL, NULL, ctx);
733 ctx->qry.path = p = path;
734 while (p < end) {
735 if (!(q = strchr(p, '/')))
736 q = end;
737 *q = '\0';
738 html_txt("/");
739 cgit_self_link(p, NULL, NULL, ctx);
740 if (q < end)
741 *q = '/';
742 p = q + 1;
743 }
744 ctx->qry.path = old_path;
627} 745}
628 746
629static void print_header(struct cgit_context *ctx) 747static void print_header(struct cgit_context *ctx)
@@ -675,47 +793,44 @@ static void print_header(struct cgit_context *ctx)
675 793
676void cgit_print_pageheader(struct cgit_context *ctx) 794void cgit_print_pageheader(struct cgit_context *ctx)
677{ 795{
678 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
679
680 if (!cmd && ctx->repo)
681 fallback_cmd = "summary";
682
683 html("<div id='cgit'>"); 796 html("<div id='cgit'>");
684 if (!ctx->cfg.noheader) 797 if (!ctx->cfg.noheader)
685 print_header(ctx); 798 print_header(ctx);
686 799
687 html("<table class='tabs'><tr><td>\n"); 800 html("<table class='tabs'><tr><td>\n");
688 if (ctx->repo) { 801 if (ctx->repo) {
689 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 802 cgit_summary_link("summary", NULL, hc(ctx, "summary"),
690 ctx->qry.head); 803 ctx->qry.head);
691 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 804 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head,
692 ctx->qry.sha1, NULL); 805 ctx->qry.sha1, NULL);
693 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 806 cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head,
694 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); 807 NULL, ctx->qry.vpath, 0, NULL, NULL,
695 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 808 ctx->qry.showmsg);
696 ctx->qry.sha1, NULL); 809 cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head,
697 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 810 ctx->qry.sha1, ctx->qry.vpath);
698 ctx->qry.head, ctx->qry.sha1); 811 cgit_commit_link("commit", NULL, hc(ctx, "commit"),
699 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 812 ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0);
700 ctx->qry.sha1, ctx->qry.sha2, NULL); 813 cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head,
814 ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0);
701 if (ctx->repo->max_stats) 815 if (ctx->repo->max_stats)
702 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 816 cgit_stats_link("stats", NULL, hc(ctx, "stats"),
703 ctx->qry.head, NULL); 817 ctx->qry.head, ctx->qry.vpath);
704 if (ctx->repo->readme) 818 if (ctx->repo->readme)
705 reporevlink("about", "about", NULL, 819 reporevlink("about", "about", NULL,
706 hc(cmd, "about"), ctx->qry.head, NULL, 820 hc(ctx, "about"), ctx->qry.head, NULL,
707 NULL); 821 NULL);
708 html("</td><td class='form'>"); 822 html("</td><td class='form'>");
709 html("<form class='right' method='get' action='"); 823 html("<form class='right' method='get' action='");
710 if (ctx->cfg.virtual_root) 824 if (ctx->cfg.virtual_root)
711 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 825 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
712 ctx->qry.path, NULL)); 826 ctx->qry.vpath, NULL));
713 html("'>\n"); 827 html("'>\n");
714 cgit_add_hidden_formfields(1, 0, "log"); 828 cgit_add_hidden_formfields(1, 0, "log");
715 html("<select name='qt'>\n"); 829 html("<select name='qt'>\n");
716 html_option("grep", "log msg", ctx->qry.grep); 830 html_option("grep", "log msg", ctx->qry.grep);
717 html_option("author", "author", ctx->qry.grep); 831 html_option("author", "author", ctx->qry.grep);
718 html_option("committer", "committer", ctx->qry.grep); 832 html_option("committer", "committer", ctx->qry.grep);
833 html_option("range", "range", ctx->qry.grep);
719 html("</select>\n"); 834 html("</select>\n");
720 html("<input class='txt' type='text' size='10' name='q' value='"); 835 html("<input class='txt' type='text' size='10' name='q' value='");
721 html_attr(ctx->qry.search); 836 html_attr(ctx->qry.search);
@@ -723,9 +838,9 @@ void cgit_print_pageheader(struct cgit_context *ctx)
723 html("<input type='submit' value='search'/>\n"); 838 html("<input type='submit' value='search'/>\n");
724 html("</form>\n"); 839 html("</form>\n");
725 } else { 840 } else {
726 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 841 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0);
727 if (ctx->cfg.root_readme) 842 if (ctx->cfg.root_readme)
728 site_link("about", "about", NULL, hc(cmd, "about"), 843 site_link("about", "about", NULL, hc(ctx, "about"),
729 NULL, 0); 844 NULL, 0);
730 html("</td><td class='form'>"); 845 html("</td><td class='form'>");
731 html("<form method='get' action='"); 846 html("<form method='get' action='");
@@ -738,6 +853,12 @@ void cgit_print_pageheader(struct cgit_context *ctx)
738 html("</form>"); 853 html("</form>");
739 } 854 }
740 html("</td></tr></table>\n"); 855 html("</td></tr></table>\n");
856 if (ctx->qry.vpath) {
857 html("<div class='path'>");
858 html("path: ");
859 cgit_print_path_crumbs(ctx, ctx->qry.vpath);
860 html("</div>");
861 }
741 html("<div class='content'>"); 862 html("<div class='content'>");
742} 863}
743 864
@@ -760,13 +881,18 @@ void cgit_print_snapshot_links(const char *repo, const char *head,
760 const char *hex, int snapshots) 881 const char *hex, int snapshots)
761{ 882{
762 const struct cgit_snapshot_format* f; 883 const struct cgit_snapshot_format* f;
884 char *prefix;
763 char *filename; 885 char *filename;
886 unsigned char sha1[20];
764 887
888 if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 &&
889 (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1]))
890 hex++;
891 prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex));
765 for (f = cgit_snapshot_formats; f->suffix; f++) { 892 for (f = cgit_snapshot_formats; f->suffix; f++) {
766 if (!(snapshots & f->bit)) 893 if (!(snapshots & f->bit))
767 continue; 894 continue;
768 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 895 filename = fmt("%s%s", prefix, f->suffix);
769 f->suffix);
770 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 896 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
771 html("<br/>"); 897 html("<br/>");
772 } 898 }
diff --git a/ui-shared.h b/ui-shared.h
index b12aa89..3cc1258 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -10,35 +10,50 @@ extern char *cgit_fileurl(const char *reponame, const char *pagename,
10extern char *cgit_pageurl(const char *reponame, const char *pagename, 10extern char *cgit_pageurl(const char *reponame, const char *pagename,
11 const char *query); 11 const char *query);
12 12
13extern void cgit_index_link(char *name, char *title, char *class, 13extern void cgit_index_link(const char *name, const char *title,
14 char *pattern, int ofs); 14 const char *class, const char *pattern, int ofs);
15extern void cgit_summary_link(char *name, char *title, char *class, char *head); 15extern void cgit_summary_link(const char *name, const char *title,
16extern void cgit_tag_link(char *name, char *title, char *class, char *head, 16 const char *class, const char *head);
17 char *rev); 17extern void cgit_tag_link(const char *name, const char *title,
18extern void cgit_tree_link(char *name, char *title, char *class, char *head, 18 const char *class, const char *head,
19 char *rev, char *path); 19 const char *rev);
20extern void cgit_plain_link(char *name, char *title, char *class, char *head, 20extern void cgit_tree_link(const char *name, const char *title,
21 char *rev, char *path); 21 const char *class, const char *head,
22extern void cgit_log_link(char *name, char *title, char *class, char *head, 22 const char *rev, const char *path);
23 char *rev, char *path, int ofs, char *grep, 23extern void cgit_plain_link(const char *name, const char *title,
24 char *pattern, int showmsg); 24 const char *class, const char *head,
25extern void cgit_commit_link(char *name, char *title, char *class, char *head, 25 const char *rev, const char *path);
26 char *rev); 26extern void cgit_log_link(const char *name, const char *title,
27extern void cgit_patch_link(char *name, char *title, char *class, char *head, 27 const char *class, const char *head, const char *rev,
28 char *rev); 28 const char *path, int ofs, const char *grep,
29extern void cgit_refs_link(char *name, char *title, char *class, char *head, 29 const char *pattern, int showmsg);
30 char *rev, char *path); 30extern void cgit_commit_link(char *name, const char *title,
31extern void cgit_snapshot_link(char *name, char *title, char *class, 31 const char *class, const char *head,
32 char *head, char *rev, char *archivename); 32 const char *rev, const char *path,
33extern void cgit_diff_link(char *name, char *title, char *class, char *head, 33 int toggle_ssdiff);
34 char *new_rev, char *old_rev, char *path); 34extern void cgit_patch_link(const char *name, const char *title,
35extern void cgit_stats_link(char *name, char *title, char *class, char *head, 35 const char *class, const char *head,
36 char *path); 36 const char *rev, const char *path);
37extern void cgit_refs_link(const char *name, const char *title,
38 const char *class, const char *head,
39 const char *rev, const char *path);
40extern void cgit_snapshot_link(const char *name, const char *title,
41 const char *class, const char *head,
42 const char *rev, const char *archivename);
43extern void cgit_diff_link(const char *name, const char *title,
44 const char *class, const char *head,
45 const char *new_rev, const char *old_rev,
46 const char *path, int toggle_ssdiff);
47extern void cgit_stats_link(const char *name, const char *title,
48 const char *class, const char *head,
49 const char *path);
50extern void cgit_self_link(char *name, const char *title,
51 const char *class, struct cgit_context *ctx);
37extern void cgit_object_link(struct object *obj); 52extern void cgit_object_link(struct object *obj);
38 53
39extern void cgit_print_error(char *msg); 54extern void cgit_print_error(const char *msg);
40extern void cgit_print_date(time_t secs, char *format, int local_time); 55extern void cgit_print_date(time_t secs, const char *format, int local_time);
41extern void cgit_print_age(time_t t, time_t max_relative, char *format); 56extern void cgit_print_age(time_t t, time_t max_relative, const char *format);
42extern void cgit_print_http_headers(struct cgit_context *ctx); 57extern void cgit_print_http_headers(struct cgit_context *ctx);
43extern void cgit_print_docstart(struct cgit_context *ctx); 58extern void cgit_print_docstart(struct cgit_context *ctx);
44extern void cgit_print_docend(); 59extern void cgit_print_docend();
@@ -47,5 +62,5 @@ extern void cgit_print_filemode(unsigned short mode);
47extern void cgit_print_snapshot_links(const char *repo, const char *head, 62extern void cgit_print_snapshot_links(const char *repo, const char *head,
48 const char *hex, int snapshots); 63 const char *hex, int snapshots);
49extern void cgit_add_hidden_formfields(int incl_head, int incl_search, 64extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
50 char *page); 65 const char *page);
51#endif /* UI_SHARED_H */ 66#endif /* UI_SHARED_H */
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 4136b3e..1b25dca 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -35,11 +35,17 @@ static int write_tar_bzip2_archive(struct archiver_args *args)
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
diff --git a/ui-ssdiff.c b/ui-ssdiff.c
new file mode 100644
index 0000000..408e620
--- /dev/null
+++ b/ui-ssdiff.c
@@ -0,0 +1,369 @@
1#include "cgit.h"
2#include "html.h"
3#include "ui-shared.h"
4
5extern int use_ssdiff;
6
7static int current_old_line, current_new_line;
8
9struct deferred_lines {
10 int line_no;
11 char *line;
12 struct deferred_lines *next;
13};
14
15static struct deferred_lines *deferred_old, *deferred_old_last;
16static struct deferred_lines *deferred_new, *deferred_new_last;
17
18static char *longest_common_subsequence(char *A, char *B)
19{
20 int i, j, ri;
21 int m = strlen(A);
22 int n = strlen(B);
23 int L[m + 1][n + 1];
24 int tmp1, tmp2;
25 int lcs_length;
26 char *result;
27
28 for (i = m; i >= 0; i--) {
29 for (j = n; j >= 0; j--) {
30 if (A[i] == '\0' || B[j] == '\0') {
31 L[i][j] = 0;
32 } else if (A[i] == B[j]) {
33 L[i][j] = 1 + L[i + 1][j + 1];
34 } else {
35 tmp1 = L[i + 1][j];
36 tmp2 = L[i][j + 1];
37 L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2);
38 }
39 }
40 }
41
42 lcs_length = L[0][0];
43 result = xmalloc(lcs_length + 2);
44 memset(result, 0, sizeof(*result) * (lcs_length + 2));
45
46 ri = 0;
47 i = 0;
48 j = 0;
49 while (i < m && j < n) {
50 if (A[i] == B[j]) {
51 result[ri] = A[i];
52 ri += 1;
53 i += 1;
54 j += 1;
55 } else if (L[i + 1][j] >= L[i][j + 1]) {
56 i += 1;
57 } else {
58 j += 1;
59 }
60 }
61 return result;
62}
63
64static int line_from_hunk(char *line, char type)
65{
66 char *buf1, *buf2;
67 int len;
68
69 buf1 = strchr(line, type);
70 if (buf1 == NULL)
71 return 0;
72 buf1 += 1;
73 buf2 = strchr(buf1, ',');
74 if (buf2 == NULL)
75 return 0;
76 len = buf2 - buf1;
77 buf2 = xmalloc(len + 1);
78 strncpy(buf2, buf1, len);
79 buf2[len] = '\0';
80 int res = atoi(buf2);
81 free(buf2);
82 return res;
83}
84
85static char *replace_tabs(char *line)
86{
87 char *prev_buf = line;
88 char *cur_buf;
89 int linelen = strlen(line);
90 int n_tabs = 0;
91 int i;
92 char *result;
93 char *spaces = " ";
94
95 if (linelen == 0) {
96 result = xmalloc(1);
97 result[0] = '\0';
98 return result;
99 }
100
101 for (i = 0; i < linelen; i++)
102 if (line[i] == '\t')
103 n_tabs += 1;
104 result = xmalloc(linelen + n_tabs * 8 + 1);
105 result[0] = '\0';
106
107 while (1) {
108 cur_buf = strchr(prev_buf, '\t');
109 if (!cur_buf) {
110 strcat(result, prev_buf);
111 break;
112 } else {
113 strcat(result, " ");
114 strncat(result, spaces, 8 - (strlen(result) % 8));
115 strncat(result, prev_buf, cur_buf - prev_buf);
116 }
117 prev_buf = cur_buf + 1;
118 }
119 return result;
120}
121
122static int calc_deferred_lines(struct deferred_lines *start)
123{
124 struct deferred_lines *item = start;
125 int result = 0;
126 while (item) {
127 result += 1;
128 item = item->next;
129 }
130 return result;
131}
132
133static void deferred_old_add(char *line, int line_no)
134{
135 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
136 item->line = xstrdup(line);
137 item->line_no = line_no;
138 item->next = NULL;
139 if (deferred_old) {
140 deferred_old_last->next = item;
141 deferred_old_last = item;
142 } else {
143 deferred_old = deferred_old_last = item;
144 }
145}
146
147static void deferred_new_add(char *line, int line_no)
148{
149 struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
150 item->line = xstrdup(line);
151 item->line_no = line_no;
152 item->next = NULL;
153 if (deferred_new) {
154 deferred_new_last->next = item;
155 deferred_new_last = item;
156 } else {
157 deferred_new = deferred_new_last = item;
158 }
159}
160
161static void print_part_with_lcs(char *class, char *line, char *lcs)
162{
163 int line_len = strlen(line);
164 int i, j;
165 char c[2] = " ";
166 int same = 1;
167
168 j = 0;
169 for (i = 0; i < line_len; i++) {
170 c[0] = line[i];
171 if (same) {
172 if (line[i] == lcs[j])
173 j += 1;
174 else {
175 same = 0;
176 htmlf("<span class='%s'>", class);
177 }
178 } else if (line[i] == lcs[j]) {
179 same = 1;
180 htmlf("</span>");
181 j += 1;
182 }
183 html_txt(c);
184 }
185}
186
187static void print_ssdiff_line(char *class,
188 int old_line_no,
189 char *old_line,
190 int new_line_no,
191 char *new_line, int individual_chars)
192{
193 char *lcs = NULL;
194 if (old_line)
195 old_line = replace_tabs(old_line + 1);
196 if (new_line)
197 new_line = replace_tabs(new_line + 1);
198 if (individual_chars && old_line && new_line)
199 lcs = longest_common_subsequence(old_line, new_line);
200 html("<tr>");
201 if (old_line_no > 0)
202 htmlf("<td class='lineno'>%d</td><td class='%s'>",
203 old_line_no, class);
204 else if (old_line)
205 htmlf("<td class='lineno'></td><td class='%s'>", class);
206 else
207 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
208 if (old_line) {
209 if (lcs)
210 print_part_with_lcs("del", old_line, lcs);
211 else
212 html_txt(old_line);
213 }
214
215 html("</td>");
216 if (new_line_no > 0)
217 htmlf("<td class='lineno'>%d</td><td class='%s'>",
218 new_line_no, class);
219 else if (new_line)
220 htmlf("<td class='lineno'></td><td class='%s'>", class);
221 else
222 htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
223 if (new_line) {
224 if (lcs)
225 print_part_with_lcs("add", new_line, lcs);
226 else
227 html_txt(new_line);
228 }
229
230 html("</td></tr>");
231 if (lcs)
232 free(lcs);
233 if (new_line)
234 free(new_line);
235 if (old_line)
236 free(old_line);
237}
238
239static void print_deferred_old_lines()
240{
241 struct deferred_lines *iter_old, *tmp;
242 iter_old = deferred_old;
243 while (iter_old) {
244 print_ssdiff_line("del", iter_old->line_no,
245 iter_old->line, -1, NULL, 0);
246 tmp = iter_old->next;
247 free(iter_old);
248 iter_old = tmp;
249 }
250}
251
252static void print_deferred_new_lines()
253{
254 struct deferred_lines *iter_new, *tmp;
255 iter_new = deferred_new;
256 while (iter_new) {
257 print_ssdiff_line("add", -1, NULL,
258 iter_new->line_no, iter_new->line, 0);
259 tmp = iter_new->next;
260 free(iter_new);
261 iter_new = tmp;
262 }
263}
264
265static void print_deferred_changed_lines()
266{
267 struct deferred_lines *iter_old, *iter_new, *tmp;
268 int n_old_lines = calc_deferred_lines(deferred_old);
269 int n_new_lines = calc_deferred_lines(deferred_new);
270 int individual_chars = (n_old_lines == n_new_lines ? 1 : 0);
271
272 iter_old = deferred_old;
273 iter_new = deferred_new;
274 while (iter_old || iter_new) {
275 if (iter_old && iter_new)
276 print_ssdiff_line("changed", iter_old->line_no,
277 iter_old->line,
278 iter_new->line_no, iter_new->line,
279 individual_chars);
280 else if (iter_old)
281 print_ssdiff_line("changed", iter_old->line_no,
282 iter_old->line, -1, NULL, 0);
283 else if (iter_new)
284 print_ssdiff_line("changed", -1, NULL,
285 iter_new->line_no, iter_new->line, 0);
286 if (iter_old) {
287 tmp = iter_old->next;
288 free(iter_old);
289 iter_old = tmp;
290 }
291
292 if (iter_new) {
293 tmp = iter_new->next;
294 free(iter_new);
295 iter_new = tmp;
296 }
297 }
298}
299
300void cgit_ssdiff_print_deferred_lines()
301{
302 if (!deferred_old && !deferred_new)
303 return;
304 if (deferred_old && !deferred_new)
305 print_deferred_old_lines();
306 else if (!deferred_old && deferred_new)
307 print_deferred_new_lines();
308 else
309 print_deferred_changed_lines();
310 deferred_old = deferred_old_last = NULL;
311 deferred_new = deferred_new_last = NULL;
312}
313
314/*
315 * print a single line returned from xdiff
316 */
317void cgit_ssdiff_line_cb(char *line, int len)
318{
319 char c = line[len - 1];
320 line[len - 1] = '\0';
321 if (line[0] == '@') {
322 current_old_line = line_from_hunk(line, '-');
323 current_new_line = line_from_hunk(line, '+');
324 }
325
326 if (line[0] == ' ') {
327 if (deferred_old || deferred_new)
328 cgit_ssdiff_print_deferred_lines();
329 print_ssdiff_line("ctx", current_old_line, line,
330 current_new_line, line, 0);
331 current_old_line += 1;
332 current_new_line += 1;
333 } else if (line[0] == '+') {
334 deferred_new_add(line, current_new_line);
335 current_new_line += 1;
336 } else if (line[0] == '-') {
337 deferred_old_add(line, current_old_line);
338 current_old_line += 1;
339 } else if (line[0] == '@') {
340 html("<tr><td colspan='4' class='hunk'>");
341 html_txt(line);
342 html("</td></tr>");
343 } else {
344 html("<tr><td colspan='4' class='ctx'>");
345 html_txt(line);
346 html("</td></tr>");
347 }
348 line[len - 1] = c;
349}
350
351void cgit_ssdiff_header_begin()
352{
353 current_old_line = -1;
354 current_new_line = -1;
355 html("<tr><td class='space' colspan='4'><div></div></td></tr>");
356 html("<tr><td class='head' colspan='4'>");
357}
358
359void cgit_ssdiff_header_end()
360{
361 html("</td><tr>");
362}
363
364void cgit_ssdiff_footer()
365{
366 if (deferred_old || deferred_new)
367 cgit_ssdiff_print_deferred_lines();
368 html("<tr><td class='foot' colspan='4'></td></tr>");
369}
diff --git a/ui-ssdiff.h b/ui-ssdiff.h
new file mode 100644
index 0000000..64b4b12
--- /dev/null
+++ b/ui-ssdiff.h
@@ -0,0 +1,13 @@
1#ifndef UI_SSDIFF_H
2#define UI_SSDIFF_H
3
4extern void cgit_ssdiff_print_deferred_lines();
5
6extern void cgit_ssdiff_line_cb(char *line, int len);
7
8extern void cgit_ssdiff_header_begin();
9extern void cgit_ssdiff_header_end();
10
11extern void cgit_ssdiff_footer();
12
13#endif /* UI_SSDIFF_H */
diff --git a/ui-tag.c b/ui-tag.c
index c2d72af..39e4cb8 100644
--- a/ui-tag.c
+++ b/ui-tag.c
@@ -30,6 +30,14 @@ static void print_tag_content(char *buf)
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];
@@ -56,16 +64,16 @@ void cgit_print_tag(char *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(" ");
@@ -73,19 +81,23 @@ void cgit_print_tag(char *revname)
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;
diff --git a/ui-tree.c b/ui-tree.c
index a164767..75ec9cb 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -102,10 +102,16 @@ static void print_object(const unsigned char *sha1, char *path, const char *base
102 return; 102 return;
103 } 103 }
104 104
105 html(" ("); 105 htmlf("blob: %s (", sha1_to_hex(sha1));
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 html(")\n");
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 }
109 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);
@@ -169,6 +175,8 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
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;
@@ -217,17 +225,10 @@ static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
217{ 225{
218 static int state; 226 static int state;
219 static char buffer[PATH_MAX]; 227 static char buffer[PATH_MAX];
220 char *url;
221 228
222 if (state == 0) { 229 if (state == 0) {
223 memcpy(buffer, base, baselen); 230 memcpy(buffer, base, baselen);
224 strcpy(buffer+baselen, pathname); 231 strcpy(buffer+baselen, pathname);
225 url = cgit_pageurl(ctx.qry.repo, "tree",
226 fmt("h=%s&amp;path=%s", curr_rev, buffer));
227 html("/");
228 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
229 curr_rev, buffer);
230
231 if (strcmp(match_path, buffer)) 232 if (strcmp(match_path, buffer))
232 return READ_TREE_RECURSIVE; 233 return READ_TREE_RECURSIVE;
233 234
@@ -270,10 +271,6 @@ void cgit_print_tree(const char *rev, char *path)
270 return; 271 return;
271 } 272 }
272 273
273 html("path: <a href='");
274 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev)));
275 html("'>root</a>");
276
277 if (path == NULL) { 274 if (path == NULL) {
278 ls_tree(commit->tree->object.sha1, NULL); 275 ls_tree(commit->tree->object.sha1, NULL);
279 return; 276 return;