summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--.gitignore5
-rw-r--r--Makefile21
-rw-r--r--cgit-doc.css3
-rw-r--r--cgit.c8
-rw-r--r--cgit.h4
-rw-r--r--cgitrc.5.txt122
-rw-r--r--ui-atom.c6
-rw-r--r--ui-blob.c8
-rw-r--r--ui-plain.c6
-rw-r--r--ui-shared.c26
-rw-r--r--ui-shared.h1
-rw-r--r--ui-snapshot.c23
-rw-r--r--ui-tree.c26
13 files changed, 177 insertions, 82 deletions
diff --git a/.gitignore b/.gitignore
index 1e016e5..487728b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,11 @@
1# Files I don't care to see in git-status/commit 1# Files I don't care to see in git-status/commit
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 0f0089a..707d446 100644
--- a/Makefile
+++ b/Makefile
@@ -1,25 +1,25 @@
1CGIT_VERSION = v0.8.2 1CGIT_VERSION = v0.8.2.1
2CGIT_SCRIPT_NAME = cgit.cgi 2CGIT_SCRIPT_NAME = cgit.cgi
3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit 3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH) 4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
5CGIT_CONFIG = /etc/cgitrc 5CGIT_CONFIG = /etc/cgitrc
6CACHE_ROOT = /var/cache/cgit 6CACHE_ROOT = /var/cache/cgit
7SHA1_HEADER = <openssl/sha.h> 7SHA1_HEADER = <openssl/sha.h>
8GIT_VER = 1.6.1.1 8GIT_VER = 1.6.1.1
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 NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin). 14# Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin).
15# 15#
16 16
17#-include config.mak 17#-include config.mak
18 18
19# 19#
20# Platform specific tweaks 20# Platform specific tweaks
21# 21#
22 22
23uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') 23uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
24uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') 24uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
25uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') 25uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
@@ -79,78 +79,93 @@ OBJECTS += parsing.o
79OBJECTS += scan-tree.o 79OBJECTS += scan-tree.o
80OBJECTS += shared.o 80OBJECTS += shared.o
81OBJECTS += ui-atom.o 81OBJECTS += ui-atom.o
82OBJECTS += ui-blob.o 82OBJECTS += ui-blob.o
83OBJECTS += ui-clone.o 83OBJECTS += ui-clone.o
84OBJECTS += ui-commit.o 84OBJECTS += ui-commit.o
85OBJECTS += ui-diff.o 85OBJECTS += ui-diff.o
86OBJECTS += ui-log.o 86OBJECTS += ui-log.o
87OBJECTS += ui-patch.o 87OBJECTS += ui-patch.o
88OBJECTS += ui-plain.o 88OBJECTS += ui-plain.o
89OBJECTS += ui-refs.o 89OBJECTS += ui-refs.o
90OBJECTS += ui-repolist.o 90OBJECTS += ui-repolist.o
91OBJECTS += ui-shared.o 91OBJECTS += ui-shared.o
92OBJECTS += ui-snapshot.o 92OBJECTS += ui-snapshot.o
93OBJECTS += ui-stats.o 93OBJECTS += ui-stats.o
94OBJECTS += ui-summary.o 94OBJECTS += ui-summary.o
95OBJECTS += ui-tag.o 95OBJECTS += ui-tag.o
96OBJECTS += ui-tree.o 96OBJECTS += ui-tree.o
97 97
98ifdef NEEDS_LIBICONV 98ifdef NEEDS_LIBICONV
99 EXTLIBS += -liconv 99 EXTLIBS += -liconv
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
107VERSION: force-version 108VERSION: force-version
108 @./gen-version.sh "$(CGIT_VERSION)" 109 @./gen-version.sh "$(CGIT_VERSION)"
109-include VERSION 110-include VERSION
110 111
111 112
112CFLAGS += -g -Wall -Igit 113CFLAGS += -g -Wall -Igit
113CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' 114CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)'
114CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' 115CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"'
115CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' 116CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
116CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' 117CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
117CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' 118CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
118 119
119ifdef NO_ICONV 120ifdef NO_ICONV
120 CFLAGS += -DNO_ICONV 121 CFLAGS += -DNO_ICONV
121endif 122endif
122ifdef NO_STRCASESTR 123ifdef NO_STRCASESTR
123 CFLAGS += -DNO_STRCASESTR 124 CFLAGS += -DNO_STRCASESTR
124endif 125endif
125 126
126cgit: $(OBJECTS) libgit 127cgit: $(OBJECTS) libgit
127 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) 128 $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS)
128 129
129cgit.o: VERSION 130cgit.o: VERSION
130 131
131-include $(OBJECTS:.o=.d) 132-include $(OBJECTS:.o=.d)
132 133
133libgit: 134libgit:
134 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a 135 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a
135 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a 136 $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a
136 137
137test: all 138test: all
138 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all 139 $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
139 140
140install: all 141install: all
141 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH) 142 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
142 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 143 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
143 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) 144 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
144 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css 145 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
145 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png 146 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
146 147
147uninstall: 148uninstall:
148 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 149 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
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
--- a/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 38f0fdd..2039ab1 100644
--- a/cgit.c
+++ b/cgit.c
@@ -10,48 +10,50 @@
10#include "cache.h" 10#include "cache.h"
11#include "cmd.h" 11#include "cmd.h"
12#include "configfile.h" 12#include "configfile.h"
13#include "html.h" 13#include "html.h"
14#include "ui-shared.h" 14#include "ui-shared.h"
15#include "ui-stats.h" 15#include "ui-stats.h"
16#include "scan-tree.h" 16#include "scan-tree.h"
17 17
18const char *cgit_version = CGIT_VERSION; 18const char *cgit_version = CGIT_VERSION;
19 19
20void config_cb(const char *name, const char *value) 20void config_cb(const char *name, const char *value)
21{ 21{
22 if (!strcmp(name, "root-title")) 22 if (!strcmp(name, "root-title"))
23 ctx.cfg.root_title = xstrdup(value); 23 ctx.cfg.root_title = xstrdup(value);
24 else if (!strcmp(name, "root-desc")) 24 else if (!strcmp(name, "root-desc"))
25 ctx.cfg.root_desc = xstrdup(value); 25 ctx.cfg.root_desc = xstrdup(value);
26 else if (!strcmp(name, "root-readme")) 26 else if (!strcmp(name, "root-readme"))
27 ctx.cfg.root_readme = xstrdup(value); 27 ctx.cfg.root_readme = xstrdup(value);
28 else if (!strcmp(name, "css")) 28 else if (!strcmp(name, "css"))
29 ctx.cfg.css = xstrdup(value); 29 ctx.cfg.css = xstrdup(value);
30 else if (!strcmp(name, "favicon")) 30 else if (!strcmp(name, "favicon"))
31 ctx.cfg.favicon = xstrdup(value); 31 ctx.cfg.favicon = xstrdup(value);
32 else if (!strcmp(name, "footer")) 32 else if (!strcmp(name, "footer"))
33 ctx.cfg.footer = xstrdup(value); 33 ctx.cfg.footer = xstrdup(value);
34 else if (!strcmp(name, "head-include"))
35 ctx.cfg.head_include = xstrdup(value);
34 else if (!strcmp(name, "header")) 36 else if (!strcmp(name, "header"))
35 ctx.cfg.header = xstrdup(value); 37 ctx.cfg.header = xstrdup(value);
36 else if (!strcmp(name, "logo")) 38 else if (!strcmp(name, "logo"))
37 ctx.cfg.logo = xstrdup(value); 39 ctx.cfg.logo = xstrdup(value);
38 else if (!strcmp(name, "index-header")) 40 else if (!strcmp(name, "index-header"))
39 ctx.cfg.index_header = xstrdup(value); 41 ctx.cfg.index_header = xstrdup(value);
40 else if (!strcmp(name, "index-info")) 42 else if (!strcmp(name, "index-info"))
41 ctx.cfg.index_info = xstrdup(value); 43 ctx.cfg.index_info = xstrdup(value);
42 else if (!strcmp(name, "logo-link")) 44 else if (!strcmp(name, "logo-link"))
43 ctx.cfg.logo_link = xstrdup(value); 45 ctx.cfg.logo_link = xstrdup(value);
44 else if (!strcmp(name, "module-link")) 46 else if (!strcmp(name, "module-link"))
45 ctx.cfg.module_link = xstrdup(value); 47 ctx.cfg.module_link = xstrdup(value);
46 else if (!strcmp(name, "virtual-root")) { 48 else if (!strcmp(name, "virtual-root")) {
47 ctx.cfg.virtual_root = trim_end(value, '/'); 49 ctx.cfg.virtual_root = trim_end(value, '/');
48 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 50 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
49 ctx.cfg.virtual_root = ""; 51 ctx.cfg.virtual_root = "";
50 } else if (!strcmp(name, "nocache")) 52 } else if (!strcmp(name, "nocache"))
51 ctx.cfg.nocache = atoi(value); 53 ctx.cfg.nocache = atoi(value);
52 else if (!strcmp(name, "noheader")) 54 else if (!strcmp(name, "noheader"))
53 ctx.cfg.noheader = atoi(value); 55 ctx.cfg.noheader = atoi(value);
54 else if (!strcmp(name, "snapshots")) 56 else if (!strcmp(name, "snapshots"))
55 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 57 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
56 else if (!strcmp(name, "enable-index-links")) 58 else if (!strcmp(name, "enable-index-links"))
57 ctx.cfg.enable_index_links = atoi(value); 59 ctx.cfg.enable_index_links = atoi(value);
@@ -189,48 +191,49 @@ static void prepare_context(struct cgit_context *ctx)
189 ctx->cfg.css = "/cgit.css"; 191 ctx->cfg.css = "/cgit.css";
190 ctx->cfg.logo = "/git-logo.png"; 192 ctx->cfg.logo = "/git-logo.png";
191 ctx->cfg.local_time = 0; 193 ctx->cfg.local_time = 0;
192 ctx->cfg.max_repo_count = 50; 194 ctx->cfg.max_repo_count = 50;
193 ctx->cfg.max_commit_count = 50; 195 ctx->cfg.max_commit_count = 50;
194 ctx->cfg.max_lock_attempts = 5; 196 ctx->cfg.max_lock_attempts = 5;
195 ctx->cfg.max_msg_len = 80; 197 ctx->cfg.max_msg_len = 80;
196 ctx->cfg.max_repodesc_len = 80; 198 ctx->cfg.max_repodesc_len = 80;
197 ctx->cfg.max_stats = 0; 199 ctx->cfg.max_stats = 0;
198 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 200 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
199 ctx->cfg.renamelimit = -1; 201 ctx->cfg.renamelimit = -1;
200 ctx->cfg.robots = "index, nofollow"; 202 ctx->cfg.robots = "index, nofollow";
201 ctx->cfg.root_title = "Git repository browser"; 203 ctx->cfg.root_title = "Git repository browser";
202 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 204 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
203 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 205 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
204 ctx->cfg.summary_branches = 10; 206 ctx->cfg.summary_branches = 10;
205 ctx->cfg.summary_log = 10; 207 ctx->cfg.summary_log = 10;
206 ctx->cfg.summary_tags = 10; 208 ctx->cfg.summary_tags = 10;
207 ctx->page.mimetype = "text/html"; 209 ctx->page.mimetype = "text/html";
208 ctx->page.charset = PAGE_ENCODING; 210 ctx->page.charset = PAGE_ENCODING;
209 ctx->page.filename = NULL; 211 ctx->page.filename = NULL;
210 ctx->page.size = 0; 212 ctx->page.size = 0;
211 ctx->page.modified = time(NULL); 213 ctx->page.modified = time(NULL);
212 ctx->page.expires = ctx->page.modified; 214 ctx->page.expires = ctx->page.modified;
215 ctx->page.etag = NULL;
213} 216}
214 217
215struct refmatch { 218struct refmatch {
216 char *req_ref; 219 char *req_ref;
217 char *first_ref; 220 char *first_ref;
218 int match; 221 int match;
219}; 222};
220 223
221int find_current_ref(const char *refname, const unsigned char *sha1, 224int find_current_ref(const char *refname, const unsigned char *sha1,
222 int flags, void *cb_data) 225 int flags, void *cb_data)
223{ 226{
224 struct refmatch *info; 227 struct refmatch *info;
225 228
226 info = (struct refmatch *)cb_data; 229 info = (struct refmatch *)cb_data;
227 if (!strcmp(refname, info->req_ref)) 230 if (!strcmp(refname, info->req_ref))
228 info->match = 1; 231 info->match = 1;
229 if (!info->first_ref) 232 if (!info->first_ref)
230 info->first_ref = xstrdup(refname); 233 info->first_ref = xstrdup(refname);
231 return info->match; 234 return info->match;
232} 235}
233 236
234char *find_default_branch(struct cgit_repo *repo) 237char *find_default_branch(struct cgit_repo *repo)
235{ 238{
236 struct refmatch info; 239 struct refmatch info;
@@ -268,48 +271,50 @@ static int prepare_repo_cmd(struct cgit_context *ctx)
268 cgit_print_error(tmp); 271 cgit_print_error(tmp);
269 cgit_print_docend(); 272 cgit_print_docend();
270 return 1; 273 return 1;
271 } 274 }
272 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 275 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
273 276
274 if (!ctx->qry.head) { 277 if (!ctx->qry.head) {
275 ctx->qry.nohead = 1; 278 ctx->qry.nohead = 1;
276 ctx->qry.head = find_default_branch(ctx->repo); 279 ctx->qry.head = find_default_branch(ctx->repo);
277 ctx->repo->defbranch = ctx->qry.head; 280 ctx->repo->defbranch = ctx->qry.head;
278 } 281 }
279 282
280 if (!ctx->qry.head) { 283 if (!ctx->qry.head) {
281 cgit_print_http_headers(ctx); 284 cgit_print_http_headers(ctx);
282 cgit_print_docstart(ctx); 285 cgit_print_docstart(ctx);
283 cgit_print_pageheader(ctx); 286 cgit_print_pageheader(ctx);
284 cgit_print_error("Repository seems to be empty"); 287 cgit_print_error("Repository seems to be empty");
285 cgit_print_docend(); 288 cgit_print_docend();
286 return 1; 289 return 1;
287 } 290 }
288 291
289 if (get_sha1(ctx->qry.head, sha1)) { 292 if (get_sha1(ctx->qry.head, sha1)) {
290 tmp = xstrdup(ctx->qry.head); 293 tmp = xstrdup(ctx->qry.head);
291 ctx->qry.head = ctx->repo->defbranch; 294 ctx->qry.head = ctx->repo->defbranch;
295 ctx->page.status = 404;
296 ctx->page.statusmsg = "not found";
292 cgit_print_http_headers(ctx); 297 cgit_print_http_headers(ctx);
293 cgit_print_docstart(ctx); 298 cgit_print_docstart(ctx);
294 cgit_print_pageheader(ctx); 299 cgit_print_pageheader(ctx);
295 cgit_print_error(fmt("Invalid branch: %s", tmp)); 300 cgit_print_error(fmt("Invalid branch: %s", tmp));
296 cgit_print_docend(); 301 cgit_print_docend();
297 return 1; 302 return 1;
298 } 303 }
299 return 0; 304 return 0;
300} 305}
301 306
302static void process_request(void *cbdata) 307static void process_request(void *cbdata)
303{ 308{
304 struct cgit_context *ctx = cbdata; 309 struct cgit_context *ctx = cbdata;
305 struct cgit_cmd *cmd; 310 struct cgit_cmd *cmd;
306 311
307 cmd = cgit_get_cmd(ctx); 312 cmd = cgit_get_cmd(ctx);
308 if (!cmd) { 313 if (!cmd) {
309 ctx->page.title = "cgit error"; 314 ctx->page.title = "cgit error";
310 cgit_print_http_headers(ctx); 315 cgit_print_http_headers(ctx);
311 cgit_print_docstart(ctx); 316 cgit_print_docstart(ctx);
312 cgit_print_pageheader(ctx); 317 cgit_print_pageheader(ctx);
313 cgit_print_error("Invalid request"); 318 cgit_print_error("Invalid request");
314 cgit_print_docend(); 319 cgit_print_docend();
315 return; 320 return;
@@ -412,79 +417,82 @@ static void cgit_parse_args(int argc, const char **argv)
412 exit(0); 417 exit(0);
413 } 418 }
414} 419}
415 420
416static int calc_ttl() 421static int calc_ttl()
417{ 422{
418 if (!ctx.repo) 423 if (!ctx.repo)
419 return ctx.cfg.cache_root_ttl; 424 return ctx.cfg.cache_root_ttl;
420 425
421 if (!ctx.qry.page) 426 if (!ctx.qry.page)
422 return ctx.cfg.cache_repo_ttl; 427 return ctx.cfg.cache_repo_ttl;
423 428
424 if (ctx.qry.has_symref) 429 if (ctx.qry.has_symref)
425 return ctx.cfg.cache_dynamic_ttl; 430 return ctx.cfg.cache_dynamic_ttl;
426 431
427 if (ctx.qry.has_sha1) 432 if (ctx.qry.has_sha1)
428 return ctx.cfg.cache_static_ttl; 433 return ctx.cfg.cache_static_ttl;
429 434
430 return ctx.cfg.cache_repo_ttl; 435 return ctx.cfg.cache_repo_ttl;
431} 436}
432 437
433int main(int argc, const char **argv) 438int main(int argc, const char **argv)
434{ 439{
435 const char *cgit_config_env = getenv("CGIT_CONFIG"); 440 const char *cgit_config_env = getenv("CGIT_CONFIG");
441 const char *method = getenv("REQUEST_METHOD");
436 const char *path; 442 const char *path;
437 char *qry; 443 char *qry;
438 int err, ttl; 444 int err, ttl;
439 445
440 prepare_context(&ctx); 446 prepare_context(&ctx);
441 cgit_repolist.length = 0; 447 cgit_repolist.length = 0;
442 cgit_repolist.count = 0; 448 cgit_repolist.count = 0;
443 cgit_repolist.repos = NULL; 449 cgit_repolist.repos = NULL;
444 450
445 if (getenv("SCRIPT_NAME")) 451 if (getenv("SCRIPT_NAME"))
446 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME")); 452 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME"));
447 if (getenv("QUERY_STRING")) 453 if (getenv("QUERY_STRING"))
448 ctx.qry.raw = xstrdup(getenv("QUERY_STRING")); 454 ctx.qry.raw = xstrdup(getenv("QUERY_STRING"));
449 cgit_parse_args(argc, argv); 455 cgit_parse_args(argc, argv);
450 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG, 456 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG,
451 config_cb); 457 config_cb);
452 ctx.repo = NULL; 458 ctx.repo = NULL;
453 http_parse_querystring(ctx.qry.raw, querystring_cb); 459 http_parse_querystring(ctx.qry.raw, querystring_cb);
454 460
455 /* If virtual-root isn't specified in cgitrc, lets pretend 461 /* If virtual-root isn't specified in cgitrc, lets pretend
456 * that virtual-root equals SCRIPT_NAME. 462 * that virtual-root equals SCRIPT_NAME.
457 */ 463 */
458 if (!ctx.cfg.virtual_root) 464 if (!ctx.cfg.virtual_root)
459 ctx.cfg.virtual_root = ctx.cfg.script_name; 465 ctx.cfg.virtual_root = ctx.cfg.script_name;
460 466
461 /* If no url parameter is specified on the querystring, lets 467 /* If no url parameter is specified on the querystring, lets
462 * use PATH_INFO as url. This allows cgit to work with virtual 468 * use PATH_INFO as url. This allows cgit to work with virtual
463 * urls without the need for rewriterules in the webserver (as 469 * urls without the need for rewriterules in the webserver (as
464 * long as PATH_INFO is included in the cache lookup key). 470 * long as PATH_INFO is included in the cache lookup key).
465 */ 471 */
466 path = getenv("PATH_INFO"); 472 path = getenv("PATH_INFO");
467 if (!ctx.qry.url && path) { 473 if (!ctx.qry.url && path) {
468 if (path[0] == '/') 474 if (path[0] == '/')
469 path++; 475 path++;
470 ctx.qry.url = xstrdup(path); 476 ctx.qry.url = xstrdup(path);
471 if (ctx.qry.raw) { 477 if (ctx.qry.raw) {
472 qry = ctx.qry.raw; 478 qry = ctx.qry.raw;
473 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 479 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
474 free(qry); 480 free(qry);
475 } else 481 } else
476 ctx.qry.raw = ctx.qry.url; 482 ctx.qry.raw = ctx.qry.url;
477 cgit_parse_url(ctx.qry.url); 483 cgit_parse_url(ctx.qry.url);
478 } 484 }
479 485
480 ttl = calc_ttl(); 486 ttl = calc_ttl();
481 ctx.page.expires += ttl*60; 487 ctx.page.expires += ttl*60;
488 if (method && !strcmp(method, "HEAD"))
489 ctx.cfg.nocache = 1;
482 if (ctx.cfg.nocache) 490 if (ctx.cfg.nocache)
483 ctx.cfg.cache_size = 0; 491 ctx.cfg.cache_size = 0;
484 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 492 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
485 ctx.qry.raw, ttl, process_request, &ctx); 493 ctx.qry.raw, ttl, process_request, &ctx);
486 if (err) 494 if (err)
487 cgit_print_error(fmt("Error processing page: %s (%d)", 495 cgit_print_error(fmt("Error processing page: %s (%d)",
488 strerror(err), err)); 496 strerror(err), err));
489 return err; 497 return err;
490} 498}
diff --git a/cgit.h b/cgit.h
index ca01705..8c64efe 100644
--- a/cgit.h
+++ b/cgit.h
@@ -115,95 +115,99 @@ struct cgit_query {
115 char *page; 115 char *page;
116 char *search; 116 char *search;
117 char *grep; 117 char *grep;
118 char *head; 118 char *head;
119 char *sha1; 119 char *sha1;
120 char *sha2; 120 char *sha2;
121 char *path; 121 char *path;
122 char *name; 122 char *name;
123 char *mimetype; 123 char *mimetype;
124 char *url; 124 char *url;
125 char *period; 125 char *period;
126 int ofs; 126 int ofs;
127 int nohead; 127 int nohead;
128 char *sort; 128 char *sort;
129 int showmsg; 129 int showmsg;
130}; 130};
131 131
132struct cgit_config { 132struct cgit_config {
133 char *agefile; 133 char *agefile;
134 char *cache_root; 134 char *cache_root;
135 char *clone_prefix; 135 char *clone_prefix;
136 char *css; 136 char *css;
137 char *favicon; 137 char *favicon;
138 char *footer; 138 char *footer;
139 char *head_include;
139 char *header; 140 char *header;
140 char *index_header; 141 char *index_header;
141 char *index_info; 142 char *index_info;
142 char *logo; 143 char *logo;
143 char *logo_link; 144 char *logo_link;
144 char *module_link; 145 char *module_link;
145 char *repo_group; 146 char *repo_group;
146 char *robots; 147 char *robots;
147 char *root_title; 148 char *root_title;
148 char *root_desc; 149 char *root_desc;
149 char *root_readme; 150 char *root_readme;
150 char *script_name; 151 char *script_name;
151 char *virtual_root; 152 char *virtual_root;
152 int cache_size; 153 int cache_size;
153 int cache_dynamic_ttl; 154 int cache_dynamic_ttl;
154 int cache_max_create_time; 155 int cache_max_create_time;
155 int cache_repo_ttl; 156 int cache_repo_ttl;
156 int cache_root_ttl; 157 int cache_root_ttl;
157 int cache_static_ttl; 158 int cache_static_ttl;
158 int embedded; 159 int embedded;
159 int enable_index_links; 160 int enable_index_links;
160 int enable_log_filecount; 161 int enable_log_filecount;
161 int enable_log_linecount; 162 int enable_log_linecount;
162 int local_time; 163 int local_time;
163 int max_repo_count; 164 int max_repo_count;
164 int max_commit_count; 165 int max_commit_count;
165 int max_lock_attempts; 166 int max_lock_attempts;
166 int max_msg_len; 167 int max_msg_len;
167 int max_repodesc_len; 168 int max_repodesc_len;
168 int max_stats; 169 int max_stats;
169 int nocache; 170 int nocache;
170 int noheader; 171 int noheader;
171 int renamelimit; 172 int renamelimit;
172 int snapshots; 173 int snapshots;
173 int summary_branches; 174 int summary_branches;
174 int summary_log; 175 int summary_log;
175 int summary_tags; 176 int summary_tags;
176}; 177};
177 178
178struct cgit_page { 179struct cgit_page {
179 time_t modified; 180 time_t modified;
180 time_t expires; 181 time_t expires;
181 size_t size; 182 size_t size;
182 char *mimetype; 183 char *mimetype;
183 char *charset; 184 char *charset;
184 char *filename; 185 char *filename;
186 char *etag;
185 char *title; 187 char *title;
188 int status;
189 char *statusmsg;
186}; 190};
187 191
188struct cgit_context { 192struct cgit_context {
189 struct cgit_query qry; 193 struct cgit_query qry;
190 struct cgit_config cfg; 194 struct cgit_config cfg;
191 struct cgit_repo *repo; 195 struct cgit_repo *repo;
192 struct cgit_page page; 196 struct cgit_page page;
193}; 197};
194 198
195struct cgit_snapshot_format { 199struct cgit_snapshot_format {
196 const char *suffix; 200 const char *suffix;
197 const char *mimetype; 201 const char *mimetype;
198 write_archive_fn_t write_func; 202 write_archive_fn_t write_func;
199 int bit; 203 int bit;
200}; 204};
201 205
202extern const char *cgit_version; 206extern const char *cgit_version;
203 207
204extern struct cgit_repolist cgit_repolist; 208extern struct cgit_repolist cgit_repolist;
205extern struct cgit_context ctx; 209extern struct cgit_context ctx;
206extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 210extern const struct cgit_snapshot_format cgit_snapshot_formats[];
207 211
208extern struct cgit_repo *cgit_add_repo(const char *url); 212extern struct cgit_repo *cgit_add_repo(const char *url);
209extern struct cgit_repo *cgit_get_repoinfo(const char *url); 213extern struct cgit_repo *cgit_get_repoinfo(const char *url);
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 771bb7d..a207fe0 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,294 +1,299 @@
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
17GLOBAL SETTINGS 17GLOBAL SETTINGS
18--------------- 18---------------
19agefile 19agefile::
20 Specifies a path, relative to each repository path, which can be used 20 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. 21 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" 22 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 23 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
24 hh:mm:ss". Default value: "info/web/last-modified". 24 hh:mm:ss". Default value: "info/web/last-modified".
25 25
26cache-root 26cache-root::
27 Path used to store the cgit cache entries. Default value: 27 Path used to store the cgit cache entries. Default value:
28 "/var/cache/cgit". 28 "/var/cache/cgit".
29 29
30cache-dynamic-ttl 30cache-dynamic-ttl::
31 Number which specifies the time-to-live, in minutes, for the cached 31 Number which specifies the time-to-live, in minutes, for the cached
32 version of repository pages accessed without a fixed SHA1. Default 32 version of repository pages accessed without a fixed SHA1. Default
33 value: "5". 33 value: "5".
34 34
35cache-repo-ttl 35cache-repo-ttl::
36 Number which specifies the time-to-live, in minutes, for the cached 36 Number which specifies the time-to-live, in minutes, for the cached
37 version of the repository summary page. Default value: "5". 37 version of the repository summary page. Default value: "5".
38 38
39cache-root-ttl 39cache-root-ttl::
40 Number which specifies the time-to-live, in minutes, for the cached 40 Number which specifies the time-to-live, in minutes, for the cached
41 version of the repository index page. Default value: "5". 41 version of the repository index page. Default value: "5".
42 42
43cache-size 43cache-size::
44 The maximum number of entries in the cgit cache. Default value: "0" 44 The maximum number of entries in the cgit cache. Default value: "0"
45 (i.e. caching is disabled). 45 (i.e. caching is disabled).
46 46
47cache-static-ttl 47cache-static-ttl::
48 Number which specifies the time-to-live, in minutes, for the cached 48 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed with a fixed SHA1. Default value: 49 version of repository pages accessed with a fixed SHA1. Default value:
50 "5". 50 "5".
51 51
52clone-prefix 52clone-prefix::
53 Space-separated list of common prefixes which, when combined with a 53 Space-separated list of common prefixes which, when combined with a
54 repository url, generates valid clone urls for the repository. This 54 repository url, generates valid clone urls for the repository. This
55 setting is only used if `repo.clone-url` is unspecified. Default value: 55 setting is only used if `repo.clone-url` is unspecified. Default value:
56 none. 56 none.
57 57
58css 58css::
59 Url which specifies the css document to include in all cgit pages. 59 Url which specifies the css document to include in all cgit pages.
60 Default value: "/cgit.css". 60 Default value: "/cgit.css".
61 61
62embedded 62embedded::
63 Flag which, when set to "1", will make cgit generate a html fragment 63 Flag which, when set to "1", will make cgit generate a html fragment
64 suitable for embedding in other html pages. Default value: none. See 64 suitable for embedding in other html pages. Default value: none. See
65 also: "noheader". 65 also: "noheader".
66 66
67enable-index-links 67enable-index-links::
68 Flag which, when set to "1", will make cgit generate extra links for 68 Flag which, when set to "1", will make cgit generate extra links for
69 each repo in the repository index (specifically, to the "summary", 69 each repo in the repository index (specifically, to the "summary",
70 "commit" and "tree" pages). Default value: "0". 70 "commit" and "tree" pages). Default value: "0".
71 71
72enable-log-filecount 72enable-log-filecount::
73 Flag which, when set to "1", will make cgit print the number of 73 Flag which, when set to "1", will make cgit print the number of
74 modified files for each commit on the repository log page. Default 74 modified files for each commit on the repository log page. Default
75 value: "0". 75 value: "0".
76 76
77enable-log-linecount 77enable-log-linecount::
78 Flag which, when set to "1", will make cgit print the number of added 78 Flag which, when set to "1", will make cgit print the number of added
79 and removed lines for each commit on the repository log page. Default 79 and removed lines for each commit on the repository log page. Default
80 value: "0". 80 value: "0".
81 81
82favicon 82favicon::
83 Url used as link to a shortcut icon for cgit. If specified, it is 83 Url used as link to a shortcut icon for cgit. If specified, it is
84 suggested to use the value "/favicon.ico" since certain browsers will 84 suggested to use the value "/favicon.ico" since certain browsers will
85 ignore other values. Default value: none. 85 ignore other values. Default value: none.
86 86
87footer 87footer::
88 The content of the file specified with this option will be included 88 The content of the file specified with this option will be included
89 verbatim at the bottom of all pages (i.e. it replaces the standard 89 verbatim at the bottom of all pages (i.e. it replaces the standard
90 "generated by..." message. Default value: none. 90 "generated by..." message. Default value: none.
91 91
92header 92head-include::
93 The content of the file specified with this option will be included
94 verbatim in the html HEAD section on all pages. Default value: none.
95
96header::
93 The content of the file specified with this option will be included 97 The content of the file specified with this option will be included
94 verbatim at the top of all pages. Default value: none. 98 verbatim at the top of all pages. Default value: none.
95 99
96include 100include::
97 Name of a configfile to include before the rest of the current config- 101 Name of a configfile to include before the rest of the current config-
98 file is parsed. Default value: none. 102 file is parsed. Default value: none.
99 103
100index-header 104index-header::
101 The content of the file specified with this option will be included 105 The content of the file specified with this option will be included
102 verbatim above the repository index. This setting is deprecated, and 106 verbatim above the repository index. This setting is deprecated, and
103 will not be supported by cgit-1.0 (use root-readme instead). Default 107 will not be supported by cgit-1.0 (use root-readme instead). Default
104 value: none. 108 value: none.
105 109
106index-info 110index-info::
107 The content of the file specified with this option will be included 111 The content of the file specified with this option will be included
108 verbatim below the heading on the repository index page. This setting 112 verbatim below the heading on the repository index page. This setting
109 is deprecated, and will not be supported by cgit-1.0 (use root-desc 113 is deprecated, and will not be supported by cgit-1.0 (use root-desc
110 instead). Default value: none. 114 instead). Default value: none.
111 115
112local-time 116local-time::
113 Flag which, if set to "1", makes cgit print commit and tag times in the 117 Flag which, if set to "1", makes cgit print commit and tag times in the
114 servers timezone. Default value: "0". 118 servers timezone. Default value: "0".
115 119
116logo 120logo::
117 Url which specifies the source of an image which will be used as a logo 121 Url which specifies the source of an image which will be used as a logo
118 on all cgit pages. 122 on all cgit pages.
119 123
120logo-link 124logo-link::
121 Url loaded when clicking on the cgit logo image. If unspecified the 125 Url loaded when clicking on the cgit logo image. If unspecified the
122 calculated url of the repository index page will be used. Default 126 calculated url of the repository index page will be used. Default
123 value: none. 127 value: none.
124 128
125max-commit-count 129max-commit-count::
126 Specifies the number of entries to list per page in "log" view. Default 130 Specifies the number of entries to list per page in "log" view. Default
127 value: "50". 131 value: "50".
128 132
129max-message-length 133max-message-length::
130 Specifies the maximum number of commit message characters to display in 134 Specifies the maximum number of commit message characters to display in
131 "log" view. Default value: "80". 135 "log" view. Default value: "80".
132 136
133max-repo-count 137max-repo-count::
134 Specifies the number of entries to list per page on therepository 138 Specifies the number of entries to list per page on therepository
135 index page. Default value: "50". 139 index page. Default value: "50".
136 140
137max-repodesc-length 141max-repodesc-length::
138 Specifies the maximum number of repo description characters to display 142 Specifies the maximum number of repo description characters to display
139 on the repository index page. Default value: "80". 143 on the repository index page. Default value: "80".
140 144
141max-stats 145max-stats::
142 Set the default maximum statistics period. Valid values are "week", 146 Set the default maximum statistics period. Valid values are "week",
143 "month", "quarter" and "year". If unspecified, statistics are 147 "month", "quarter" and "year". If unspecified, statistics are
144 disabled. Default value: none. See also: "repo.max-stats". 148 disabled. Default value: none. See also: "repo.max-stats".
145 149
146module-link 150module-link::
147 Text which will be used as the formatstring for a hyperlink when a 151 Text which will be used as the formatstring for a hyperlink when a
148 submodule is printed in a directory listing. The arguments for the 152 submodule is printed in a directory listing. The arguments for the
149 formatstring are the path and SHA1 of the submodule commit. Default 153 formatstring are the path and SHA1 of the submodule commit. Default
150 value: "./?repo=%s&page=commit&id=%s" 154 value: "./?repo=%s&page=commit&id=%s"
151 155
152nocache 156nocache::
153 If set to the value "1" caching will be disabled. This settings is 157 If set to the value "1" caching will be disabled. This settings is
154 deprecated, and will not be honored starting with cgit-1.0. Default 158 deprecated, and will not be honored starting with cgit-1.0. Default
155 value: "0". 159 value: "0".
156 160
157noheader 161noheader::
158 Flag which, when set to "1", will make cgit omit the standard header 162 Flag which, when set to "1", will make cgit omit the standard header
159 on all pages. Default value: none. See also: "embedded". 163 on all pages. Default value: none. See also: "embedded".
160 164
161renamelimit 165renamelimit::
162 Maximum number of files to consider when detecting renames. The value 166 Maximum number of files to consider when detecting renames. The value
163 "-1" uses the compiletime value in git (for further info, look at 167 "-1" uses the compiletime value in git (for further info, look at
164 `man git-diff`). Default value: "-1". 168 `man git-diff`). Default value: "-1".
165 169
166repo.group 170repo.group::
167 A value for the current repository group, which all repositories 171 A value for the current repository group, which all repositories
168 specified after this setting will inherit. Default value: none. 172 specified after this setting will inherit. Default value: none.
169 173
170robots 174robots::
171 Text used as content for the "robots" meta-tag. Default value: 175 Text used as content for the "robots" meta-tag. Default value:
172 "index, nofollow". 176 "index, nofollow".
173 177
174root-desc 178root-desc::
175 Text printed below the heading on the repository index page. Default 179 Text printed below the heading on the repository index page. Default
176 value: "a fast webinterface for the git dscm". 180 value: "a fast webinterface for the git dscm".
177 181
178root-readme: 182root-readme::
179 The content of the file specified with this option will be included 183 The content of the file specified with this option will be included
180 verbatim below the "about" link on the repository index page. Default 184 verbatim below the "about" link on the repository index page. Default
181 value: none. 185 value: none.
182 186
183root-title 187root-title::
184 Text printed as heading on the repository index page. Default value: 188 Text printed as heading on the repository index page. Default value:
185 "Git Repository Browser". 189 "Git Repository Browser".
186 190
187snapshots 191snapshots::
188 Text which specifies the default (and allowed) set of snapshot formats 192 Text which specifies the default (and allowed) set of snapshot formats
189 supported by cgit. The value is a space-separated list of zero or more 193 supported by cgit. The value is a space-separated list of zero or more
190 of the following values: 194 of the following values:
191 "tar" uncompressed tar-file 195 "tar" uncompressed tar-file
192 "tar.gz"gzip-compressed tar-file 196 "tar.gz"gzip-compressed tar-file
193 "tar.bz2"bzip-compressed tar-file 197 "tar.bz2"bzip-compressed tar-file
194 "zip" zip-file 198 "zip" zip-file
195 Default value: none. 199 Default value: none.
196 200
197summary-branches 201summary-branches::
198 Specifies the number of branches to display in the repository "summary" 202 Specifies the number of branches to display in the repository "summary"
199 view. Default value: "10". 203 view. Default value: "10".
200 204
201summary-log 205summary-log::
202 Specifies the number of log entries to display in the repository 206 Specifies the number of log entries to display in the repository
203 "summary" view. Default value: "10". 207 "summary" view. Default value: "10".
204 208
205summary-tags 209summary-tags::
206 Specifies the number of tags to display in the repository "summary" 210 Specifies the number of tags to display in the repository "summary"
207 view. Default value: "10". 211 view. Default value: "10".
208 212
209virtual-root 213virtual-root::
210 Url which, if specified, will be used as root for all cgit links. It 214 Url which, if specified, will be used as root for all cgit links. It
211 will also cause cgit to generate 'virtual urls', i.e. urls like 215 will also cause cgit to generate 'virtual urls', i.e. urls like
212 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 216 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
213 value: none. 217 value: none.
214 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 218 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
215 same kind of virtual urls, so this option will probably be deprecated. 219 same kind of virtual urls, so this option will probably be deprecated.
216 220
217REPOSITORY SETTINGS 221REPOSITORY SETTINGS
218------------------- 222-------------------
219repo.clone-url 223repo.clone-url::
220 A list of space-separated urls which can be used to clone this repo. 224 A list of space-separated urls which can be used to clone this repo.
221 Default value: none. 225 Default value: none.
222 226
223repo.defbranch 227repo.defbranch::
224 The name of the default branch for this repository. If no such branch 228 The name of the default branch for this repository. If no such branch
225 exists in the repository, the first branch name (when sorted) is used 229 exists in the repository, the first branch name (when sorted) is used
226 as default instead. Default value: "master". 230 as default instead. Default value: "master".
227 231
228repo.desc 232repo.desc::
229 The value to show as repository description. Default value: none. 233 The value to show as repository description. Default value: none.
230 234
231repo.enable-log-filecount 235repo.enable-log-filecount::
232 A flag which can be used to disable the global setting 236 A flag which can be used to disable the global setting
233 `enable-log-filecount'. Default value: none. 237 `enable-log-filecount'. Default value: none.
234 238
235repo.enable-log-linecount 239repo.enable-log-linecount::
236 A flag which can be used to disable the global setting 240 A flag which can be used to disable the global setting
237 `enable-log-linecount'. Default value: none. 241 `enable-log-linecount'. Default value: none.
238 242
239repo.max-stats 243repo.max-stats::
240 Override the default maximum statistics period. Valid values are equal 244 Override the default maximum statistics period. Valid values are equal
241 to the values specified for the global "max-stats" setting. Default 245 to the values specified for the global "max-stats" setting. Default
242 value: none. 246 value: none.
243 247
244repo.name 248repo.name::
245 The value to show as repository name. Default value: <repo.url>. 249 The value to show as repository name. Default value: <repo.url>.
246 250
247repo.owner 251repo.owner::
248 A value used to identify the owner of the repository. Default value: 252 A value used to identify the owner of the repository. Default value:
249 none. 253 none.
250 254
251repo.path 255repo.path::
252 An absolute path to the repository directory. For non-bare repositories 256 An absolute path to the repository directory. For non-bare repositories
253 this is the .git-directory. Default value: none. 257 this is the .git-directory. Default value: none.
254 258
255repo.readme 259repo.readme::
256 A path (relative to <repo.path>) which specifies a file to include 260 A path (relative to <repo.path>) which specifies a file to include
257 verbatim as the "About" page for this repo. Default value: none. 261 verbatim as the "About" page for this repo. Default value: none.
258 262
259repo.snapshots 263repo.snapshots::
260 A mask of allowed snapshot-formats for this repo, restricted by the 264 A mask of allowed snapshot-formats for this repo, restricted by the
261 "snapshots" global setting. Default value: <snapshots>. 265 "snapshots" global setting. Default value: <snapshots>.
262 266
263repo.url 267repo.url::
264 The relative url used to access the repository. This must be the first 268 The relative url used to access the repository. This must be the first
265 setting specified for each repo. Default value: none. 269 setting specified for each repo. Default value: none.
266 270
267 271
268EXAMPLE CGITRC FILE 272EXAMPLE CGITRC FILE
269------------------- 273-------------------
270 274
275....
271# Enable caching of up to 1000 output entriess 276# Enable caching of up to 1000 output entriess
272cache-size=1000 277cache-size=1000
273 278
274 279
275# Specify some default clone prefixes 280# Specify some default clone prefixes
276clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 281clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
277 282
278# Specify the css url 283# Specify the css url
279css=/css/cgit.css 284css=/css/cgit.css
280 285
281 286
282# Show extra links for each repository on the index page 287# Show extra links for each repository on the index page
283enable-index-links=1 288enable-index-links=1
284 289
285 290
286# Show number of affected files per commit on the log pages 291# Show number of affected files per commit on the log pages
287enable-log-filecount=1 292enable-log-filecount=1
288 293
289 294
290# Show number of added/removed lines per commit on the log pages 295# Show number of added/removed lines per commit on the log pages
291enable-log-linecount=1 296enable-log-linecount=1
292 297
293 298
294# Add a cgit favicon 299# Add a cgit favicon
@@ -356,42 +361,43 @@ repo.path=/pub/git/wiz.git
356repo.desc=the wizard of foo 361repo.desc=the wizard of foo
357 362
358 363
359# Add some mirrored repositories 364# Add some mirrored repositories
360repo.group=mirrors 365repo.group=mirrors
361 366
362 367
363repo.url=git 368repo.url=git
364repo.path=/pub/git/git.git 369repo.path=/pub/git/git.git
365repo.desc=the dscm 370repo.desc=the dscm
366 371
367 372
368repo.url=linux 373repo.url=linux
369repo.path=/pub/git/linux.git 374repo.path=/pub/git/linux.git
370repo.desc=the kernel 375repo.desc=the kernel
371 376
372# Disable adhoc downloads of this repo 377# Disable adhoc downloads of this repo
373repo.snapshots=0 378repo.snapshots=0
374 379
375# Disable line-counts for this repo 380# Disable line-counts for this repo
376repo.enable-log-linecount=0 381repo.enable-log-linecount=0
377 382
378# Restrict the max statistics period for this repo 383# Restrict the max statistics period for this repo
379repo.max-stats=month 384repo.max-stats=month
385....
380 386
381 387
382BUGS 388BUGS
383---- 389----
384Comments currently cannot appear on the same line as a setting; the comment 390Comments currently cannot appear on the same line as a setting; the comment
385will be included as part of the value. E.g. this line: 391will be included as part of the value. E.g. this line:
386 392
387 robots=index # allow indexing 393 robots=index # allow indexing
388 394
389will generate the following html element: 395will generate the following html element:
390 396
391 <meta name='robots' content='index # allow indexing'/> 397 <meta name='robots' content='index # allow indexing'/>
392 398
393 399
394 400
395AUTHOR 401AUTHOR
396------ 402------
397Lars Hjemli <hjemli@gmail.com> 403Lars Hjemli <hjemli@gmail.com>
diff --git a/ui-atom.c b/ui-atom.c
index a6ea3ee..e5c31d9 100644
--- a/ui-atom.c
+++ b/ui-atom.c
@@ -31,49 +31,50 @@ void add_entry(struct commit *commit, char *host)
31 html("<name>"); 31 html("<name>");
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) {
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)
39 t++; 39 t++;
40 else 40 else
41 t = mail; 41 t = mail;
42 t2 = strchr(t, '>'); 42 t2 = strchr(t, '>');
43 if (t2) 43 if (t2)
44 *t2 = '\0'; 44 *t2 = '\0';
45 html("<email>"); 45 html("<email>");
46 html_txt(t); 46 html_txt(t);
47 html("</email>\n"); 47 html("</email>\n");
48 free(mail); 48 free(mail);
49 } 49 }
50 html("</author>\n"); 50 html("</author>\n");
51 html("<published>"); 51 html("<published>");
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)
59 delim = '?'; 60 delim = '?';
60 htmlf("%cid=%s", delim, hex); 61 htmlf("%cid=%s", delim, hex);
61 html("'/>\n"); 62 html("'/>\n");
62 } 63 }
63 htmlf("<id>%s</id>\n", hex); 64 htmlf("<id>%s</id>\n", hex);
64 html("<content type='text'>\n"); 65 html("<content type='text'>\n");
65 html_txt(info->msg); 66 html_txt(info->msg);
66 html("</content>\n"); 67 html("</content>\n");
67 html("<content type='xhtml'>\n"); 68 html("<content type='xhtml'>\n");
68 html("<div xmlns='http://www.w3.org/1999/xhtml'>\n"); 69 html("<div xmlns='http://www.w3.org/1999/xhtml'>\n");
69 html("<pre>\n"); 70 html("<pre>\n");
70 html_txt(info->msg); 71 html_txt(info->msg);
71 html("</pre>\n"); 72 html("</pre>\n");
72 html("</div>\n"); 73 html("</div>\n");
73 html("</content>\n"); 74 html("</content>\n");
74 html("</entry>\n"); 75 html("</entry>\n");
75 cgit_free_commitinfo(info); 76 cgit_free_commitinfo(info);
76} 77}
77 78
78 79
79void cgit_print_atom(char *tip, char *path, int max_count) 80void cgit_print_atom(char *tip, char *path, int max_count)
@@ -92,38 +93,39 @@ void cgit_print_atom(char *tip, char *path, int max_count)
92 argv[argc++] = path; 93 argv[argc++] = path;
93 } 94 }
94 95
95 init_revisions(&rev, NULL); 96 init_revisions(&rev, NULL);
96 rev.abbrev = DEFAULT_ABBREV; 97 rev.abbrev = DEFAULT_ABBREV;
97 rev.commit_format = CMIT_FMT_DEFAULT; 98 rev.commit_format = CMIT_FMT_DEFAULT;
98 rev.verbose_header = 1; 99 rev.verbose_header = 1;
99 rev.show_root_diff = 0; 100 rev.show_root_diff = 0;
100 rev.max_count = max_count; 101 rev.max_count = max_count;
101 setup_revisions(argc, argv, &rev, NULL); 102 setup_revisions(argc, argv, &rev, NULL);
102 prepare_revision_walk(&rev); 103 prepare_revision_walk(&rev);
103 104
104 host = cgit_hosturl(); 105 host = cgit_hosturl();
105 ctx.page.mimetype = "text/xml"; 106 ctx.page.mimetype = "text/xml";
106 ctx.page.charset = "utf-8"; 107 ctx.page.charset = "utf-8";
107 cgit_print_http_headers(&ctx); 108 cgit_print_http_headers(&ctx);
108 html("<feed xmlns='http://www.w3.org/2005/Atom'>\n"); 109 html("<feed xmlns='http://www.w3.org/2005/Atom'>\n");
109 html("<title>"); 110 html("<title>");
110 html_txt(ctx.repo->name); 111 html_txt(ctx.repo->name);
111 html("</title>\n"); 112 html("</title>\n");
112 html("<subtitle>"); 113 html("<subtitle>");
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");
120 } 122 }
121 while ((commit = get_revision(&rev)) != NULL) { 123 while ((commit = get_revision(&rev)) != NULL) {
122 add_entry(commit, host); 124 add_entry(commit, host);
123 free(commit->buffer); 125 free(commit->buffer);
124 commit->buffer = NULL; 126 commit->buffer = NULL;
125 free_commit_list(commit->parents); 127 free_commit_list(commit->parents);
126 commit->parents = NULL; 128 commit->parents = NULL;
127 } 129 }
128 html("</feed>\n"); 130 html("</feed>\n");
129} 131}
diff --git a/ui-blob.c b/ui-blob.c
index 3cda03d..2ccd31d 100644
--- a/ui-blob.c
+++ b/ui-blob.c
@@ -6,68 +6,74 @@
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12 12
13static char *match_path; 13static char *match_path;
14static unsigned char *matched_sha1; 14static unsigned char *matched_sha1;
15 15
16static int walk_tree(const unsigned char *sha1, const char *base,int baselen, 16static int walk_tree(const unsigned char *sha1, const char *base,int baselen,
17 const char *pathname, unsigned mode, int stage, void *cbdata) { 17 const char *pathname, unsigned mode, int stage, void *cbdata) {
18 if(strncmp(base,match_path,baselen) 18 if(strncmp(base,match_path,baselen)
19 || strcmp(match_path+baselen,pathname) ) 19 || strcmp(match_path+baselen,pathname) )
20 return READ_TREE_RECURSIVE; 20 return READ_TREE_RECURSIVE;
21 memmove(matched_sha1,sha1,20); 21 memmove(matched_sha1,sha1,20);
22 return 0; 22 return 0;
23} 23}
24 24
25void cgit_print_blob(const char *hex, char *path, const char *head) 25void cgit_print_blob(const char *hex, char *path, const char *head)
26{ 26{
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};
34 34
35 if (hex) { 35 if (hex) {
36 if (get_sha1_hex(hex, sha1)){ 36 if (get_sha1_hex(hex, sha1)){
37 cgit_print_error(fmt("Bad hex value: %s", hex)); 37 cgit_print_error(fmt("Bad hex value: %s", hex));
38 return; 38 return;
39 } 39 }
40 } else { 40 } else {
41 if (get_sha1(head,sha1)) { 41 if (get_sha1(head,sha1)) {
42 cgit_print_error(fmt("Bad ref: %s", head)); 42 cgit_print_error(fmt("Bad ref: %s", head));
43 return; 43 return;
44 } 44 }
45 } 45 }
46 46
47 type = sha1_object_info(sha1, &size); 47 type = sha1_object_info(sha1, &size);
48 48
49 if((!hex) && type == OBJ_COMMIT && path) { 49 if((!hex) && type == OBJ_COMMIT && path) {
50 commit = lookup_commit_reference(sha1); 50 commit = lookup_commit_reference(sha1);
51 match_path = path; 51 match_path = path;
52 matched_sha1 = sha1; 52 matched_sha1 = sha1;
53 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL); 53 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL);
54 type = sha1_object_info(sha1,&size); 54 type = sha1_object_info(sha1,&size);
55 } 55 }
56 56
57 if (type == OBJ_BAD) { 57 if (type == OBJ_BAD) {
58 cgit_print_error(fmt("Bad object name: %s", hex)); 58 cgit_print_error(fmt("Bad object name: %s", hex));
59 return; 59 return;
60 } 60 }
61 61
62 buf = read_sha1_file(sha1, &type, &size); 62 buf = read_sha1_file(sha1, &type, &size);
63 if (!buf) { 63 if (!buf) {
64 cgit_print_error(fmt("Error reading object %s", hex)); 64 cgit_print_error(fmt("Error reading object %s", hex));
65 return; 65 return;
66 } 66 }
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);
73} 79}
diff --git a/ui-plain.c b/ui-plain.c
index 5addd9e..93a3a05 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -10,51 +10,55 @@
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12 12
13char *curr_rev; 13char *curr_rev;
14char *match_path; 14char *match_path;
15int match; 15int match;
16 16
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;
21 unsigned long size; 21 unsigned long size;
22 22
23 type = sha1_object_info(sha1, &size); 23 type = sha1_object_info(sha1, &size);
24 if (type == OBJ_BAD) { 24 if (type == OBJ_BAD) {
25 html_status(404, "Not found", 0); 25 html_status(404, "Not found", 0);
26 return; 26 return;
27 } 27 }
28 28
29 buf = read_sha1_file(sha1, &type, &size); 29 buf = read_sha1_file(sha1, &type, &size);
30 if (!buf) { 30 if (!buf) {
31 html_status(404, "Not found", 0); 31 html_status(404, "Not found", 0);
32 return; 32 return;
33 } 33 }
34 ctx.page.mimetype = "text/plain"; 34 if (buffer_is_binary(buf, size))
35 ctx.page.mimetype = "application/octet-stream";
36 else
37 ctx.page.mimetype = "text/plain";
35 ctx.page.filename = fmt("%s", path); 38 ctx.page.filename = fmt("%s", path);
36 ctx.page.size = size; 39 ctx.page.size = size;
40 ctx.page.etag = sha1_to_hex(sha1);
37 cgit_print_http_headers(&ctx); 41 cgit_print_http_headers(&ctx);
38 html_raw(buf, size); 42 html_raw(buf, size);
39 match = 1; 43 match = 1;
40} 44}
41 45
42static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 46static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
43 const char *pathname, unsigned mode, int stage, 47 const char *pathname, unsigned mode, int stage,
44 void *cbdata) 48 void *cbdata)
45{ 49{
46 if (S_ISDIR(mode)) 50 if (S_ISDIR(mode))
47 return READ_TREE_RECURSIVE; 51 return READ_TREE_RECURSIVE;
48 52
49 if (S_ISREG(mode)) 53 if (S_ISREG(mode))
50 print_object(sha1, pathname); 54 print_object(sha1, pathname);
51 55
52 return 0; 56 return 0;
53} 57}
54 58
55void cgit_print_plain(struct cgit_context *ctx) 59void cgit_print_plain(struct cgit_context *ctx)
56{ 60{
57 const char *rev = ctx->qry.sha1; 61 const char *rev = ctx->qry.sha1;
58 unsigned char sha1[20]; 62 unsigned char sha1[20];
59 struct commit *commit; 63 struct commit *commit;
60 const char *paths[] = {ctx->qry.path, NULL}; 64 const char *paths[] = {ctx->qry.path, NULL};
diff --git a/ui-shared.c b/ui-shared.c
index 5e03a7a..015c52b 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -13,48 +13,59 @@
13const char cgit_doctype[] = 13const char cgit_doctype[] =
14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
16 16
17static char *http_date(time_t t) 17static char *http_date(time_t t)
18{ 18{
19 static char day[][4] = 19 static char day[][4] =
20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
21 static char month[][4] = 21 static char month[][4] =
22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
23 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"}; 23 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"};
24 struct tm *tm = gmtime(&t); 24 struct tm *tm = gmtime(&t);
25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
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(char *msg)
31{ 31{
32 html("<div class='error'>"); 32 html("<div class='error'>");
33 html_txt(msg); 33 html_txt(msg);
34 html("</div>\n"); 34 html("</div>\n");
35} 35}
36 36
37char *cgit_httpscheme()
38{
39 char *https;
40
41 https = getenv("HTTPS");
42 if (https != NULL && strcmp(https, "on") == 0)
43 return "https://";
44 else
45 return "http://";
46}
47
37char *cgit_hosturl() 48char *cgit_hosturl()
38{ 49{
39 char *host, *port; 50 char *host, *port;
40 51
41 host = getenv("HTTP_HOST"); 52 host = getenv("HTTP_HOST");
42 if (host) { 53 if (host) {
43 host = xstrdup(host); 54 host = xstrdup(host);
44 } else { 55 } else {
45 host = getenv("SERVER_NAME"); 56 host = getenv("SERVER_NAME");
46 if (!host) 57 if (!host)
47 return NULL; 58 return NULL;
48 port = getenv("SERVER_PORT"); 59 port = getenv("SERVER_PORT");
49 if (port && atoi(port) != 80) 60 if (port && atoi(port) != 80)
50 host = xstrdup(fmt("%s:%d", host, atoi(port))); 61 host = xstrdup(fmt("%s:%d", host, atoi(port)));
51 else 62 else
52 host = xstrdup(host); 63 host = xstrdup(host);
53 } 64 }
54 return host; 65 return host;
55} 66}
56 67
57char *cgit_rooturl() 68char *cgit_rooturl()
58{ 69{
59 if (ctx.cfg.virtual_root) 70 if (ctx.cfg.virtual_root)
60 return fmt("%s/", ctx.cfg.virtual_root); 71 return fmt("%s/", ctx.cfg.virtual_root);
@@ -435,96 +446,107 @@ void cgit_print_age(time_t t, time_t max_relative, char *format)
435 secs * 1.0 / TM_HOUR); 446 secs * 1.0 / TM_HOUR);
436 return; 447 return;
437 } 448 }
438 if (secs < TM_WEEK * 2) { 449 if (secs < TM_WEEK * 2) {
439 htmlf("<span class='age-days'>%.0f days</span>", 450 htmlf("<span class='age-days'>%.0f days</span>",
440 secs * 1.0 / TM_DAY); 451 secs * 1.0 / TM_DAY);
441 return; 452 return;
442 } 453 }
443 if (secs < TM_MONTH * 2) { 454 if (secs < TM_MONTH * 2) {
444 htmlf("<span class='age-weeks'>%.0f weeks</span>", 455 htmlf("<span class='age-weeks'>%.0f weeks</span>",
445 secs * 1.0 / TM_WEEK); 456 secs * 1.0 / TM_WEEK);
446 return; 457 return;
447 } 458 }
448 if (secs < TM_YEAR * 2) { 459 if (secs < TM_YEAR * 2) {
449 htmlf("<span class='age-months'>%.0f months</span>", 460 htmlf("<span class='age-months'>%.0f months</span>",
450 secs * 1.0 / TM_MONTH); 461 secs * 1.0 / TM_MONTH);
451 return; 462 return;
452 } 463 }
453 htmlf("<span class='age-years'>%.0f years</span>", 464 htmlf("<span class='age-years'>%.0f years</span>",
454 secs * 1.0 / TM_YEAR); 465 secs * 1.0 / TM_YEAR);
455} 466}
456 467
457void cgit_print_http_headers(struct cgit_context *ctx) 468void cgit_print_http_headers(struct cgit_context *ctx)
458{ 469{
470 const char *method = getenv("REQUEST_METHOD");
471
459 if (ctx->cfg.embedded) 472 if (ctx->cfg.embedded)
460 return; 473 return;
461 474
475 if (ctx->page.status)
476 htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg);
462 if (ctx->page.mimetype && ctx->page.charset) 477 if (ctx->page.mimetype && ctx->page.charset)
463 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 478 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
464 ctx->page.charset); 479 ctx->page.charset);
465 else if (ctx->page.mimetype) 480 else if (ctx->page.mimetype)
466 htmlf("Content-Type: %s\n", ctx->page.mimetype); 481 htmlf("Content-Type: %s\n", ctx->page.mimetype);
467 if (ctx->page.size) 482 if (ctx->page.size)
468 htmlf("Content-Length: %ld\n", ctx->page.size); 483 htmlf("Content-Length: %ld\n", ctx->page.size);
469 if (ctx->page.filename) 484 if (ctx->page.filename)
470 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 485 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
471 ctx->page.filename); 486 ctx->page.filename);
472 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 487 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
473 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 488 htmlf("Expires: %s\n", http_date(ctx->page.expires));
489 if (ctx->page.etag)
490 htmlf("ETag: \"%s\"\n", ctx->page.etag);
474 html("\n"); 491 html("\n");
492 if (method && !strcmp(method, "HEAD"))
493 exit(0);
475} 494}
476 495
477void cgit_print_docstart(struct cgit_context *ctx) 496void cgit_print_docstart(struct cgit_context *ctx)
478{ 497{
479 if (ctx->cfg.embedded) 498 if (ctx->cfg.embedded)
480 return; 499 return;
481 500
482 char *host = cgit_hosturl(); 501 char *host = cgit_hosturl();
483 html(cgit_doctype); 502 html(cgit_doctype);
484 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 503 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
485 html("<head>\n"); 504 html("<head>\n");
486 html("<title>"); 505 html("<title>");
487 html_txt(ctx->page.title); 506 html_txt(ctx->page.title);
488 html("</title>\n"); 507 html("</title>\n");
489 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 508 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
490 if (ctx->cfg.robots && *ctx->cfg.robots) 509 if (ctx->cfg.robots && *ctx->cfg.robots)
491 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 510 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
492 html("<link rel='stylesheet' type='text/css' href='"); 511 html("<link rel='stylesheet' type='text/css' href='");
493 html_attr(ctx->cfg.css); 512 html_attr(ctx->cfg.css);
494 html("'/>\n"); 513 html("'/>\n");
495 if (ctx->cfg.favicon) { 514 if (ctx->cfg.favicon) {
496 html("<link rel='shortcut icon' href='"); 515 html("<link rel='shortcut icon' href='");
497 html_attr(ctx->cfg.favicon); 516 html_attr(ctx->cfg.favicon);
498 html("'/>\n"); 517 html("'/>\n");
499 } 518 }
500 if (host && ctx->repo) { 519 if (host && ctx->repo) {
501 html("<link rel='alternate' title='Atom feed' href='http://"); 520 html("<link rel='alternate' title='Atom feed' href='");
521 html(cgit_httpscheme());
502 html_attr(cgit_hosturl()); 522 html_attr(cgit_hosturl());
503 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 523 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path,
504 fmt("h=%s", ctx->qry.head))); 524 fmt("h=%s", ctx->qry.head)));
505 html("' type='application/atom+xml'/>"); 525 html("' type='application/atom+xml'/>\n");
506 } 526 }
527 if (ctx->cfg.head_include)
528 html_include(ctx->cfg.head_include);
507 html("</head>\n"); 529 html("</head>\n");
508 html("<body>\n"); 530 html("<body>\n");
509 if (ctx->cfg.header) 531 if (ctx->cfg.header)
510 html_include(ctx->cfg.header); 532 html_include(ctx->cfg.header);
511} 533}
512 534
513void cgit_print_docend() 535void cgit_print_docend()
514{ 536{
515 html("</div>"); 537 html("</div>");
516 if (ctx.cfg.footer) 538 if (ctx.cfg.footer)
517 html_include(ctx.cfg.footer); 539 html_include(ctx.cfg.footer);
518 else { 540 else {
519 htmlf("<div class='footer'>generated by cgit %s at ", 541 htmlf("<div class='footer'>generated by cgit %s at ",
520 cgit_version); 542 cgit_version);
521 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); 543 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
522 html("</div>\n"); 544 html("</div>\n");
523 } 545 }
524 html("</div>"); 546 html("</div>");
525 if (ctx.cfg.embedded) 547 if (ctx.cfg.embedded)
526 return; 548 return;
527 html("</body>\n</html>\n"); 549 html("</body>\n</html>\n");
528} 550}
529 551
530int print_branch_option(const char *refname, const unsigned char *sha1, 552int print_branch_option(const char *refname, const unsigned char *sha1,
diff --git a/ui-shared.h b/ui-shared.h
index 5a3821f..bff4826 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,27 +1,28 @@
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,
7 const char *filename, const char *query); 8 const char *filename, const char *query);
8extern char *cgit_pageurl(const char *reponame, const char *pagename, 9extern char *cgit_pageurl(const char *reponame, const char *pagename,
9 const char *query); 10 const char *query);
10 11
11extern void cgit_index_link(char *name, char *title, char *class, 12extern void cgit_index_link(char *name, char *title, char *class,
12 char *pattern, int ofs); 13 char *pattern, int ofs);
13extern void cgit_summary_link(char *name, char *title, char *class, char *head); 14extern void cgit_summary_link(char *name, char *title, char *class, char *head);
14extern void cgit_tag_link(char *name, char *title, char *class, char *head, 15extern void cgit_tag_link(char *name, char *title, char *class, char *head,
15 char *rev); 16 char *rev);
16extern void cgit_tree_link(char *name, char *title, char *class, char *head, 17extern void cgit_tree_link(char *name, char *title, char *class, char *head,
17 char *rev, char *path); 18 char *rev, char *path);
18extern void cgit_plain_link(char *name, char *title, char *class, char *head, 19extern void cgit_plain_link(char *name, char *title, char *class, char *head,
19 char *rev, char *path); 20 char *rev, char *path);
20extern void cgit_log_link(char *name, char *title, char *class, char *head, 21extern void cgit_log_link(char *name, char *title, char *class, char *head,
21 char *rev, char *path, int ofs, char *grep, 22 char *rev, char *path, int ofs, char *grep,
22 char *pattern, int showmsg); 23 char *pattern, int showmsg);
23extern void cgit_commit_link(char *name, char *title, char *class, char *head, 24extern void cgit_commit_link(char *name, char *title, char *class, char *head,
24 char *rev); 25 char *rev);
25extern void cgit_patch_link(char *name, char *title, char *class, char *head, 26extern void cgit_patch_link(char *name, char *title, char *class, char *head,
26 char *rev); 27 char *rev);
27extern void cgit_refs_link(char *name, char *title, char *class, char *head, 28extern void cgit_refs_link(char *name, char *title, char *class, char *head,
diff --git a/ui-snapshot.c b/ui-snapshot.c
index f25613e..5372f5d 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -135,60 +135,71 @@ static const char *get_ref_from_filename(const char *url, const char *filename,
135 snapshot[strlen(snapshot) - strlen(format->suffix)] = '\0'; 135 snapshot[strlen(snapshot) - strlen(format->suffix)] = '\0';
136 fprintf(stderr, "snapshot=%s\n", snapshot); 136 fprintf(stderr, "snapshot=%s\n", snapshot);
137 137
138 if (get_sha1(snapshot, sha1) == 0) 138 if (get_sha1(snapshot, sha1) == 0)
139 return snapshot; 139 return snapshot;
140 140
141 reponame = cgit_repobasename(url); 141 reponame = cgit_repobasename(url);
142 fprintf(stderr, "reponame=%s\n", reponame); 142 fprintf(stderr, "reponame=%s\n", reponame);
143 if (prefixcmp(snapshot, reponame) == 0) { 143 if (prefixcmp(snapshot, reponame) == 0) {
144 snapshot += strlen(reponame); 144 snapshot += strlen(reponame);
145 while (snapshot && (*snapshot == '-' || *snapshot == '_')) 145 while (snapshot && (*snapshot == '-' || *snapshot == '_'))
146 snapshot++; 146 snapshot++;
147 } 147 }
148 148
149 if (get_sha1(snapshot, sha1) == 0) 149 if (get_sha1(snapshot, sha1) == 0)
150 return snapshot; 150 return snapshot;
151 151
152 snapshot = fmt("v%s", snapshot); 152 snapshot = fmt("v%s", snapshot);
153 if (get_sha1(snapshot, sha1) == 0) 153 if (get_sha1(snapshot, sha1) == 0)
154 return snapshot; 154 return snapshot;
155 155
156 return NULL; 156 return NULL;
157} 157}
158 158
159void show_error(char *msg)
160{
161 ctx.page.mimetype = "text/html";
162 cgit_print_http_headers(&ctx);
163 cgit_print_docstart(&ctx);
164 cgit_print_pageheader(&ctx);
165 cgit_print_error(msg);
166 cgit_print_docend();
167}
168
159void cgit_print_snapshot(const char *head, const char *hex, 169void cgit_print_snapshot(const char *head, const char *hex,
160 const char *filename, int snapshots, int dwim) 170 const char *filename, int snapshots, int dwim)
161{ 171{
162 const struct cgit_snapshot_format* f; 172 const struct cgit_snapshot_format* f;
163 char *prefix = NULL; 173 char *prefix = NULL;
164 174
175 if (!filename) {
176 show_error("No snapshot name specified");
177 return;
178 }
179
165 f = get_format(filename); 180 f = get_format(filename);
166 if (!f) { 181 if (!f) {
167 ctx.page.mimetype = "text/html"; 182 show_error(xstrdup(fmt("Unsupported snapshot format: %s",
168 cgit_print_http_headers(&ctx); 183 filename)));
169 cgit_print_docstart(&ctx);
170 cgit_print_pageheader(&ctx);
171 cgit_print_error(fmt("Unsupported snapshot format: %s", filename));
172 cgit_print_docend();
173 return; 184 return;
174 } 185 }
175 186
176 if (!hex && dwim) { 187 if (!hex && dwim) {
177 hex = get_ref_from_filename(ctx.repo->url, filename, f); 188 hex = get_ref_from_filename(ctx.repo->url, filename, f);
178 if (hex == NULL) { 189 if (hex == NULL) {
179 html_status(404, "Not found", 0); 190 html_status(404, "Not found", 0);
180 return; 191 return;
181 } 192 }
182 prefix = xstrdup(filename); 193 prefix = xstrdup(filename);
183 prefix[strlen(filename) - strlen(f->suffix)] = '\0'; 194 prefix[strlen(filename) - strlen(f->suffix)] = '\0';
184 } 195 }
185 196
186 if (!hex) 197 if (!hex)
187 hex = head; 198 hex = head;
188 199
189 if (!prefix) 200 if (!prefix)
190 prefix = xstrdup(cgit_repobasename(ctx.repo->url)); 201 prefix = xstrdup(cgit_repobasename(ctx.repo->url));
191 202
192 make_snapshot(f, hex, prefix, filename); 203 make_snapshot(f, hex, prefix, filename);
193 free(prefix); 204 free(prefix);
194} 205}
diff --git a/ui-tree.c b/ui-tree.c
index a37a4e5..553dbaa 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -4,75 +4,83 @@
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include <ctype.h> 9#include <ctype.h>
10#include "cgit.h" 10#include "cgit.h"
11#include "html.h" 11#include "html.h"
12#include "ui-shared.h" 12#include "ui-shared.h"
13 13
14char *curr_rev; 14char *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(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 html("<tr><td class='linenumbers'><pre>"); 25 html("<tr><td class='linenumbers'><pre>");
26 idx = 0; 26 idx = 0;
27 lineno = 0; 27 lineno = 0;
28 htmlf(numberfmt, ++lineno); 28
29 while(idx < size - 1) { // skip absolute last newline 29 if (size) {
30 if (buf[idx] == '\n') 30 htmlf(numberfmt, ++lineno);
31 htmlf(numberfmt, ++lineno); 31 while(idx < size - 1) { // skip absolute last newline
32 idx++; 32 if (buf[idx] == '\n')
33 htmlf(numberfmt, ++lineno);
34 idx++;
35 }
33 } 36 }
34 html("</pre></td>\n"); 37 html("</pre></td>\n");
35 html("<td class='lines'><pre><code>"); 38 html("<td class='lines'><pre><code>");
36 html_txt(buf); 39 html_txt(buf);
37 html("</code></pre></td></tr></table>\n"); 40 html("</code></pre></td></tr></table>\n");
38} 41}
39 42
43#define ROWLEN 32
44
40static void print_binary_buffer(char *buf, unsigned long size) 45static void print_binary_buffer(char *buf, unsigned long size)
41{ 46{
42 unsigned long ofs, idx; 47 unsigned long ofs, idx;
48 static char ascii[ROWLEN + 1];
43 49
44 html("<table summary='blob content' class='bin-blob'>\n"); 50 html("<table summary='blob content' class='bin-blob'>\n");
45 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>"); 51 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
46 for (ofs = 0; ofs < size; ofs += 32, buf += 32) { 52 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
47 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs); 53 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs);
48 for (idx = 0; idx < 32 && ofs + idx < size; idx++) 54 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
49 htmlf("%*s%02x", 55 htmlf("%*s%02x",
50 idx == 16 ? 4 : 1, "", 56 idx == 16 ? 4 : 1, "",
51 buf[idx] & 0xff); 57 buf[idx] & 0xff);
52 html(" </td><td class='hex'>"); 58 html(" </td><td class='hex'>");
53 for (idx = 0; idx < 32 && ofs + idx < size; idx++) 59 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
54 htmlf("%c", isgraph(buf[idx]) ? buf[idx] : '.'); 60 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
61 ascii[idx] = '\0';
62 html_txt(ascii);
55 html("</td></tr>\n"); 63 html("</td></tr>\n");
56 } 64 }
57 html("</table>\n"); 65 html("</table>\n");
58} 66}
59 67
60static void print_object(const unsigned char *sha1, char *path) 68static void print_object(const unsigned char *sha1, char *path)
61{ 69{
62 enum object_type type; 70 enum object_type type;
63 char *buf; 71 char *buf;
64 unsigned long size; 72 unsigned long size;
65 73
66 type = sha1_object_info(sha1, &size); 74 type = sha1_object_info(sha1, &size);
67 if (type == OBJ_BAD) { 75 if (type == OBJ_BAD) {
68 cgit_print_error(fmt("Bad object name: %s", 76 cgit_print_error(fmt("Bad object name: %s",
69 sha1_to_hex(sha1))); 77 sha1_to_hex(sha1)));
70 return; 78 return;
71 } 79 }
72 80
73 buf = read_sha1_file(sha1, &type, &size); 81 buf = read_sha1_file(sha1, &type, &size);
74 if (!buf) { 82 if (!buf) {
75 cgit_print_error(fmt("Error reading object %s", 83 cgit_print_error(fmt("Error reading object %s",
76 sha1_to_hex(sha1))); 84 sha1_to_hex(sha1)));
77 return; 85 return;
78 } 86 }