aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore5
-rw-r--r--Makefile21
-rw-r--r--cgit-doc.css3
-rw-r--r--cgit.c89
-rw-r--r--cgit.css2
-rw-r--r--cgit.h42
-rw-r--r--cgitrc.5.txt198
-rw-r--r--cmd.c2
-rwxr-xr-xfilters/commit-links.sh12
-rwxr-xr-xfilters/syntax-highlighting.sh39
m---------git0
-rw-r--r--scan-tree.c19
-rw-r--r--shared.c59
-rw-r--r--ui-atom.c8
-rw-r--r--ui-blob.c8
-rw-r--r--ui-commit.c20
-rw-r--r--ui-log.c4
-rw-r--r--ui-patch.c6
-rw-r--r--ui-plain.c18
-rw-r--r--ui-refs.c19
-rw-r--r--ui-repolist.c28
-rw-r--r--ui-shared.c81
-rw-r--r--ui-shared.h1
-rw-r--r--ui-snapshot.c35
-rw-r--r--ui-summary.c28
-rw-r--r--ui-summary.h2
-rw-r--r--ui-tag.c2
-rw-r--r--ui-tree.c26
28 files changed, 594 insertions, 183 deletions
diff --git a/.gitignore b/.gitignore
index 1e016e5..487728b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,5 +2,10 @@
2cgit 2cgit
3cgit.conf 3cgit.conf
4VERSION 4VERSION
5cgitrc.5
6cgitrc.5.fo
7cgitrc.5.html
8cgitrc.5.pdf
9cgitrc.5.xml
5*.o 10*.o
6*.d 11*.d
diff --git a/Makefile b/Makefile
index 33c606d..1f9893a 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@ 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.1.1 8GIT_VER = 1.6.3.4
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
@@ -100,7 +100,8 @@ ifdef NEEDS_LIBICONV
100endif 100endif
101 101
102 102
103.PHONY: all libgit test install uninstall clean force-version get-git 103.PHONY: all libgit test install uninstall clean force-version get-git \
104 doc man-doc html-doc clean-doc
104 105
105all: cgit 106all: cgit
106 107
@@ -149,8 +150,22 @@ uninstall:
149 rm -f $(CGIT_DATA_PATH)/cgit.css 150 rm -f $(CGIT_DATA_PATH)/cgit.css
150 rm -f $(CGIT_DATA_PATH)/cgit.png 151 rm -f $(CGIT_DATA_PATH)/cgit.png
151 152
152clean: 153doc: man-doc html-doc pdf-doc
154
155man-doc: cgitrc.5.txt
156 a2x -f manpage cgitrc.5.txt
157
158html-doc: cgitrc.5.txt
159 a2x -f xhtml --stylesheet=cgit-doc.css cgitrc.5.txt
160
161pdf-doc: cgitrc.5.txt
162 a2x -f pdf cgitrc.5.txt
163
164clean: clean-doc
153 rm -f cgit VERSION *.o *.d 165 rm -f cgit VERSION *.o *.d
154 166
167clean-doc:
168 rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo
169
155get-git: 170get-git:
156 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git 171 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git
diff --git a/cgit-doc.css b/cgit-doc.css
new file mode 100644
index 0000000..5a399b6
--- /dev/null
+++ b/cgit-doc.css
@@ -0,0 +1,3 @@
1div.variablelist dt {
2 margin-top: 1em;
3}
diff --git a/cgit.c b/cgit.c
index 5301840..b0e1c44 100644
--- a/cgit.c
+++ b/cgit.c
@@ -17,6 +17,29 @@
17 17
18const char *cgit_version = CGIT_VERSION; 18const char *cgit_version = CGIT_VERSION;
19 19
20void add_mimetype(const char *name, const char *value)
21{
22 struct string_list_item *item;
23
24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
25 item->util = xstrdup(value);
26}
27
28struct cgit_filter *new_filter(const char *cmd, int extra_args)
29{
30 struct cgit_filter *f;
31
32 if (!cmd || !cmd[0])
33 return NULL;
34
35 f = xmalloc(sizeof(struct cgit_filter));
36 f->cmd = xstrdup(cmd);
37 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
38 f->argv[0] = f->cmd;
39 f->argv[1] = NULL;
40 return f;
41}
42
20void config_cb(const char *name, const char *value) 43void config_cb(const char *name, const char *value)
21{ 44{
22 if (!strcmp(name, "root-title")) 45 if (!strcmp(name, "root-title"))
@@ -31,6 +54,8 @@ void config_cb(const char *name, const char *value)
31 ctx.cfg.favicon = xstrdup(value); 54 ctx.cfg.favicon = xstrdup(value);
32 else if (!strcmp(name, "footer")) 55 else if (!strcmp(name, "footer"))
33 ctx.cfg.footer = xstrdup(value); 56 ctx.cfg.footer = xstrdup(value);
57 else if (!strcmp(name, "head-include"))
58 ctx.cfg.head_include = xstrdup(value);
34 else if (!strcmp(name, "header")) 59 else if (!strcmp(name, "header"))
35 ctx.cfg.header = xstrdup(value); 60 ctx.cfg.header = xstrdup(value);
36 else if (!strcmp(name, "logo")) 61 else if (!strcmp(name, "logo"))
@@ -49,6 +74,10 @@ void config_cb(const char *name, const char *value)
49 ctx.cfg.virtual_root = ""; 74 ctx.cfg.virtual_root = "";
50 } else if (!strcmp(name, "nocache")) 75 } else if (!strcmp(name, "nocache"))
51 ctx.cfg.nocache = atoi(value); 76 ctx.cfg.nocache = atoi(value);
77 else if (!strcmp(name, "noplainemail"))
78 ctx.cfg.noplainemail = atoi(value);
79 else if (!strcmp(name, "noheader"))
80 ctx.cfg.noheader = atoi(value);
52 else if (!strcmp(name, "snapshots")) 81 else if (!strcmp(name, "snapshots"))
53 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 82 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
54 else if (!strcmp(name, "enable-index-links")) 83 else if (!strcmp(name, "enable-index-links"))
@@ -71,6 +100,12 @@ void config_cb(const char *name, const char *value)
71 ctx.cfg.cache_static_ttl = atoi(value); 100 ctx.cfg.cache_static_ttl = atoi(value);
72 else if (!strcmp(name, "cache-dynamic-ttl")) 101 else if (!strcmp(name, "cache-dynamic-ttl"))
73 ctx.cfg.cache_dynamic_ttl = atoi(value); 102 ctx.cfg.cache_dynamic_ttl = atoi(value);
103 else if (!strcmp(name, "about-filter"))
104 ctx.cfg.about_filter = new_filter(value, 0);
105 else if (!strcmp(name, "commit-filter"))
106 ctx.cfg.commit_filter = new_filter(value, 0);
107 else if (!strcmp(name, "embedded"))
108 ctx.cfg.embedded = atoi(value);
74 else if (!strcmp(name, "max-message-length")) 109 else if (!strcmp(name, "max-message-length"))
75 ctx.cfg.max_msg_len = atoi(value); 110 ctx.cfg.max_msg_len = atoi(value);
76 else if (!strcmp(name, "max-repodesc-length")) 111 else if (!strcmp(name, "max-repodesc-length"))
@@ -79,6 +114,8 @@ void config_cb(const char *name, const char *value)
79 ctx.cfg.max_repo_count = atoi(value); 114 ctx.cfg.max_repo_count = atoi(value);
80 else if (!strcmp(name, "max-commit-count")) 115 else if (!strcmp(name, "max-commit-count"))
81 ctx.cfg.max_commit_count = atoi(value); 116 ctx.cfg.max_commit_count = atoi(value);
117 else if (!strcmp(name, "source-filter"))
118 ctx.cfg.source_filter = new_filter(value, 1);
82 else if (!strcmp(name, "summary-log")) 119 else if (!strcmp(name, "summary-log"))
83 ctx.cfg.summary_log = atoi(value); 120 ctx.cfg.summary_log = atoi(value);
84 else if (!strcmp(name, "summary-branches")) 121 else if (!strcmp(name, "summary-branches"))
@@ -95,6 +132,8 @@ void config_cb(const char *name, const char *value)
95 ctx.cfg.clone_prefix = xstrdup(value); 132 ctx.cfg.clone_prefix = xstrdup(value);
96 else if (!strcmp(name, "local-time")) 133 else if (!strcmp(name, "local-time"))
97 ctx.cfg.local_time = atoi(value); 134 ctx.cfg.local_time = atoi(value);
135 else if (!prefixcmp(name, "mimetype."))
136 add_mimetype(name + 9, value);
98 else if (!strcmp(name, "repo.group")) 137 else if (!strcmp(name, "repo.group"))
99 ctx.cfg.repo_group = xstrdup(value); 138 ctx.cfg.repo_group = xstrdup(value);
100 else if (!strcmp(name, "repo.url")) 139 else if (!strcmp(name, "repo.url"))
@@ -121,6 +160,12 @@ void config_cb(const char *name, const char *value)
121 ctx.repo->max_stats = cgit_find_stats_period(value, NULL); 160 ctx.repo->max_stats = cgit_find_stats_period(value, NULL);
122 else if (ctx.repo && !strcmp(name, "repo.module-link")) 161 else if (ctx.repo && !strcmp(name, "repo.module-link"))
123 ctx.repo->module_link= xstrdup(value); 162 ctx.repo->module_link= xstrdup(value);
163 else if (ctx.repo && !strcmp(name, "repo.about-filter"))
164 ctx.repo->about_filter = new_filter(value, 0);
165 else if (ctx.repo && !strcmp(name, "repo.commit-filter"))
166 ctx.repo->commit_filter = new_filter(value, 0);
167 else if (ctx.repo && !strcmp(name, "repo.source-filter"))
168 ctx.repo->source_filter = new_filter(value, 1);
124 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 169 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
125 if (*value == '/') 170 if (*value == '/')
126 ctx.repo->readme = xstrdup(value); 171 ctx.repo->readme = xstrdup(value);
@@ -173,6 +218,11 @@ static void querystring_cb(const char *name, const char *value)
173 } 218 }
174} 219}
175 220
221char *xstrdupn(const char *str)
222{
223 return (str ? xstrdup(str) : NULL);
224}
225
176static void prepare_context(struct cgit_context *ctx) 226static void prepare_context(struct cgit_context *ctx)
177{ 227{
178 memset(ctx, 0, sizeof(ctx)); 228 memset(ctx, 0, sizeof(ctx));
@@ -186,7 +236,7 @@ static void prepare_context(struct cgit_context *ctx)
186 ctx->cfg.cache_root_ttl = 5; 236 ctx->cfg.cache_root_ttl = 5;
187 ctx->cfg.cache_static_ttl = -1; 237 ctx->cfg.cache_static_ttl = -1;
188 ctx->cfg.css = "/cgit.css"; 238 ctx->cfg.css = "/cgit.css";
189 ctx->cfg.logo = "/git-logo.png"; 239 ctx->cfg.logo = "/cgit.png";
190 ctx->cfg.local_time = 0; 240 ctx->cfg.local_time = 0;
191 ctx->cfg.max_repo_count = 50; 241 ctx->cfg.max_repo_count = 50;
192 ctx->cfg.max_commit_count = 50; 242 ctx->cfg.max_commit_count = 50;
@@ -203,12 +253,30 @@ static void prepare_context(struct cgit_context *ctx)
203 ctx->cfg.summary_branches = 10; 253 ctx->cfg.summary_branches = 10;
204 ctx->cfg.summary_log = 10; 254 ctx->cfg.summary_log = 10;
205 ctx->cfg.summary_tags = 10; 255 ctx->cfg.summary_tags = 10;
256 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
257 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
258 ctx->env.https = xstrdupn(getenv("HTTPS"));
259 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
260 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
261 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
262 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
263 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
264 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
265 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
206 ctx->page.mimetype = "text/html"; 266 ctx->page.mimetype = "text/html";
207 ctx->page.charset = PAGE_ENCODING; 267 ctx->page.charset = PAGE_ENCODING;
208 ctx->page.filename = NULL; 268 ctx->page.filename = NULL;
209 ctx->page.size = 0; 269 ctx->page.size = 0;
210 ctx->page.modified = time(NULL); 270 ctx->page.modified = time(NULL);
211 ctx->page.expires = ctx->page.modified; 271 ctx->page.expires = ctx->page.modified;
272 ctx->page.etag = NULL;
273 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
274 if (ctx->env.script_name)
275 ctx->cfg.script_name = ctx->env.script_name;
276 if (ctx->env.query_string)
277 ctx->qry.raw = ctx->env.query_string;
278 if (!ctx->env.cgit_config)
279 ctx->env.cgit_config = CGIT_CONFIG;
212} 280}
213 281
214struct refmatch { 282struct refmatch {
@@ -288,6 +356,8 @@ static int prepare_repo_cmd(struct cgit_context *ctx)
288 if (get_sha1(ctx->qry.head, sha1)) { 356 if (get_sha1(ctx->qry.head, sha1)) {
289 tmp = xstrdup(ctx->qry.head); 357 tmp = xstrdup(ctx->qry.head);
290 ctx->qry.head = ctx->repo->defbranch; 358 ctx->qry.head = ctx->repo->defbranch;
359 ctx->page.status = 404;
360 ctx->page.statusmsg = "not found";
291 cgit_print_http_headers(ctx); 361 cgit_print_http_headers(ctx);
292 cgit_print_docstart(ctx); 362 cgit_print_docstart(ctx);
293 cgit_print_pageheader(ctx); 363 cgit_print_pageheader(ctx);
@@ -379,6 +449,9 @@ static void cgit_parse_args(int argc, const char **argv)
379 if (!strcmp(argv[i], "--nocache")) { 449 if (!strcmp(argv[i], "--nocache")) {
380 ctx.cfg.nocache = 1; 450 ctx.cfg.nocache = 1;
381 } 451 }
452 if (!strcmp(argv[i], "--nohttp")) {
453 ctx.env.no_http = "1";
454 }
382 if (!strncmp(argv[i], "--query=", 8)) { 455 if (!strncmp(argv[i], "--query=", 8)) {
383 ctx.qry.raw = xstrdup(argv[i]+8); 456 ctx.qry.raw = xstrdup(argv[i]+8);
384 } 457 }
@@ -431,7 +504,6 @@ static int calc_ttl()
431 504
432int main(int argc, const char **argv) 505int main(int argc, const char **argv)
433{ 506{
434 const char *cgit_config_env = getenv("CGIT_CONFIG");
435 const char *path; 507 const char *path;
436 char *qry; 508 char *qry;
437 int err, ttl; 509 int err, ttl;
@@ -441,13 +513,8 @@ int main(int argc, const char **argv)
441 cgit_repolist.count = 0; 513 cgit_repolist.count = 0;
442 cgit_repolist.repos = NULL; 514 cgit_repolist.repos = NULL;
443 515
444 if (getenv("SCRIPT_NAME"))
445 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME"));
446 if (getenv("QUERY_STRING"))
447 ctx.qry.raw = xstrdup(getenv("QUERY_STRING"));
448 cgit_parse_args(argc, argv); 516 cgit_parse_args(argc, argv);
449 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG, 517 parse_configfile(ctx.env.cgit_config, config_cb);
450 config_cb);
451 ctx.repo = NULL; 518 ctx.repo = NULL;
452 http_parse_querystring(ctx.qry.raw, querystring_cb); 519 http_parse_querystring(ctx.qry.raw, querystring_cb);
453 520
@@ -462,7 +529,7 @@ int main(int argc, const char **argv)
462 * urls without the need for rewriterules in the webserver (as 529 * urls without the need for rewriterules in the webserver (as
463 * long as PATH_INFO is included in the cache lookup key). 530 * long as PATH_INFO is included in the cache lookup key).
464 */ 531 */
465 path = getenv("PATH_INFO"); 532 path = ctx.env.path_info;
466 if (!ctx.qry.url && path) { 533 if (!ctx.qry.url && path) {
467 if (path[0] == '/') 534 if (path[0] == '/')
468 path++; 535 path++;
@@ -472,12 +539,14 @@ int main(int argc, const char **argv)
472 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 539 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
473 free(qry); 540 free(qry);
474 } else 541 } else
475 ctx.qry.raw = ctx.qry.url; 542 ctx.qry.raw = xstrdup(ctx.qry.url);
476 cgit_parse_url(ctx.qry.url); 543 cgit_parse_url(ctx.qry.url);
477 } 544 }
478 545
479 ttl = calc_ttl(); 546 ttl = calc_ttl();
480 ctx.page.expires += ttl*60; 547 ctx.page.expires += ttl*60;
548 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
549 ctx.cfg.nocache = 1;
481 if (ctx.cfg.nocache) 550 if (ctx.cfg.nocache)
482 ctx.cfg.cache_size = 0; 551 ctx.cfg.cache_size = 0;
483 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 552 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
diff --git a/cgit.css b/cgit.css
index adfc8ae..e3b32e7 100644
--- a/cgit.css
+++ b/cgit.css
@@ -155,7 +155,7 @@ table.list td.logsubject {
155table.list td.logmsg { 155table.list td.logmsg {
156 font-family: monospace; 156 font-family: monospace;
157 white-space: pre; 157 white-space: pre;
158 padding: 1em 0em 2em 0em; 158 padding: 1em 0.5em 2em 0.5em;
159} 159}
160 160
161table.list td a { 161table.list td a {
diff --git a/cgit.h b/cgit.h
index 5f7af51..adb8da4 100644
--- a/cgit.h
+++ b/cgit.h
@@ -15,6 +15,7 @@
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h>
18#include <xdiff-interface.h> 19#include <xdiff-interface.h>
19#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
20#include <utf8.h> 21#include <utf8.h>
@@ -48,6 +49,15 @@ typedef void (*configfn)(const char *name, const char *value);
48typedef void (*filepair_fn)(struct diff_filepair *pair); 49typedef void (*filepair_fn)(struct diff_filepair *pair);
49typedef void (*linediff_fn)(char *line, int len); 50typedef void (*linediff_fn)(char *line, int len);
50 51
52struct cgit_filter {
53 char *cmd;
54 char **argv;
55 int old_stdout;
56 int pipe_fh[2];
57 int pid;
58 int exitstatus;
59};
60
51struct cgit_repo { 61struct cgit_repo {
52 char *url; 62 char *url;
53 char *name; 63 char *name;
@@ -64,6 +74,9 @@ struct cgit_repo {
64 int enable_log_linecount; 74 int enable_log_linecount;
65 int max_stats; 75 int max_stats;
66 time_t mtime; 76 time_t mtime;
77 struct cgit_filter *about_filter;
78 struct cgit_filter *commit_filter;
79 struct cgit_filter *source_filter;
67}; 80};
68 81
69struct cgit_repolist { 82struct cgit_repolist {
@@ -136,6 +149,7 @@ struct cgit_config {
136 char *css; 149 char *css;
137 char *favicon; 150 char *favicon;
138 char *footer; 151 char *footer;
152 char *head_include;
139 char *header; 153 char *header;
140 char *index_header; 154 char *index_header;
141 char *index_info; 155 char *index_info;
@@ -155,6 +169,7 @@ struct cgit_config {
155 int cache_repo_ttl; 169 int cache_repo_ttl;
156 int cache_root_ttl; 170 int cache_root_ttl;
157 int cache_static_ttl; 171 int cache_static_ttl;
172 int embedded;
158 int enable_index_links; 173 int enable_index_links;
159 int enable_log_filecount; 174 int enable_log_filecount;
160 int enable_log_linecount; 175 int enable_log_linecount;
@@ -166,11 +181,17 @@ struct cgit_config {
166 int max_repodesc_len; 181 int max_repodesc_len;
167 int max_stats; 182 int max_stats;
168 int nocache; 183 int nocache;
184 int noplainemail;
185 int noheader;
169 int renamelimit; 186 int renamelimit;
170 int snapshots; 187 int snapshots;
171 int summary_branches; 188 int summary_branches;
172 int summary_log; 189 int summary_log;
173 int summary_tags; 190 int summary_tags;
191 struct string_list mimetypes;
192 struct cgit_filter *about_filter;
193 struct cgit_filter *commit_filter;
194 struct cgit_filter *source_filter;
174}; 195};
175 196
176struct cgit_page { 197struct cgit_page {
@@ -180,10 +201,27 @@ struct cgit_page {
180 char *mimetype; 201 char *mimetype;
181 char *charset; 202 char *charset;
182 char *filename; 203 char *filename;
204 char *etag;
183 char *title; 205 char *title;
206 int status;
207 char *statusmsg;
208};
209
210struct cgit_environment {
211 char *cgit_config;
212 char *http_host;
213 char *https;
214 char *no_http;
215 char *path_info;
216 char *query_string;
217 char *request_method;
218 char *script_name;
219 char *server_name;
220 char *server_port;
184}; 221};
185 222
186struct cgit_context { 223struct cgit_context {
224 struct cgit_environment env;
187 struct cgit_query qry; 225 struct cgit_query qry;
188 struct cgit_config cfg; 226 struct cgit_config cfg;
189 struct cgit_repo *repo; 227 struct cgit_repo *repo;
@@ -242,5 +280,9 @@ extern const char *cgit_repobasename(const char *reponame);
242 280
243extern int cgit_parse_snapshots_mask(const char *str); 281extern int cgit_parse_snapshots_mask(const char *str);
244 282
283extern int cgit_open_filter(struct cgit_filter *filter);
284extern int cgit_close_filter(struct cgit_filter *filter);
285
286extern int readfile(const char *path, char **buf, size_t *size);
245 287
246#endif /* CGIT_H */ 288#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index fd299ae..0d18290 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,203 +1,247 @@
1CGITRC 1CGITRC(5)
2====== 2========
3 3
4 4
5NAME 5NAME
6---- 6----
7 cgitrc - runtime configuration for cgit 7cgitrc - runtime configuration for cgit
8 8
9 9
10DESCRIPTION 10SYNOPSIS
11----------- 11--------
12Cgitrc contains all runtime settings for cgit, including the list of git 12Cgitrc contains all runtime settings for cgit, including the list of git
13repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank 13repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
14lines, and lines starting with '#', are ignored. 14lines, and lines starting with '#', are ignored.
15 15
16 16
17LOCATION
18--------
19The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
20runtime, cgit will consult the environment variable CGIT_CONFIG and, if
21defined, use its value instead.
22
23
17GLOBAL SETTINGS 24GLOBAL SETTINGS
18--------------- 25---------------
19agefile 26about-filter::
27 Specifies a command which will be invoked to format the content of
28 about pages (both top-level and for each repository). The command will
29 get the content of the about-file on its STDIN, and the STDOUT from the
30 command will be included verbatim on the about page. Default value:
31 none.
32
33agefile::
20 Specifies a path, relative to each repository path, which can be used 34 Specifies a path, relative to each repository path, which can be used
21 to specify the date and time of the youngest commit in the repository. 35 to specify the date and time of the youngest commit in the repository.
22 The first line in the file is used as input to the "parse_date" 36 The first line in the file is used as input to the "parse_date"
23 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 37 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
24 hh:mm:ss". Default value: "info/web/last-modified". 38 hh:mm:ss". Default value: "info/web/last-modified".
25 39
26cache-root 40cache-root::
27 Path used to store the cgit cache entries. Default value: 41 Path used to store the cgit cache entries. Default value:
28 "/var/cache/cgit". 42 "/var/cache/cgit".
29 43
30cache-dynamic-ttl 44cache-dynamic-ttl::
31 Number which specifies the time-to-live, in minutes, for the cached 45 Number which specifies the time-to-live, in minutes, for the cached
32 version of repository pages accessed without a fixed SHA1. Default 46 version of repository pages accessed without a fixed SHA1. Default
33 value: "5". 47 value: "5".
34 48
35cache-repo-ttl 49cache-repo-ttl::
36 Number which specifies the time-to-live, in minutes, for the cached 50 Number which specifies the time-to-live, in minutes, for the cached
37 version of the repository summary page. Default value: "5". 51 version of the repository summary page. Default value: "5".
38 52
39cache-root-ttl 53cache-root-ttl::
40 Number which specifies the time-to-live, in minutes, for the cached 54 Number which specifies the time-to-live, in minutes, for the cached
41 version of the repository index page. Default value: "5". 55 version of the repository index page. Default value: "5".
42 56
43cache-size 57cache-size::
44 The maximum number of entries in the cgit cache. Default value: "0" 58 The maximum number of entries in the cgit cache. Default value: "0"
45 (i.e. caching is disabled). 59 (i.e. caching is disabled).
46 60
47cache-static-ttl 61cache-static-ttl::
48 Number which specifies the time-to-live, in minutes, for the cached 62 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed with a fixed SHA1. Default value: 63 version of repository pages accessed with a fixed SHA1. Default value:
50 "5". 64 "5".
51 65
52clone-prefix 66clone-prefix::
53 Space-separated list of common prefixes which, when combined with a 67 Space-separated list of common prefixes which, when combined with a
54 repository url, generates valid clone urls for the repository. This 68 repository url, generates valid clone urls for the repository. This
55 setting is only used if `repo.clone-url` is unspecified. Default value: 69 setting is only used if `repo.clone-url` is unspecified. Default value:
56 none. 70 none.
57 71
58css 72commit-filter::
73 Specifies a command which will be invoked to format commit messages.
74 The command will get the message on its STDIN, and the STDOUT from the
75 command will be included verbatim as the commit message, i.e. this can
76 be used to implement bugtracker integration. Default value: none.
77
78css::
59 Url which specifies the css document to include in all cgit pages. 79 Url which specifies the css document to include in all cgit pages.
60 Default value: "/cgit.css". 80 Default value: "/cgit.css".
61 81
62enable-index-links 82embedded::
83 Flag which, when set to "1", will make cgit generate a html fragment
84 suitable for embedding in other html pages. Default value: none. See
85 also: "noheader".
86
87enable-index-links::
63 Flag which, when set to "1", will make cgit generate extra links for 88 Flag which, when set to "1", will make cgit generate extra links for
64 each repo in the repository index (specifically, to the "summary", 89 each repo in the repository index (specifically, to the "summary",
65 "commit" and "tree" pages). Default value: "0". 90 "commit" and "tree" pages). Default value: "0".
66 91
67enable-log-filecount 92enable-log-filecount::
68 Flag which, when set to "1", will make cgit print the number of 93 Flag which, when set to "1", will make cgit print the number of
69 modified files for each commit on the repository log page. Default 94 modified files for each commit on the repository log page. Default
70 value: "0". 95 value: "0".
71 96
72enable-log-linecount 97enable-log-linecount::
73 Flag which, when set to "1", will make cgit print the number of added 98 Flag which, when set to "1", will make cgit print the number of added
74 and removed lines for each commit on the repository log page. Default 99 and removed lines for each commit on the repository log page. Default
75 value: "0". 100 value: "0".
76 101
77favicon 102favicon::
78 Url used as link to a shortcut icon for cgit. If specified, it is 103 Url used as link to a shortcut icon for cgit. If specified, it is
79 suggested to use the value "/favicon.ico" since certain browsers will 104 suggested to use the value "/favicon.ico" since certain browsers will
80 ignore other values. Default value: none. 105 ignore other values. Default value: none.
81 106
82footer 107footer::
83 The content of the file specified with this option will be included 108 The content of the file specified with this option will be included
84 verbatim at the bottom of all pages (i.e. it replaces the standard 109 verbatim at the bottom of all pages (i.e. it replaces the standard
85 "generated by..." message. Default value: none. 110 "generated by..." message. Default value: none.
86 111
87header 112head-include::
113 The content of the file specified with this option will be included
114 verbatim in the html HEAD section on all pages. Default value: none.
115
116header::
88 The content of the file specified with this option will be included 117 The content of the file specified with this option will be included
89 verbatim at the top of all pages. Default value: none. 118 verbatim at the top of all pages. Default value: none.
90 119
91include 120include::
92 Name of a configfile to include before the rest of the current config- 121 Name of a configfile to include before the rest of the current config-
93 file is parsed. Default value: none. 122 file is parsed. Default value: none.
94 123
95index-header 124index-header::
96 The content of the file specified with this option will be included 125 The content of the file specified with this option will be included
97 verbatim above the repository index. This setting is deprecated, and 126 verbatim above the repository index. This setting is deprecated, and
98 will not be supported by cgit-1.0 (use root-readme instead). Default 127 will not be supported by cgit-1.0 (use root-readme instead). Default
99 value: none. 128 value: none.
100 129
101index-info 130index-info::
102 The content of the file specified with this option will be included 131 The content of the file specified with this option will be included
103 verbatim below the heading on the repository index page. This setting 132 verbatim below the heading on the repository index page. This setting
104 is deprecated, and will not be supported by cgit-1.0 (use root-desc 133 is deprecated, and will not be supported by cgit-1.0 (use root-desc
105 instead). Default value: none. 134 instead). Default value: none.
106 135
107local-time 136local-time::
108 Flag which, if set to "1", makes cgit print commit and tag times in the 137 Flag which, if set to "1", makes cgit print commit and tag times in the
109 servers timezone. Default value: "0". 138 servers timezone. Default value: "0".
110 139
111logo 140logo::
112 Url which specifies the source of an image which will be used as a logo 141 Url which specifies the source of an image which will be used as a logo
113 on all cgit pages. 142 on all cgit pages. Default value: "/cgit.png".
114 143
115logo-link 144logo-link::
116 Url loaded when clicking on the cgit logo image. If unspecified the 145 Url loaded when clicking on the cgit logo image. If unspecified the
117 calculated url of the repository index page will be used. Default 146 calculated url of the repository index page will be used. Default
118 value: none. 147 value: none.
119 148
120max-commit-count 149max-commit-count::
121 Specifies the number of entries to list per page in "log" view. Default 150 Specifies the number of entries to list per page in "log" view. Default
122 value: "50". 151 value: "50".
123 152
124max-message-length 153max-message-length::
125 Specifies the maximum number of commit message characters to display in 154 Specifies the maximum number of commit message characters to display in
126 "log" view. Default value: "80". 155 "log" view. Default value: "80".
127 156
128max-repo-count 157max-repo-count::
129 Specifies the number of entries to list per page on the repository 158 Specifies the number of entries to list per page on the repository
130 index page. Default value: "50". 159 index page. Default value: "50".
131 160
132max-repodesc-length 161max-repodesc-length::
133 Specifies the maximum number of repo description characters to display 162 Specifies the maximum number of repo description characters to display
134 on the repository index page. Default value: "80". 163 on the repository index page. Default value: "80".
135 164
136max-stats 165max-stats::
137 Set the default maximum statistics period. Valid values are "week", 166 Set the default maximum statistics period. Valid values are "week",
138 "month", "quarter" and "year". If unspecified, statistics are 167 "month", "quarter" and "year". If unspecified, statistics are
139 disabled. Default value: none. See also: "repo.max-stats". 168 disabled. Default value: none. See also: "repo.max-stats".
140 169
141module-link 170mimetype.<ext>::
171 Set the mimetype for the specified filename extension. This is used
172 by the `plain` command when returning blob content.
173
174module-link::
142 Text which will be used as the formatstring for a hyperlink when a 175 Text which will be used as the formatstring for a hyperlink when a
143 submodule is printed in a directory listing. The arguments for the 176 submodule is printed in a directory listing. The arguments for the
144 formatstring are the path and SHA1 of the submodule commit. Default 177 formatstring are the path and SHA1 of the submodule commit. Default
145 value: "./?repo=%s&page=commit&id=%s" 178 value: "./?repo=%s&page=commit&id=%s"
146 179
147nocache 180nocache::
148 If set to the value "1" caching will be disabled. This settings is 181 If set to the value "1" caching will be disabled. This settings is
149 deprecated, and will not be honored starting with cgit-1.0. Default 182 deprecated, and will not be honored starting with cgit-1.0. Default
150 value: "0". 183 value: "0".
151 184
152renamelimit 185noplainemail::
186 If set to "1" showing full author email adresses will be disabled.
187 Default value: "0".
188
189noheader::
190 Flag which, when set to "1", will make cgit omit the standard header
191 on all pages. Default value: none. See also: "embedded".
192
193renamelimit::
153 Maximum number of files to consider when detecting renames. The value 194 Maximum number of files to consider when detecting renames. The value
154 "-1" uses the compiletime value in git (for further info, look at 195 "-1" uses the compiletime value in git (for further info, look at
155 `man git-diff`). Default value: "-1". 196 `man git-diff`). Default value: "-1".
156 197
157repo.group 198repo.group::
158 A value for the current repository group, which all repositories 199 A value for the current repository group, which all repositories
159 specified after this setting will inherit. Default value: none. 200 specified after this setting will inherit. Default value: none.
160 201
161robots 202robots::
162 Text used as content for the "robots" meta-tag. Default value: 203 Text used as content for the "robots" meta-tag. Default value:
163 "index, nofollow". 204 "index, nofollow".
164 205
165root-desc 206root-desc::
166 Text printed below the heading on the repository index page. Default 207 Text printed below the heading on the repository index page. Default
167 value: "a fast webinterface for the git dscm". 208 value: "a fast webinterface for the git dscm".
168 209
169root-readme: 210root-readme::
170 The content of the file specified with this option will be included 211 The content of the file specified with this option will be included
171 verbatim below the "about" link on the repository index page. Default 212 verbatim below the "about" link on the repository index page. Default
172 value: none. 213 value: none.
173 214
174root-title 215root-title::
175 Text printed as heading on the repository index page. Default value: 216 Text printed as heading on the repository index page. Default value:
176 "Git Repository Browser". 217 "Git Repository Browser".
177 218
178snapshots 219snapshots::
179 Text which specifies the default (and allowed) set of snapshot formats 220 Text which specifies the default set of snapshot formats generated by
180 supported by cgit. The value is a space-separated list of zero or more 221 cgit. The value is a space-separated list of zero or more of the
181 of the following values: 222 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
182 "tar" uncompressed tar-file 223
183 "tar.gz" gzip-compressed tar-file 224source-filter::
184 "tar.bz2" bzip-compressed tar-file 225 Specifies a command which will be invoked to format plaintext blobs
185 "zip" zip-file 226 in the tree view. The command will get the blob content on its STDIN
186 Default value: none. 227 and the name of the blob as its only command line argument. The STDOUT
228 from the command will be included verbatim as the blob contents, i.e.
229 this can be used to implement e.g. syntax highlighting. Default value:
230 none.
187 231
188summary-branches 232summary-branches::
189 Specifies the number of branches to display in the repository "summary" 233 Specifies the number of branches to display in the repository "summary"
190 view. Default value: "10". 234 view. Default value: "10".
191 235
192summary-log 236summary-log::
193 Specifies the number of log entries to display in the repository 237 Specifies the number of log entries to display in the repository
194 "summary" view. Default value: "10". 238 "summary" view. Default value: "10".
195 239
196summary-tags 240summary-tags::
197 Specifies the number of tags to display in the repository "summary" 241 Specifies the number of tags to display in the repository "summary"
198 view. Default value: "10". 242 view. Default value: "10".
199 243
200virtual-root 244virtual-root::
201 Url which, if specified, will be used as root for all cgit links. It 245 Url which, if specified, will be used as root for all cgit links. It
202 will also cause cgit to generate 'virtual urls', i.e. urls like 246 will also cause cgit to generate 'virtual urls', i.e. urls like
203 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 247 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
@@ -207,51 +251,60 @@ virtual-root
207 251
208REPOSITORY SETTINGS 252REPOSITORY SETTINGS
209------------------- 253-------------------
210repo.clone-url 254repo.about-filter::
255 Override the default about-filter. Default value: <about-filter>.
256
257repo.clone-url::
211 A list of space-separated urls which can be used to clone this repo. 258 A list of space-separated urls which can be used to clone this repo.
212 Default value: none. 259 Default value: none.
213 260
214repo.defbranch 261repo.commit-filter::
262 Override the default commit-filter. Default value: <commit-filter>.
263
264repo.defbranch::
215 The name of the default branch for this repository. If no such branch 265 The name of the default branch for this repository. If no such branch
216 exists in the repository, the first branch name (when sorted) is used 266 exists in the repository, the first branch name (when sorted) is used
217 as default instead. Default value: "master". 267 as default instead. Default value: "master".
218 268
219repo.desc 269repo.desc::
220 The value to show as repository description. Default value: none. 270 The value to show as repository description. Default value: none.
221 271
222repo.enable-log-filecount 272repo.enable-log-filecount::
223 A flag which can be used to disable the global setting 273 A flag which can be used to disable the global setting
224 `enable-log-filecount'. Default value: none. 274 `enable-log-filecount'. Default value: none.
225 275
226repo.enable-log-linecount 276repo.enable-log-linecount::
227 A flag which can be used to disable the global setting 277 A flag which can be used to disable the global setting
228 `enable-log-linecount'. Default value: none. 278 `enable-log-linecount'. Default value: none.
229 279
230repo.max-stats 280repo.max-stats::
231 Override the default maximum statistics period. Valid values are equal 281 Override the default maximum statistics period. Valid values are equal
232 to the values specified for the global "max-stats" setting. Default 282 to the values specified for the global "max-stats" setting. Default
233 value: none. 283 value: none.
234 284
235repo.name 285repo.name::
236 The value to show as repository name. Default value: <repo.url>. 286 The value to show as repository name. Default value: <repo.url>.
237 287
238repo.owner 288repo.owner::
239 A value used to identify the owner of the repository. Default value: 289 A value used to identify the owner of the repository. Default value:
240 none. 290 none.
241 291
242repo.path 292repo.path::
243 An absolute path to the repository directory. For non-bare repositories 293 An absolute path to the repository directory. For non-bare repositories
244 this is the .git-directory. Default value: none. 294 this is the .git-directory. Default value: none.
245 295
246repo.readme 296repo.readme::
247 A path (relative to <repo.path>) which specifies a file to include 297 A path (relative to <repo.path>) which specifies a file to include
248 verbatim as the "About" page for this repo. Default value: none. 298 verbatim as the "About" page for this repo. Default value: none.
249 299
250repo.snapshots 300repo.snapshots::
251 A mask of allowed snapshot-formats for this repo, restricted by the 301 A mask of allowed snapshot-formats for this repo, restricted by the
252 "snapshots" global setting. Default value: <snapshots>. 302 "snapshots" global setting. Default value: <snapshots>.
253 303
254repo.url 304repo.source-filter::
305 Override the default source-filter. Default value: <source-filter>.
306
307repo.url::
255 The relative url used to access the repository. This must be the first 308 The relative url used to access the repository. This must be the first
256 setting specified for each repo. Default value: none. 309 setting specified for each repo. Default value: none.
257 310
@@ -259,6 +312,7 @@ repo.url
259EXAMPLE CGITRC FILE 312EXAMPLE CGITRC FILE
260------------------- 313-------------------
261 314
315....
262# Enable caching of up to 1000 output entriess 316# Enable caching of up to 1000 output entriess
263cache-size=1000 317cache-size=1000
264 318
@@ -311,6 +365,19 @@ snapshots=tar.gz tar.bz2 zip
311 365
312 366
313## 367##
368## List of common mimetypes
369##
370
371mimetype.git=image/git
372mimetype.html=text/html
373mimetype.jpg=image/jpeg
374mimetype.jpeg=image/jpeg
375mimetype.pdf=application/pdf
376mimetype.png=image/png
377mimetype.svg=image/svg+xml
378
379
380##
314## List of repositories. 381## List of repositories.
315## PS: Any repositories listed when repo.group is unset will not be 382## PS: Any repositories listed when repo.group is unset will not be
316## displayed under a group heading 383## displayed under a group heading
@@ -368,6 +435,7 @@ repo.enable-log-linecount=0
368 435
369# Restrict the max statistics period for this repo 436# Restrict the max statistics period for this repo
370repo.max-stats=month 437repo.max-stats=month
438....
371 439
372 440
373BUGS 441BUGS
diff --git a/cmd.c b/cmd.c
index cf97da7..766f903 100644
--- a/cmd.c
+++ b/cmd.c
@@ -39,7 +39,7 @@ static void atom_fn(struct cgit_context *ctx)
39static void about_fn(struct cgit_context *ctx) 39static void about_fn(struct cgit_context *ctx)
40{ 40{
41 if (ctx->repo) 41 if (ctx->repo)
42 cgit_print_repo_readme(); 42 cgit_print_repo_readme(ctx->qry.path);
43 else 43 else
44 cgit_print_site_readme(); 44 cgit_print_site_readme();
45} 45}
diff --git a/filters/commit-links.sh b/filters/commit-links.sh
new file mode 100755
index 0000000..165a533
--- /dev/null
+++ b/filters/commit-links.sh
@@ -0,0 +1,12 @@
1#!/bin/sh
2# This script can be used to generate links in commit messages - the first
3# sed expression generates links to commits referenced by their SHA1, while
4# the second expression generates links to a fictional bugtracker.
5#
6# To use this script, refer to this file with either the commit-filter or the
7# repo.commit-filter options in cgitrc.
8
9sed -re '
10s|\b([0-9a-fA-F]{8,40})\b|<a href="./?id=\1">\1</a>|g
11s| #([0-9]+)\b|<a href="http://bugs.example.com/?bug=\1">#\1</a>|g
12'
diff --git a/filters/syntax-highlighting.sh b/filters/syntax-highlighting.sh
new file mode 100755
index 0000000..999ad0c
--- /dev/null
+++ b/filters/syntax-highlighting.sh
@@ -0,0 +1,39 @@
1#!/bin/sh
2# This script can be used to implement syntax highlighting in the cgit
3# tree-view by refering to this file with the source-filter or repo.source-
4# filter options in cgitrc.
5#
6# 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
8# in your css file (generated by highlight 2.4.8 and adapted for cgit):
9#
10# table.blob .num { color:#2928ff; }
11# table.blob .esc { color:#ff00ff; }
12# table.blob .str { color:#ff0000; }
13# table.blob .dstr { color:#818100; }
14# table.blob .slc { color:#838183; font-style:italic; }
15# table.blob .com { color:#838183; font-style:italic; }
16# table.blob .dir { color:#008200; }
17# table.blob .sym { color:#000000; }
18# table.blob .kwa { color:#000000; font-weight:bold; }
19# table.blob .kwb { color:#830000; }
20# table.blob .kwc { color:#000000; font-weight:bold; }
21# table.blob .kwd { color:#010181; }
22
23case "$1" in
24 *.c)
25 highlight -f -I -X -S c
26 ;;
27 *.h)
28 highlight -f -I -X -S c
29 ;;
30 *.sh)
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 5c415311f743ccb11a50f350ff1c385778f049d Subproject e276f018f2c1f0fc962fbe44a36708d1cdebada
diff --git a/scan-tree.c b/scan-tree.c
index 47f3988..4da21a4 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -35,25 +35,13 @@ static int is_git_dir(const char *path)
35 return 1; 35 return 1;
36} 36}
37 37
38char *readfile(const char *path)
39{
40 FILE *f;
41 static char buf[MAX_PATH];
42
43 if (!(f = fopen(path, "r")))
44 return NULL;
45 buf[0] = 0;
46 fgets(buf, MAX_PATH, f);
47 fclose(f);
48 return buf;
49}
50
51static void add_repo(const char *base, const char *path) 38static void add_repo(const char *base, const char *path)
52{ 39{
53 struct cgit_repo *repo; 40 struct cgit_repo *repo;
54 struct stat st; 41 struct stat st;
55 struct passwd *pwd; 42 struct passwd *pwd;
56 char *p; 43 char *p;
44 size_t size;
57 45
58 if (stat(path, &st)) { 46 if (stat(path, &st)) {
59 fprintf(stderr, "Error accessing %s: %s (%d)\n", 47 fprintf(stderr, "Error accessing %s: %s (%d)\n",
@@ -76,11 +64,14 @@ static void add_repo(const char *base, const char *path)
76 repo = cgit_add_repo(xstrdup(p)); 64 repo = cgit_add_repo(xstrdup(p));
77 repo->name = repo->url; 65 repo->name = repo->url;
78 repo->path = xstrdup(path); 66 repo->path = xstrdup(path);
67 p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL;
68 if (p)
69 *p = '\0';
79 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : ""); 70 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : "");
80 71
81 p = fmt("%s/description", path); 72 p = fmt("%s/description", path);
82 if (!stat(p, &st)) 73 if (!stat(p, &st))
83 repo->desc = xstrdup(readfile(p)); 74 readfile(p, &repo->desc, &size);
84 75
85 p = fmt("%s/README.html", path); 76 p = fmt("%s/README.html", path);
86 if (!stat(p, &st)) 77 if (!stat(p, &st))
diff --git a/shared.c b/shared.c
index cce0af4..4cb9573 100644
--- a/shared.c
+++ b/shared.c
@@ -62,6 +62,9 @@ struct cgit_repo *cgit_add_repo(const char *url)
62 ret->module_link = ctx.cfg.module_link; 62 ret->module_link = ctx.cfg.module_link;
63 ret->readme = NULL; 63 ret->readme = NULL;
64 ret->mtime = -1; 64 ret->mtime = -1;
65 ret->about_filter = ctx.cfg.about_filter;
66 ret->commit_filter = ctx.cfg.commit_filter;
67 ret->source_filter = ctx.cfg.source_filter;
65 return ret; 68 return ret;
66} 69}
67 70
@@ -355,3 +358,59 @@ int cgit_parse_snapshots_mask(const char *str)
355 } 358 }
356 return rv; 359 return rv;
357} 360}
361
362int cgit_open_filter(struct cgit_filter *filter)
363{
364
365 filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
366 "Unable to duplicate STDOUT");
367 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess");
368 filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
369 if (filter->pid == 0) {
370 close(filter->pipe_fh[1]);
371 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO),
372 "Unable to use pipe as STDIN");
373 execvp(filter->cmd, filter->argv);
374 die("Unable to exec subprocess %s: %s (%d)", filter->cmd,
375 strerror(errno), errno);
376 }
377 close(filter->pipe_fh[0]);
378 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO),
379 "Unable to use pipe as STDOUT");
380 close(filter->pipe_fh[1]);
381 return 0;
382}
383
384int cgit_close_filter(struct cgit_filter *filter)
385{
386 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
387 "Unable to restore STDOUT");
388 close(filter->old_stdout);
389 if (filter->pid < 0)
390 return 0;
391 waitpid(filter->pid, &filter->exitstatus, 0);
392 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus))
393 return 0;
394 die("Subprocess %s exited abnormally", filter->cmd);
395}
396
397/* Read the content of the specified file into a newly allocated buffer,
398 * zeroterminate the buffer and return 0 on success, errno otherwise.
399 */
400int readfile(const char *path, char **buf, size_t *size)
401{
402 int fd;
403 struct stat st;
404
405 fd = open(path, O_RDONLY);
406 if (fd == -1)
407 return errno;
408 if (fstat(fd, &st))
409 return errno;
410 if (!S_ISREG(st.st_mode))
411 return EISDIR;
412 *buf = xmalloc(st.st_size + 1);
413 *size = read_in_full(fd, *buf, st.st_size);
414 (*buf)[*size] = '\0';
415 return (*size == st.st_size ? 0 : errno);
416}
diff --git a/ui-atom.c b/ui-atom.c
index a6ea3ee..808b2d0 100644
--- a/ui-atom.c
+++ b/ui-atom.c
@@ -32,7 +32,7 @@ void add_entry(struct commit *commit, char *host)
32 html_txt(info->author); 32 html_txt(info->author);
33 html("</name>\n"); 33 html("</name>\n");
34 } 34 }
35 if (info->author_email) { 35 if (info->author_email && !ctx.cfg.noplainemail) {
36 mail = xstrdup(info->author_email); 36 mail = xstrdup(info->author_email);
37 t = strchr(mail, '<'); 37 t = strchr(mail, '<');
38 if (t) 38 if (t)
@@ -52,7 +52,8 @@ void add_entry(struct commit *commit, char *host)
52 cgit_print_date(info->author_date, FMT_ATOMDATE, ctx.cfg.local_time); 52 cgit_print_date(info->author_date, FMT_ATOMDATE, ctx.cfg.local_time);
53 html("</published>\n"); 53 html("</published>\n");
54 if (host) { 54 if (host) {
55 html("<link rel='alternate' type='text/html' href='http://"); 55 html("<link rel='alternate' type='text/html' href='");
56 html(cgit_httpscheme());
56 html_attr(host); 57 html_attr(host);
57 html_attr(cgit_pageurl(ctx.repo->url, "commit", NULL)); 58 html_attr(cgit_pageurl(ctx.repo->url, "commit", NULL));
58 if (ctx.cfg.virtual_root) 59 if (ctx.cfg.virtual_root)
@@ -113,7 +114,8 @@ void cgit_print_atom(char *tip, char *path, int max_count)
113 html_txt(ctx.repo->desc); 114 html_txt(ctx.repo->desc);
114 html("</subtitle>\n"); 115 html("</subtitle>\n");
115 if (host) { 116 if (host) {
116 html("<link rel='alternate' type='text/html' href='http://"); 117 html("<link rel='alternate' type='text/html' href='");
118 html(cgit_httpscheme());
117 html_attr(host); 119 html_attr(host);
118 html_attr(cgit_repourl(ctx.repo->url)); 120 html_attr(cgit_repourl(ctx.repo->url));
119 html("'/>\n"); 121 html("'/>\n");
diff --git a/ui-blob.c b/ui-blob.c
index 3cda03d..2ccd31d 100644
--- a/ui-blob.c
+++ b/ui-blob.c
@@ -27,7 +27,7 @@ void cgit_print_blob(const char *hex, char *path, const char *head)
27 27
28 unsigned char sha1[20]; 28 unsigned char sha1[20];
29 enum object_type type; 29 enum object_type type;
30 unsigned char *buf; 30 char *buf;
31 unsigned long size; 31 unsigned long size;
32 struct commit *commit; 32 struct commit *commit;
33 const char *paths[] = {path, NULL}; 33 const char *paths[] = {path, NULL};
@@ -67,6 +67,12 @@ void cgit_print_blob(const char *hex, char *path, const char *head)
67 67
68 buf[size] = '\0'; 68 buf[size] = '\0';
69 ctx.page.mimetype = ctx.qry.mimetype; 69 ctx.page.mimetype = ctx.qry.mimetype;
70 if (!ctx.page.mimetype) {
71 if (buffer_is_binary(buf, size))
72 ctx.page.mimetype = "application/octet-stream";
73 else
74 ctx.page.mimetype = "text/plain";
75 }
70 ctx.page.filename = path; 76 ctx.page.filename = path;
71 cgit_print_http_headers(&ctx); 77 cgit_print_http_headers(&ctx);
72 write(htmlfd, buf, size); 78 write(htmlfd, buf, size);
diff --git a/ui-commit.c b/ui-commit.c
index 41ce70e..d6b73ee 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -40,15 +40,19 @@ void cgit_print_commit(char *hex)
40 html("<table summary='commit info' class='commit-info'>\n"); 40 html("<table summary='commit info' class='commit-info'>\n");
41 html("<tr><th>author</th><td>"); 41 html("<tr><th>author</th><td>");
42 html_txt(info->author); 42 html_txt(info->author);
43 html(" "); 43 if (!ctx.cfg.noplainemail) {
44 html_txt(info->author_email); 44 html(" ");
45 html_txt(info->author_email);
46 }
45 html("</td><td class='right'>"); 47 html("</td><td class='right'>");
46 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time); 48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time);
47 html("</td></tr>\n"); 49 html("</td></tr>\n");
48 html("<tr><th>committer</th><td>"); 50 html("<tr><th>committer</th><td>");
49 html_txt(info->committer); 51 html_txt(info->committer);
50 html(" "); 52 if (!ctx.cfg.noplainemail) {
51 html_txt(info->committer_email); 53 html(" ");
54 html_txt(info->committer_email);
55 }
52 html("</td><td class='right'>"); 56 html("</td><td class='right'>");
53 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
54 html("</td></tr>\n"); 58 html("</td></tr>\n");
@@ -89,11 +93,19 @@ void cgit_print_commit(char *hex)
89 } 93 }
90 html("</table>\n"); 94 html("</table>\n");
91 html("<div class='commit-subject'>"); 95 html("<div class='commit-subject'>");
96 if (ctx.repo->commit_filter)
97 cgit_open_filter(ctx.repo->commit_filter);
92 html_txt(info->subject); 98 html_txt(info->subject);
99 if (ctx.repo->commit_filter)
100 cgit_close_filter(ctx.repo->commit_filter);
93 show_commit_decorations(commit); 101 show_commit_decorations(commit);
94 html("</div>"); 102 html("</div>");
95 html("<div class='commit-msg'>"); 103 html("<div class='commit-msg'>");
104 if (ctx.repo->commit_filter)
105 cgit_open_filter(ctx.repo->commit_filter);
96 html_txt(info->msg); 106 html_txt(info->msg);
107 if (ctx.repo->commit_filter)
108 cgit_close_filter(ctx.repo->commit_filter);
97 html("</div>"); 109 html("</div>");
98 if (parents < 3) { 110 if (parents < 3) {
99 if (parents) 111 if (parents)
diff --git a/ui-log.c b/ui-log.c
index ba2ab03..0b37785 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -53,6 +53,10 @@ void show_commit_decorations(struct commit *commit)
53 strncpy(buf, deco->name + 15, sizeof(buf) - 1); 53 strncpy(buf, deco->name + 15, sizeof(buf) - 1);
54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); 54 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
55 } 55 }
56 else if (!prefixcmp(deco->name, "refs/tags/")) {
57 strncpy(buf, deco->name + 10, sizeof(buf) - 1);
58 cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
59 }
56 else if (!prefixcmp(deco->name, "refs/remotes/")) { 60 else if (!prefixcmp(deco->name, "refs/remotes/")) {
57 strncpy(buf, deco->name + 13, sizeof(buf) - 1); 61 strncpy(buf, deco->name + 13, sizeof(buf) - 1);
58 cgit_log_link(buf, NULL, "remote-deco", NULL, 62 cgit_log_link(buf, NULL, "remote-deco", NULL,
diff --git a/ui-patch.c b/ui-patch.c
index 5d665d3..2a8f7a5 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -108,7 +108,11 @@ void cgit_print_patch(char *hex)
108 ctx.page.filename = patchname; 108 ctx.page.filename = patchname;
109 cgit_print_http_headers(&ctx); 109 cgit_print_http_headers(&ctx);
110 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1)); 110 htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1));
111 htmlf("From: %s %s\n", info->author, info->author_email); 111 htmlf("From: %s", info->author);
112 if (!ctx.cfg.noplainemail) {
113 htmlf(" %s", info->author_email);
114 }
115 html("\n");
112 html("Date: "); 116 html("Date: ");
113 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time); 117 cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time);
114 htmlf("Subject: %s\n\n", info->subject); 118 htmlf("Subject: %s\n\n", info->subject);
diff --git a/ui-plain.c b/ui-plain.c
index e08b15b..a4ce077 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -17,8 +17,9 @@ int match;
17static void print_object(const unsigned char *sha1, const char *path) 17static void print_object(const unsigned char *sha1, const char *path)
18{ 18{
19 enum object_type type; 19 enum object_type type;
20 char *buf; 20 char *buf, *ext;
21 unsigned long size; 21 unsigned long size;
22 struct string_list_item *mime;
22 23
23 type = sha1_object_info(sha1, &size); 24 type = sha1_object_info(sha1, &size);
24 if (type == OBJ_BAD) { 25 if (type == OBJ_BAD) {
@@ -31,9 +32,22 @@ static void print_object(const unsigned char *sha1, const char *path)
31 html_status(404, "Not found", 0); 32 html_status(404, "Not found", 0);
32 return; 33 return;
33 } 34 }
34 ctx.page.mimetype = "text/plain"; 35 ctx.page.mimetype = NULL;
36 ext = strrchr(path, '.');
37 if (ext && *(++ext)) {
38 mime = string_list_lookup(ext, &ctx.cfg.mimetypes);
39 if (mime)
40 ctx.page.mimetype = (char *)mime->util;
41 }
42 if (!ctx.page.mimetype) {
43 if (buffer_is_binary(buf, size))
44 ctx.page.mimetype = "application/octet-stream";
45 else
46 ctx.page.mimetype = "text/plain";
47 }
35 ctx.page.filename = fmt("%s", path); 48 ctx.page.filename = fmt("%s", path);
36 ctx.page.size = size; 49 ctx.page.size = size;
50 ctx.page.etag = sha1_to_hex(sha1);
37 cgit_print_http_headers(&ctx); 51 cgit_print_http_headers(&ctx);
38 html_raw(buf, size); 52 html_raw(buf, size);
39 match = 1; 53 match = 1;
diff --git a/ui-refs.c b/ui-refs.c
index 25da00a..d3b4f6e 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -46,8 +46,19 @@ static int cmp_tag_age(const void *a, const void *b)
46{ 46{
47 struct refinfo *r1 = *(struct refinfo **)a; 47 struct refinfo *r1 = *(struct refinfo **)a;
48 struct refinfo *r2 = *(struct refinfo **)b; 48 struct refinfo *r2 = *(struct refinfo **)b;
49 int r1date, r2date;
49 50
50 return cmp_age(r1->tag->tagger_date, r2->tag->tagger_date); 51 if (r1->object->type != OBJ_COMMIT)
52 r1date = r1->tag->tagger_date;
53 else
54 r1date = r1->commit->committer_date;
55
56 if (r2->object->type != OBJ_COMMIT)
57 r2date = r2->tag->tagger_date;
58 else
59 r2date = r2->commit->committer_date;
60
61 return cmp_age(r1date, r2date);
51} 62}
52 63
53static int print_branch(struct refinfo *ref) 64static int print_branch(struct refinfo *ref)
@@ -145,6 +156,12 @@ static int print_tag(struct refinfo *ref)
145 print_tag_downloads(ctx.repo, name); 156 print_tag_downloads(ctx.repo, name);
146 else 157 else
147 cgit_object_link(ref->object); 158 cgit_object_link(ref->object);
159 html("</td><td>");
160 if (ref->object->type == OBJ_COMMIT)
161 html(ref->commit->author);
162 html("</td><td colspan='2'>");
163 if (ref->object->type == OBJ_COMMIT)
164 cgit_print_age(ref->commit->commit->date, -1, NULL);
148 html("</td></tr>\n"); 165 html("</td></tr>\n");
149 } 166 }
150 return 0; 167 return 0;
diff --git a/ui-repolist.c b/ui-repolist.c
index 3aedde5..7c7aa9b 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -18,19 +18,20 @@
18 18
19time_t read_agefile(char *path) 19time_t read_agefile(char *path)
20{ 20{
21 FILE *f; 21 time_t result;
22 static char buf[64], buf2[64]; 22 size_t size;
23 char *buf;
24 static char buf2[64];
23 25
24 if (!(f = fopen(path, "r"))) 26 if (readfile(path, &buf, &size))
25 return -1; 27 return -1;
26 buf[0] = 0; 28
27 if (fgets(buf, sizeof(buf), f) == NULL)
28 return -1;
29 fclose(f);
30 if (parse_date(buf, buf2, sizeof(buf2))) 29 if (parse_date(buf, buf2, sizeof(buf2)))
31 return strtoul(buf2, NULL, 10); 30 result = strtoul(buf2, NULL, 10);
32 else 31 else
33 return 0; 32 result = 0;
33 free(buf);
34 return result;
34} 35}
35 36
36static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) 37static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime)
@@ -274,6 +275,11 @@ void cgit_print_repolist()
274 275
275void cgit_print_site_readme() 276void cgit_print_site_readme()
276{ 277{
277 if (ctx.cfg.root_readme) 278 if (!ctx.cfg.root_readme)
278 html_include(ctx.cfg.root_readme); 279 return;
280 if (ctx.cfg.about_filter)
281 cgit_open_filter(ctx.cfg.about_filter);
282 html_include(ctx.cfg.root_readme);
283 if (ctx.cfg.about_filter)
284 cgit_close_filter(ctx.cfg.about_filter);
279} 285}
diff --git a/ui-shared.c b/ui-shared.c
index 40060ba..07d5dd4 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -34,24 +34,23 @@ void cgit_print_error(char *msg)
34 html("</div>\n"); 34 html("</div>\n");
35} 35}
36 36
37char *cgit_hosturl() 37char *cgit_httpscheme()
38{ 38{
39 char *host, *port; 39 if (ctx.env.https && !strcmp(ctx.env.https, "on"))
40 return "https://";
41 else
42 return "http://";
43}
40 44
41 host = getenv("HTTP_HOST"); 45char *cgit_hosturl()
42 if (host) { 46{
43 host = xstrdup(host); 47 if (ctx.env.http_host)
44 } else { 48 return ctx.env.http_host;
45 host = getenv("SERVER_NAME"); 49 if (!ctx.env.server_name)
46 if (!host) 50 return NULL;
47 return NULL; 51 if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
48 port = getenv("SERVER_PORT"); 52 return ctx.env.server_name;
49 if (port && atoi(port) != 80) 53 return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port));
50 host = xstrdup(fmt("%s:%d", host, atoi(port)));
51 else
52 host = xstrdup(host);
53 }
54 return host;
55} 54}
56 55
57char *cgit_rooturl() 56char *cgit_rooturl()
@@ -456,6 +455,11 @@ void cgit_print_age(time_t t, time_t max_relative, char *format)
456 455
457void cgit_print_http_headers(struct cgit_context *ctx) 456void cgit_print_http_headers(struct cgit_context *ctx)
458{ 457{
458 if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1"))
459 return;
460
461 if (ctx->page.status)
462 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg);
459 if (ctx->page.mimetype && ctx->page.charset) 463 if (ctx->page.mimetype && ctx->page.charset)
460 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 464 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
461 ctx->page.charset); 465 ctx->page.charset);
@@ -468,11 +472,21 @@ void cgit_print_http_headers(struct cgit_context *ctx)
468 ctx->page.filename); 472 ctx->page.filename);
469 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 473 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
470 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 474 htmlf("Expires: %s\n", http_date(ctx->page.expires));
475 if (ctx->page.etag)
476 htmlf("ETag: \"%s\"\n", ctx->page.etag);
471 html("\n"); 477 html("\n");
478 if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD"))
479 exit(0);
472} 480}
473 481
474void cgit_print_docstart(struct cgit_context *ctx) 482void cgit_print_docstart(struct cgit_context *ctx)
475{ 483{
484 if (ctx->cfg.embedded) {
485 if (ctx->cfg.header)
486 html_include(ctx->cfg.header);
487 return;
488 }
489
476 char *host = cgit_hosturl(); 490 char *host = cgit_hosturl();
477 html(cgit_doctype); 491 html(cgit_doctype);
478 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 492 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
@@ -492,12 +506,15 @@ void cgit_print_docstart(struct cgit_context *ctx)
492 html("'/>\n"); 506 html("'/>\n");
493 } 507 }
494 if (host && ctx->repo) { 508 if (host && ctx->repo) {
495 html("<link rel='alternate' title='Atom feed' href='http://"); 509 html("<link rel='alternate' title='Atom feed' href='");
510 html(cgit_httpscheme());
496 html_attr(cgit_hosturl()); 511 html_attr(cgit_hosturl());
497 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 512 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path,
498 fmt("h=%s", ctx->qry.head))); 513 fmt("h=%s", ctx->qry.head)));
499 html("' type='application/atom+xml'/>"); 514 html("' type='application/atom+xml'/>\n");
500 } 515 }
516 if (ctx->cfg.head_include)
517 html_include(ctx->cfg.head_include);
501 html("</head>\n"); 518 html("</head>\n");
502 html("<body>\n"); 519 html("<body>\n");
503 if (ctx->cfg.header) 520 if (ctx->cfg.header)
@@ -506,7 +523,13 @@ void cgit_print_docstart(struct cgit_context *ctx)
506 523
507void cgit_print_docend() 524void cgit_print_docend()
508{ 525{
509 html("</div>"); 526 html("</div> <!-- class=content -->\n");
527 if (ctx.cfg.embedded) {
528 html("</div> <!-- id=cgit -->\n");
529 if (ctx.cfg.footer)
530 html_include(ctx.cfg.footer);
531 return;
532 }
510 if (ctx.cfg.footer) 533 if (ctx.cfg.footer)
511 html_include(ctx.cfg.footer); 534 html_include(ctx.cfg.footer);
512 else { 535 else {
@@ -515,6 +538,7 @@ void cgit_print_docend()
515 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); 538 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
516 html("</div>\n"); 539 html("</div>\n");
517 } 540 }
541 html("</div> <!-- id=cgit -->\n");
518 html("</body>\n</html>\n"); 542 html("</body>\n</html>\n");
519} 543}
520 544
@@ -602,13 +626,8 @@ char *hc(struct cgit_cmd *cmd, const char *page)
602 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); 626 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active");
603} 627}
604 628
605void cgit_print_pageheader(struct cgit_context *ctx) 629static void print_header(struct cgit_context *ctx)
606{ 630{
607 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
608
609 if (!cmd && ctx->repo)
610 fallback_cmd = "summary";
611
612 html("<table id='header'>\n"); 631 html("<table id='header'>\n");
613 html("<tr>\n"); 632 html("<tr>\n");
614 633
@@ -652,6 +671,18 @@ void cgit_print_pageheader(struct cgit_context *ctx)
652 html_include(ctx->cfg.index_info); 671 html_include(ctx->cfg.index_info);
653 } 672 }
654 html("</td></tr></table>\n"); 673 html("</td></tr></table>\n");
674}
675
676void cgit_print_pageheader(struct cgit_context *ctx)
677{
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'>");
684 if (!ctx->cfg.noheader)
685 print_header(ctx);
655 686
656 html("<table class='tabs'><tr><td>\n"); 687 html("<table class='tabs'><tr><td>\n");
657 if (ctx->repo) { 688 if (ctx->repo) {
diff --git a/ui-shared.h b/ui-shared.h
index 5a3821f..bff4826 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,6 +1,7 @@
1#ifndef UI_SHARED_H 1#ifndef UI_SHARED_H
2#define UI_SHARED_H 2#define UI_SHARED_H
3 3
4extern char *cgit_httpscheme();
4extern char *cgit_hosturl(); 5extern char *cgit_hosturl();
5extern char *cgit_repourl(const char *reponame); 6extern char *cgit_repourl(const char *reponame);
6extern char *cgit_fileurl(const char *reponame, const char *pagename, 7extern char *cgit_fileurl(const char *reponame, const char *pagename,
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 5372f5d..4136b3e 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -12,37 +12,16 @@
12 12
13static int write_compressed_tar_archive(struct archiver_args *args,const char *filter) 13static int write_compressed_tar_archive(struct archiver_args *args,const char *filter)
14{ 14{
15 int rw[2];
16 pid_t gzpid;
17 int stdout2;
18 int status;
19 int rv; 15 int rv;
16 struct cgit_filter f;
20 17
21 stdout2 = chk_non_negative(dup(STDIN_FILENO), "Preserving STDOUT before compressing"); 18 f.cmd = xstrdup(filter);
22 chk_zero(pipe(rw), "Opening pipe from compressor subprocess"); 19 f.argv = malloc(2 * sizeof(char *));
23 gzpid = chk_non_negative(fork(), "Forking compressor subprocess"); 20 f.argv[0] = f.cmd;
24 if(gzpid==0) { 21 f.argv[1] = NULL;
25 /* child */ 22 cgit_open_filter(&f);
26 chk_zero(close(rw[1]), "Closing write end of pipe in child");
27 chk_zero(close(STDIN_FILENO), "Closing STDIN");
28 chk_non_negative(dup2(rw[0],STDIN_FILENO), "Redirecting compressor input to stdin");
29 execlp(filter,filter,NULL);
30 _exit(-1);
31 }
32 /* parent */
33 chk_zero(close(rw[0]), "Closing read end of pipe");
34 chk_non_negative(dup2(rw[1],STDOUT_FILENO), "Redirecting output to compressor");
35
36 rv = write_tar_archive(args); 23 rv = write_tar_archive(args);
37 24 cgit_close_filter(&f);
38 chk_zero(close(STDOUT_FILENO), "Closing STDOUT redirected to compressor");
39 chk_non_negative(dup2(stdout2,STDOUT_FILENO), "Restoring uncompressed STDOUT");
40 chk_zero(close(stdout2), "Closing uncompressed STDOUT");
41 chk_zero(close(rw[1]), "Closing write end of pipe in parent");
42 chk_positive(waitpid(gzpid,&status,0), "Waiting on compressor process");
43 if(! ( WIFEXITED(status) && WEXITSTATUS(status)==0 ) )
44 cgit_print_error("Failed to compress archive");
45
46 return rv; 25 return rv;
47} 26}
48 27
diff --git a/ui-summary.c b/ui-summary.c
index ede4a62..a2c018e 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -66,11 +66,27 @@ void cgit_print_summary()
66 html("</table>"); 66 html("</table>");
67} 67}
68 68
69void cgit_print_repo_readme() 69void cgit_print_repo_readme(char *path)
70{ 70{
71 if (ctx.repo->readme) { 71 char *slash, *tmp;
72 html("<div id='summary'>"); 72
73 html_include(ctx.repo->readme); 73 if (!ctx.repo->readme)
74 html("</div>"); 74 return;
75 } 75
76 if (path) {
77 slash = strrchr(ctx.repo->readme, '/');
78 if (!slash)
79 return;
80 tmp = xmalloc(slash - ctx.repo->readme + 1 + strlen(path) + 1);
81 strncpy(tmp, ctx.repo->readme, slash - ctx.repo->readme + 1);
82 strcpy(tmp + (slash - ctx.repo->readme + 1), path);
83 } else
84 tmp = ctx.repo->readme;
85 html("<div id='summary'>");
86 if (ctx.repo->about_filter)
87 cgit_open_filter(ctx.repo->about_filter);
88 html_include(tmp);
89 if (ctx.repo->about_filter)
90 cgit_close_filter(ctx.repo->about_filter);
91 html("</div>");
76} 92}
diff --git a/ui-summary.h b/ui-summary.h
index 3e13039..c01f560 100644
--- a/ui-summary.h
+++ b/ui-summary.h
@@ -2,6 +2,6 @@
2#define UI_SUMMARY_H 2#define UI_SUMMARY_H
3 3
4extern void cgit_print_summary(); 4extern void cgit_print_summary();
5extern void cgit_print_repo_readme(); 5extern void cgit_print_repo_readme(char *path);
6 6
7#endif /* UI_SUMMARY_H */ 7#endif /* UI_SUMMARY_H */
diff --git a/ui-tag.c b/ui-tag.c
index 8c263ab..c2d72af 100644
--- a/ui-tag.c
+++ b/ui-tag.c
@@ -67,7 +67,7 @@ void cgit_print_tag(char *revname)
67 if (info->tagger) { 67 if (info->tagger) {
68 html("<tr><td>Tagged by</td><td>"); 68 html("<tr><td>Tagged by</td><td>");
69 html_txt(info->tagger); 69 html_txt(info->tagger);
70 if (info->tagger_email) { 70 if (info->tagger_email && !ctx.cfg.noplainemail) {
71 html(" "); 71 html(" ");
72 html_txt(info->tagger_email); 72 html_txt(info->tagger_email);
73 } 73 }
diff --git a/ui-tree.c b/ui-tree.c
index 553dbaa..c608754 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -15,13 +15,23 @@ char *curr_rev;
15char *match_path; 15char *match_path;
16int header = 0; 16int header = 0;
17 17
18static void print_text_buffer(char *buf, unsigned long size) 18static void print_text_buffer(const char *name, char *buf, unsigned long size)
19{ 19{
20 unsigned long lineno, idx; 20 unsigned long lineno, idx;
21 const char *numberfmt = 21 const char *numberfmt =
22 "<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n"; 22 "<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n";
23 23
24 html("<table summary='blob content' class='blob'>\n"); 24 html("<table summary='blob content' class='blob'>\n");
25 if (ctx.repo->source_filter) {
26 html("<tr><td class='lines'><pre><code>");
27 ctx.repo->source_filter->argv[1] = xstrdup(name);
28 cgit_open_filter(ctx.repo->source_filter);
29 write(STDOUT_FILENO, buf, size);
30 cgit_close_filter(ctx.repo->source_filter);
31 html("</code></pre></td></tr></table>\n");
32 return;
33 }
34
25 html("<tr><td class='linenumbers'><pre>"); 35 html("<tr><td class='linenumbers'><pre>");
26 idx = 0; 36 idx = 0;
27 lineno = 0; 37 lineno = 0;
@@ -65,7 +75,7 @@ static void print_binary_buffer(char *buf, unsigned long size)
65 html("</table>\n"); 75 html("</table>\n");
66} 76}
67 77
68static void print_object(const unsigned char *sha1, char *path) 78static void print_object(const unsigned char *sha1, char *path, const char *basename)
69{ 79{
70 enum object_type type; 80 enum object_type type;
71 char *buf; 81 char *buf;
@@ -93,7 +103,7 @@ static void print_object(const unsigned char *sha1, char *path)
93 if (buffer_is_binary(buf, size)) 103 if (buffer_is_binary(buf, size))
94 print_binary_buffer(buf, size); 104 print_binary_buffer(buf, size);
95 else 105 else
96 print_text_buffer(buf, size); 106 print_text_buffer(basename, buf, size);
97} 107}
98 108
99 109
@@ -103,6 +113,7 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
103{ 113{
104 char *name; 114 char *name;
105 char *fullpath; 115 char *fullpath;
116 char *class;
106 enum object_type type; 117 enum object_type type;
107 unsigned long size = 0; 118 unsigned long size = 0;
108 119
@@ -135,7 +146,12 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
135 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 146 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
136 curr_rev, fullpath); 147 curr_rev, fullpath);
137 } else { 148 } else {
138 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head, 149 class = strrchr(name, '.');
150 if (class != NULL) {
151 class = fmt("ls-blob %s", class + 1);
152 } else
153 class = "ls-blob";
154 cgit_tree_link(name, NULL, class, ctx.qry.head,
139 curr_rev, fullpath); 155 curr_rev, fullpath);
140 } 156 }
141 htmlf("</td><td class='ls-size'>%li</td>", size); 157 htmlf("</td><td class='ls-size'>%li</td>", size);
@@ -213,7 +229,7 @@ static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
213 ls_head(); 229 ls_head();
214 return READ_TREE_RECURSIVE; 230 return READ_TREE_RECURSIVE;
215 } else { 231 } else {
216 print_object(sha1, buffer); 232 print_object(sha1, buffer, pathname);
217 return 0; 233 return 0;
218 } 234 }
219 } 235 }