summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--.gitignore5
-rw-r--r--Makefile21
-rw-r--r--cache.h2
-rw-r--r--cgit-doc.css3
-rw-r--r--cgit.c345
-rw-r--r--cgit.css10
-rw-r--r--cgit.h52
-rw-r--r--cgitrc.5.txt244
-rw-r--r--cmd.c2
-rwxr-xr-xfilters/commit-links.sh12
-rwxr-xr-xfilters/syntax-highlighting.sh39
m---------git0
-rw-r--r--scan-tree.c45
-rw-r--r--scan-tree.h2
-rw-r--r--shared.c62
-rw-r--r--ui-atom.c8
-rw-r--r--ui-blob.c8
-rw-r--r--ui-commit.c22
-rw-r--r--ui-log.c6
-rw-r--r--ui-patch.c6
-rw-r--r--ui-plain.c18
-rw-r--r--ui-refs.c19
-rw-r--r--ui-repolist.c65
-rw-r--r--ui-shared.c81
-rw-r--r--ui-shared.h1
-rw-r--r--ui-snapshot.c35
-rw-r--r--ui-stats.c8
-rw-r--r--ui-stats.h1
-rw-r--r--ui-summary.c28
-rw-r--r--ui-summary.h2
-rw-r--r--ui-tag.c2
-rw-r--r--ui-tree.c55
32 files changed, 942 insertions, 267 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 44138ea..da2518b 100644
--- a/Makefile
+++ b/Makefile
@@ -1,32 +1,32 @@
1CGIT_VERSION = v0.8.2.2 1CGIT_VERSION = v0.8.2.2
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.4.3
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')
26 26
27ifeq ($(uname_O),Cygwin) 27ifeq ($(uname_O),Cygwin)
28 NO_STRCASESTR = YesPlease 28 NO_STRCASESTR = YesPlease
29 NEEDS_LIBICONV = YesPlease 29 NEEDS_LIBICONV = YesPlease
30endif 30endif
31 31
32# 32#
@@ -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/cache.h b/cache.h
index 66cc41f..ac9276b 100644
--- a/cache.h
+++ b/cache.h
@@ -11,25 +11,27 @@ typedef void (*cache_fill_fn)(void *cbdata);
11 11
12/* Print cached content to stdout, generate the content if necessary. 12/* Print cached content to stdout, generate the content if necessary.
13 * 13 *
14 * Parameters 14 * Parameters
15 * size max number of cache files 15 * size max number of cache files
16 * path directory used to store cache files 16 * path directory used to store cache files
17 * key the key used to lookup cache files 17 * key the key used to lookup cache files
18 * ttl max cache time in seconds for this key 18 * ttl max cache time in seconds for this key
19 * fn content generator function for this key 19 * fn content generator function for this key
20 * cbdata user-supplied data to the content generator function 20 * cbdata user-supplied data to the content generator function
21 * 21 *
22 * Return value 22 * Return value
23 * 0 indicates success, everyting else is an error 23 * 0 indicates success, everyting else is an error
24 */ 24 */
25extern int cache_process(int size, const char *path, const char *key, int ttl, 25extern int cache_process(int size, const char *path, const char *key, int ttl,
26 cache_fill_fn fn, void *cbdata); 26 cache_fill_fn fn, void *cbdata);
27 27
28 28
29/* List info about all cache entries on stdout */ 29/* List info about all cache entries on stdout */
30extern int cache_ls(const char *path); 30extern int cache_ls(const char *path);
31 31
32/* Print a message to stdout */ 32/* Print a message to stdout */
33extern void cache_log(const char *format, ...); 33extern void cache_log(const char *format, ...);
34 34
35extern unsigned long hash_str(const char *str);
36
35#endif /* CGIT_CACHE_H */ 37#endif /* CGIT_CACHE_H */
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 5301840..bd37788 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,235 +1,329 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "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 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
43static void process_cached_repolist(const char *path);
44
45void repo_config(struct cgit_repo *repo, const char *name, const char *value)
46{
47 if (!strcmp(name, "name"))
48 repo->name = xstrdup(value);
49 else if (!strcmp(name, "clone-url"))
50 repo->clone_url = xstrdup(value);
51 else if (!strcmp(name, "desc"))
52 repo->desc = xstrdup(value);
53 else if (!strcmp(name, "owner"))
54 repo->owner = xstrdup(value);
55 else if (!strcmp(name, "defbranch"))
56 repo->defbranch = xstrdup(value);
57 else if (!strcmp(name, "snapshots"))
58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
59 else if (!strcmp(name, "enable-log-filecount"))
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
63 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link"))
66 repo->module_link= xstrdup(value);
67 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value);
69 else if (!strcmp(name, "readme") && value != NULL) {
70 if (*value == '/')
71 ctx.repo->readme = xstrdup(value);
72 else
73 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
74 } else if (ctx.cfg.enable_filter_overrides) {
75 if (!strcmp(name, "about-filter"))
76 repo->about_filter = new_filter(value, 0);
77 else if (!strcmp(name, "commit-filter"))
78 repo->commit_filter = new_filter(value, 0);
79 else if (!strcmp(name, "source-filter"))
80 repo->source_filter = new_filter(value, 1);
81 }
82}
83
20void config_cb(const char *name, const char *value) 84void config_cb(const char *name, const char *value)
21{ 85{
22 if (!strcmp(name, "root-title")) 86 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
87 ctx.cfg.section = xstrdup(value);
88 else if (!strcmp(name, "repo.url"))
89 ctx.repo = cgit_add_repo(value);
90 else if (ctx.repo && !strcmp(name, "repo.path"))
91 ctx.repo->path = trim_end(value, '/');
92 else if (ctx.repo && !prefixcmp(name, "repo."))
93 repo_config(ctx.repo, name + 5, value);
94 else if (!strcmp(name, "root-title"))
23 ctx.cfg.root_title = xstrdup(value); 95 ctx.cfg.root_title = xstrdup(value);
24 else if (!strcmp(name, "root-desc")) 96 else if (!strcmp(name, "root-desc"))
25 ctx.cfg.root_desc = xstrdup(value); 97 ctx.cfg.root_desc = xstrdup(value);
26 else if (!strcmp(name, "root-readme")) 98 else if (!strcmp(name, "root-readme"))
27 ctx.cfg.root_readme = xstrdup(value); 99 ctx.cfg.root_readme = xstrdup(value);
28 else if (!strcmp(name, "css")) 100 else if (!strcmp(name, "css"))
29 ctx.cfg.css = xstrdup(value); 101 ctx.cfg.css = xstrdup(value);
30 else if (!strcmp(name, "favicon")) 102 else if (!strcmp(name, "favicon"))
31 ctx.cfg.favicon = xstrdup(value); 103 ctx.cfg.favicon = xstrdup(value);
32 else if (!strcmp(name, "footer")) 104 else if (!strcmp(name, "footer"))
33 ctx.cfg.footer = xstrdup(value); 105 ctx.cfg.footer = xstrdup(value);
106 else if (!strcmp(name, "head-include"))
107 ctx.cfg.head_include = xstrdup(value);
34 else if (!strcmp(name, "header")) 108 else if (!strcmp(name, "header"))
35 ctx.cfg.header = xstrdup(value); 109 ctx.cfg.header = xstrdup(value);
36 else if (!strcmp(name, "logo")) 110 else if (!strcmp(name, "logo"))
37 ctx.cfg.logo = xstrdup(value); 111 ctx.cfg.logo = xstrdup(value);
38 else if (!strcmp(name, "index-header")) 112 else if (!strcmp(name, "index-header"))
39 ctx.cfg.index_header = xstrdup(value); 113 ctx.cfg.index_header = xstrdup(value);
40 else if (!strcmp(name, "index-info")) 114 else if (!strcmp(name, "index-info"))
41 ctx.cfg.index_info = xstrdup(value); 115 ctx.cfg.index_info = xstrdup(value);
42 else if (!strcmp(name, "logo-link")) 116 else if (!strcmp(name, "logo-link"))
43 ctx.cfg.logo_link = xstrdup(value); 117 ctx.cfg.logo_link = xstrdup(value);
44 else if (!strcmp(name, "module-link")) 118 else if (!strcmp(name, "module-link"))
45 ctx.cfg.module_link = xstrdup(value); 119 ctx.cfg.module_link = xstrdup(value);
46 else if (!strcmp(name, "virtual-root")) { 120 else if (!strcmp(name, "virtual-root")) {
47 ctx.cfg.virtual_root = trim_end(value, '/'); 121 ctx.cfg.virtual_root = trim_end(value, '/');
48 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 122 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
49 ctx.cfg.virtual_root = ""; 123 ctx.cfg.virtual_root = "";
50 } else if (!strcmp(name, "nocache")) 124 } else if (!strcmp(name, "nocache"))
51 ctx.cfg.nocache = atoi(value); 125 ctx.cfg.nocache = atoi(value);
126 else if (!strcmp(name, "noplainemail"))
127 ctx.cfg.noplainemail = atoi(value);
128 else if (!strcmp(name, "noheader"))
129 ctx.cfg.noheader = atoi(value);
52 else if (!strcmp(name, "snapshots")) 130 else if (!strcmp(name, "snapshots"))
53 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 131 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
132 else if (!strcmp(name, "enable-filter-overrides"))
133 ctx.cfg.enable_filter_overrides = atoi(value);
54 else if (!strcmp(name, "enable-index-links")) 134 else if (!strcmp(name, "enable-index-links"))
55 ctx.cfg.enable_index_links = atoi(value); 135 ctx.cfg.enable_index_links = atoi(value);
56 else if (!strcmp(name, "enable-log-filecount")) 136 else if (!strcmp(name, "enable-log-filecount"))
57 ctx.cfg.enable_log_filecount = atoi(value); 137 ctx.cfg.enable_log_filecount = atoi(value);
58 else if (!strcmp(name, "enable-log-linecount")) 138 else if (!strcmp(name, "enable-log-linecount"))
59 ctx.cfg.enable_log_linecount = atoi(value); 139 ctx.cfg.enable_log_linecount = atoi(value);
140 else if (!strcmp(name, "enable-tree-linenumbers"))
141 ctx.cfg.enable_tree_linenumbers = atoi(value);
60 else if (!strcmp(name, "max-stats")) 142 else if (!strcmp(name, "max-stats"))
61 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
62 else if (!strcmp(name, "cache-size")) 144 else if (!strcmp(name, "cache-size"))
63 ctx.cfg.cache_size = atoi(value); 145 ctx.cfg.cache_size = atoi(value);
64 else if (!strcmp(name, "cache-root")) 146 else if (!strcmp(name, "cache-root"))
65 ctx.cfg.cache_root = xstrdup(value); 147 ctx.cfg.cache_root = xstrdup(value);
66 else if (!strcmp(name, "cache-root-ttl")) 148 else if (!strcmp(name, "cache-root-ttl"))
67 ctx.cfg.cache_root_ttl = atoi(value); 149 ctx.cfg.cache_root_ttl = atoi(value);
68 else if (!strcmp(name, "cache-repo-ttl")) 150 else if (!strcmp(name, "cache-repo-ttl"))
69 ctx.cfg.cache_repo_ttl = atoi(value); 151 ctx.cfg.cache_repo_ttl = atoi(value);
152 else if (!strcmp(name, "cache-scanrc-ttl"))
153 ctx.cfg.cache_scanrc_ttl = atoi(value);
70 else if (!strcmp(name, "cache-static-ttl")) 154 else if (!strcmp(name, "cache-static-ttl"))
71 ctx.cfg.cache_static_ttl = atoi(value); 155 ctx.cfg.cache_static_ttl = atoi(value);
72 else if (!strcmp(name, "cache-dynamic-ttl")) 156 else if (!strcmp(name, "cache-dynamic-ttl"))
73 ctx.cfg.cache_dynamic_ttl = atoi(value); 157 ctx.cfg.cache_dynamic_ttl = atoi(value);
158 else if (!strcmp(name, "about-filter"))
159 ctx.cfg.about_filter = new_filter(value, 0);
160 else if (!strcmp(name, "commit-filter"))
161 ctx.cfg.commit_filter = new_filter(value, 0);
162 else if (!strcmp(name, "embedded"))
163 ctx.cfg.embedded = atoi(value);
74 else if (!strcmp(name, "max-message-length")) 164 else if (!strcmp(name, "max-message-length"))
75 ctx.cfg.max_msg_len = atoi(value); 165 ctx.cfg.max_msg_len = atoi(value);
76 else if (!strcmp(name, "max-repodesc-length")) 166 else if (!strcmp(name, "max-repodesc-length"))
77 ctx.cfg.max_repodesc_len = atoi(value); 167 ctx.cfg.max_repodesc_len = atoi(value);
78 else if (!strcmp(name, "max-repo-count")) 168 else if (!strcmp(name, "max-repo-count"))
79 ctx.cfg.max_repo_count = atoi(value); 169 ctx.cfg.max_repo_count = atoi(value);
80 else if (!strcmp(name, "max-commit-count")) 170 else if (!strcmp(name, "max-commit-count"))
81 ctx.cfg.max_commit_count = atoi(value); 171 ctx.cfg.max_commit_count = atoi(value);
172 else if (!strcmp(name, "scan-path"))
173 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
174 process_cached_repolist(value);
175 else
176 scan_tree(value, repo_config);
177 else if (!strcmp(name, "source-filter"))
178 ctx.cfg.source_filter = new_filter(value, 1);
82 else if (!strcmp(name, "summary-log")) 179 else if (!strcmp(name, "summary-log"))
83 ctx.cfg.summary_log = atoi(value); 180 ctx.cfg.summary_log = atoi(value);
84 else if (!strcmp(name, "summary-branches")) 181 else if (!strcmp(name, "summary-branches"))
85 ctx.cfg.summary_branches = atoi(value); 182 ctx.cfg.summary_branches = atoi(value);
86 else if (!strcmp(name, "summary-tags")) 183 else if (!strcmp(name, "summary-tags"))
87 ctx.cfg.summary_tags = atoi(value); 184 ctx.cfg.summary_tags = atoi(value);
88 else if (!strcmp(name, "agefile")) 185 else if (!strcmp(name, "agefile"))
89 ctx.cfg.agefile = xstrdup(value); 186 ctx.cfg.agefile = xstrdup(value);
90 else if (!strcmp(name, "renamelimit")) 187 else if (!strcmp(name, "renamelimit"))
91 ctx.cfg.renamelimit = atoi(value); 188 ctx.cfg.renamelimit = atoi(value);
92 else if (!strcmp(name, "robots")) 189 else if (!strcmp(name, "robots"))
93 ctx.cfg.robots = xstrdup(value); 190 ctx.cfg.robots = xstrdup(value);
94 else if (!strcmp(name, "clone-prefix")) 191 else if (!strcmp(name, "clone-prefix"))
95 ctx.cfg.clone_prefix = xstrdup(value); 192 ctx.cfg.clone_prefix = xstrdup(value);
96 else if (!strcmp(name, "local-time")) 193 else if (!strcmp(name, "local-time"))
97 ctx.cfg.local_time = atoi(value); 194 ctx.cfg.local_time = atoi(value);
98 else if (!strcmp(name, "repo.group")) 195 else if (!prefixcmp(name, "mimetype."))
99 ctx.cfg.repo_group = xstrdup(value); 196 add_mimetype(name + 9, value);
100 else if (!strcmp(name, "repo.url")) 197 else if (!strcmp(name, "include"))
101 ctx.repo = cgit_add_repo(value);
102 else if (!strcmp(name, "repo.name"))
103 ctx.repo->name = xstrdup(value);
104 else if (ctx.repo && !strcmp(name, "repo.path"))
105 ctx.repo->path = trim_end(value, '/');
106 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
107 ctx.repo->clone_url = xstrdup(value);
108 else if (ctx.repo && !strcmp(name, "repo.desc"))
109 ctx.repo->desc = xstrdup(value);
110 else if (ctx.repo && !strcmp(name, "repo.owner"))
111 ctx.repo->owner = xstrdup(value);
112 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
113 ctx.repo->defbranch = xstrdup(value);
114 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
115 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
116 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
117 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
118 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
119 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
120 else if (ctx.repo && !strcmp(name, "repo.max-stats"))
121 ctx.repo->max_stats = cgit_find_stats_period(value, NULL);
122 else if (ctx.repo && !strcmp(name, "repo.module-link"))
123 ctx.repo->module_link= xstrdup(value);
124 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
125 if (*value == '/')
126 ctx.repo->readme = xstrdup(value);
127 else
128 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
129 } else if (!strcmp(name, "include"))
130 parse_configfile(value, config_cb); 198 parse_configfile(value, config_cb);
131} 199}
132 200
133static void querystring_cb(const char *name, const char *value) 201static void querystring_cb(const char *name, const char *value)
134{ 202{
135 if (!value) 203 if (!value)
136 value = ""; 204 value = "";
137 205
138 if (!strcmp(name,"r")) { 206 if (!strcmp(name,"r")) {
139 ctx.qry.repo = xstrdup(value); 207 ctx.qry.repo = xstrdup(value);
140 ctx.repo = cgit_get_repoinfo(value); 208 ctx.repo = cgit_get_repoinfo(value);
141 } else if (!strcmp(name, "p")) { 209 } else if (!strcmp(name, "p")) {
142 ctx.qry.page = xstrdup(value); 210 ctx.qry.page = xstrdup(value);
143 } else if (!strcmp(name, "url")) { 211 } else if (!strcmp(name, "url")) {
144 ctx.qry.url = xstrdup(value); 212 ctx.qry.url = xstrdup(value);
145 cgit_parse_url(value); 213 cgit_parse_url(value);
146 } else if (!strcmp(name, "qt")) { 214 } else if (!strcmp(name, "qt")) {
147 ctx.qry.grep = xstrdup(value); 215 ctx.qry.grep = xstrdup(value);
148 } else if (!strcmp(name, "q")) { 216 } else if (!strcmp(name, "q")) {
149 ctx.qry.search = xstrdup(value); 217 ctx.qry.search = xstrdup(value);
150 } else if (!strcmp(name, "h")) { 218 } else if (!strcmp(name, "h")) {
151 ctx.qry.head = xstrdup(value); 219 ctx.qry.head = xstrdup(value);
152 ctx.qry.has_symref = 1; 220 ctx.qry.has_symref = 1;
153 } else if (!strcmp(name, "id")) { 221 } else if (!strcmp(name, "id")) {
154 ctx.qry.sha1 = xstrdup(value); 222 ctx.qry.sha1 = xstrdup(value);
155 ctx.qry.has_sha1 = 1; 223 ctx.qry.has_sha1 = 1;
156 } else if (!strcmp(name, "id2")) { 224 } else if (!strcmp(name, "id2")) {
157 ctx.qry.sha2 = xstrdup(value); 225 ctx.qry.sha2 = xstrdup(value);
158 ctx.qry.has_sha1 = 1; 226 ctx.qry.has_sha1 = 1;
159 } else if (!strcmp(name, "ofs")) { 227 } else if (!strcmp(name, "ofs")) {
160 ctx.qry.ofs = atoi(value); 228 ctx.qry.ofs = atoi(value);
161 } else if (!strcmp(name, "path")) { 229 } else if (!strcmp(name, "path")) {
162 ctx.qry.path = trim_end(value, '/'); 230 ctx.qry.path = trim_end(value, '/');
163 } else if (!strcmp(name, "name")) { 231 } else if (!strcmp(name, "name")) {
164 ctx.qry.name = xstrdup(value); 232 ctx.qry.name = xstrdup(value);
165 } else if (!strcmp(name, "mimetype")) { 233 } else if (!strcmp(name, "mimetype")) {
166 ctx.qry.mimetype = xstrdup(value); 234 ctx.qry.mimetype = xstrdup(value);
167 } else if (!strcmp(name, "s")){ 235 } else if (!strcmp(name, "s")){
168 ctx.qry.sort = xstrdup(value); 236 ctx.qry.sort = xstrdup(value);
169 } else if (!strcmp(name, "showmsg")) { 237 } else if (!strcmp(name, "showmsg")) {
170 ctx.qry.showmsg = atoi(value); 238 ctx.qry.showmsg = atoi(value);
171 } else if (!strcmp(name, "period")) { 239 } else if (!strcmp(name, "period")) {
172 ctx.qry.period = xstrdup(value); 240 ctx.qry.period = xstrdup(value);
173 } 241 }
174} 242}
175 243
244char *xstrdupn(const char *str)
245{
246 return (str ? xstrdup(str) : NULL);
247}
248
176static void prepare_context(struct cgit_context *ctx) 249static void prepare_context(struct cgit_context *ctx)
177{ 250{
178 memset(ctx, 0, sizeof(ctx)); 251 memset(ctx, 0, sizeof(ctx));
179 ctx->cfg.agefile = "info/web/last-modified"; 252 ctx->cfg.agefile = "info/web/last-modified";
180 ctx->cfg.nocache = 0; 253 ctx->cfg.nocache = 0;
181 ctx->cfg.cache_size = 0; 254 ctx->cfg.cache_size = 0;
182 ctx->cfg.cache_dynamic_ttl = 5; 255 ctx->cfg.cache_dynamic_ttl = 5;
183 ctx->cfg.cache_max_create_time = 5; 256 ctx->cfg.cache_max_create_time = 5;
184 ctx->cfg.cache_repo_ttl = 5; 257 ctx->cfg.cache_repo_ttl = 5;
185 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 258 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
186 ctx->cfg.cache_root_ttl = 5; 259 ctx->cfg.cache_root_ttl = 5;
260 ctx->cfg.cache_scanrc_ttl = 15;
187 ctx->cfg.cache_static_ttl = -1; 261 ctx->cfg.cache_static_ttl = -1;
188 ctx->cfg.css = "/cgit.css"; 262 ctx->cfg.css = "/cgit.css";
189 ctx->cfg.logo = "/git-logo.png"; 263 ctx->cfg.logo = "/cgit.png";
190 ctx->cfg.local_time = 0; 264 ctx->cfg.local_time = 0;
265 ctx->cfg.enable_tree_linenumbers = 1;
191 ctx->cfg.max_repo_count = 50; 266 ctx->cfg.max_repo_count = 50;
192 ctx->cfg.max_commit_count = 50; 267 ctx->cfg.max_commit_count = 50;
193 ctx->cfg.max_lock_attempts = 5; 268 ctx->cfg.max_lock_attempts = 5;
194 ctx->cfg.max_msg_len = 80; 269 ctx->cfg.max_msg_len = 80;
195 ctx->cfg.max_repodesc_len = 80; 270 ctx->cfg.max_repodesc_len = 80;
196 ctx->cfg.max_stats = 0; 271 ctx->cfg.max_stats = 0;
197 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 272 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
198 ctx->cfg.renamelimit = -1; 273 ctx->cfg.renamelimit = -1;
199 ctx->cfg.robots = "index, nofollow"; 274 ctx->cfg.robots = "index, nofollow";
200 ctx->cfg.root_title = "Git repository browser"; 275 ctx->cfg.root_title = "Git repository browser";
201 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 276 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
202 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 277 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
278 ctx->cfg.section = "";
203 ctx->cfg.summary_branches = 10; 279 ctx->cfg.summary_branches = 10;
204 ctx->cfg.summary_log = 10; 280 ctx->cfg.summary_log = 10;
205 ctx->cfg.summary_tags = 10; 281 ctx->cfg.summary_tags = 10;
282 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
283 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
284 ctx->env.https = xstrdupn(getenv("HTTPS"));
285 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
286 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
287 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
288 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
289 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
290 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
291 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
206 ctx->page.mimetype = "text/html"; 292 ctx->page.mimetype = "text/html";
207 ctx->page.charset = PAGE_ENCODING; 293 ctx->page.charset = PAGE_ENCODING;
208 ctx->page.filename = NULL; 294 ctx->page.filename = NULL;
209 ctx->page.size = 0; 295 ctx->page.size = 0;
210 ctx->page.modified = time(NULL); 296 ctx->page.modified = time(NULL);
211 ctx->page.expires = ctx->page.modified; 297 ctx->page.expires = ctx->page.modified;
298 ctx->page.etag = NULL;
299 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
300 if (ctx->env.script_name)
301 ctx->cfg.script_name = ctx->env.script_name;
302 if (ctx->env.query_string)
303 ctx->qry.raw = ctx->env.query_string;
304 if (!ctx->env.cgit_config)
305 ctx->env.cgit_config = CGIT_CONFIG;
212} 306}
213 307
214struct refmatch { 308struct refmatch {
215 char *req_ref; 309 char *req_ref;
216 char *first_ref; 310 char *first_ref;
217 int match; 311 int match;
218}; 312};
219 313
220int find_current_ref(const char *refname, const unsigned char *sha1, 314int find_current_ref(const char *refname, const unsigned char *sha1,
221 int flags, void *cb_data) 315 int flags, void *cb_data)
222{ 316{
223 struct refmatch *info; 317 struct refmatch *info;
224 318
225 info = (struct refmatch *)cb_data; 319 info = (struct refmatch *)cb_data;
226 if (!strcmp(refname, info->req_ref)) 320 if (!strcmp(refname, info->req_ref))
227 info->match = 1; 321 info->match = 1;
228 if (!info->first_ref) 322 if (!info->first_ref)
229 info->first_ref = xstrdup(refname); 323 info->first_ref = xstrdup(refname);
230 return info->match; 324 return info->match;
231} 325}
232 326
233char *find_default_branch(struct cgit_repo *repo) 327char *find_default_branch(struct cgit_repo *repo)
234{ 328{
235 struct refmatch info; 329 struct refmatch info;
@@ -267,48 +361,50 @@ static int prepare_repo_cmd(struct cgit_context *ctx)
267 cgit_print_error(tmp); 361 cgit_print_error(tmp);
268 cgit_print_docend(); 362 cgit_print_docend();
269 return 1; 363 return 1;
270 } 364 }
271 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 365 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
272 366
273 if (!ctx->qry.head) { 367 if (!ctx->qry.head) {
274 ctx->qry.nohead = 1; 368 ctx->qry.nohead = 1;
275 ctx->qry.head = find_default_branch(ctx->repo); 369 ctx->qry.head = find_default_branch(ctx->repo);
276 ctx->repo->defbranch = ctx->qry.head; 370 ctx->repo->defbranch = ctx->qry.head;
277 } 371 }
278 372
279 if (!ctx->qry.head) { 373 if (!ctx->qry.head) {
280 cgit_print_http_headers(ctx); 374 cgit_print_http_headers(ctx);
281 cgit_print_docstart(ctx); 375 cgit_print_docstart(ctx);
282 cgit_print_pageheader(ctx); 376 cgit_print_pageheader(ctx);
283 cgit_print_error("Repository seems to be empty"); 377 cgit_print_error("Repository seems to be empty");
284 cgit_print_docend(); 378 cgit_print_docend();
285 return 1; 379 return 1;
286 } 380 }
287 381
288 if (get_sha1(ctx->qry.head, sha1)) { 382 if (get_sha1(ctx->qry.head, sha1)) {
289 tmp = xstrdup(ctx->qry.head); 383 tmp = xstrdup(ctx->qry.head);
290 ctx->qry.head = ctx->repo->defbranch; 384 ctx->qry.head = ctx->repo->defbranch;
385 ctx->page.status = 404;
386 ctx->page.statusmsg = "not found";
291 cgit_print_http_headers(ctx); 387 cgit_print_http_headers(ctx);
292 cgit_print_docstart(ctx); 388 cgit_print_docstart(ctx);
293 cgit_print_pageheader(ctx); 389 cgit_print_pageheader(ctx);
294 cgit_print_error(fmt("Invalid branch: %s", tmp)); 390 cgit_print_error(fmt("Invalid branch: %s", tmp));
295 cgit_print_docend(); 391 cgit_print_docend();
296 return 1; 392 return 1;
297 } 393 }
298 return 0; 394 return 0;
299} 395}
300 396
301static void process_request(void *cbdata) 397static void process_request(void *cbdata)
302{ 398{
303 struct cgit_context *ctx = cbdata; 399 struct cgit_context *ctx = cbdata;
304 struct cgit_cmd *cmd; 400 struct cgit_cmd *cmd;
305 401
306 cmd = cgit_get_cmd(ctx); 402 cmd = cgit_get_cmd(ctx);
307 if (!cmd) { 403 if (!cmd) {
308 ctx->page.title = "cgit error"; 404 ctx->page.title = "cgit error";
309 cgit_print_http_headers(ctx); 405 cgit_print_http_headers(ctx);
310 cgit_print_docstart(ctx); 406 cgit_print_docstart(ctx);
311 cgit_print_pageheader(ctx); 407 cgit_print_pageheader(ctx);
312 cgit_print_error("Invalid request"); 408 cgit_print_error("Invalid request");
313 cgit_print_docend(); 409 cgit_print_docend();
314 return; 410 return;
@@ -323,167 +419,300 @@ static void process_request(void *cbdata)
323 return; 419 return;
324 } 420 }
325 421
326 if (ctx->repo && prepare_repo_cmd(ctx)) 422 if (ctx->repo && prepare_repo_cmd(ctx))
327 return; 423 return;
328 424
329 if (cmd->want_layout) { 425 if (cmd->want_layout) {
330 cgit_print_http_headers(ctx); 426 cgit_print_http_headers(ctx);
331 cgit_print_docstart(ctx); 427 cgit_print_docstart(ctx);
332 cgit_print_pageheader(ctx); 428 cgit_print_pageheader(ctx);
333 } 429 }
334 430
335 cmd->fn(ctx); 431 cmd->fn(ctx);
336 432
337 if (cmd->want_layout) 433 if (cmd->want_layout)
338 cgit_print_docend(); 434 cgit_print_docend();
339} 435}
340 436
341int cmp_repos(const void *a, const void *b) 437int cmp_repos(const void *a, const void *b)
342{ 438{
343 const struct cgit_repo *ra = a, *rb = b; 439 const struct cgit_repo *ra = a, *rb = b;
344 return strcmp(ra->url, rb->url); 440 return strcmp(ra->url, rb->url);
345} 441}
346 442
347void print_repo(struct cgit_repo *repo) 443char *build_snapshot_setting(int bitmap)
348{ 444{
349 printf("repo.url=%s\n", repo->url); 445 const struct cgit_snapshot_format *f;
350 printf("repo.name=%s\n", repo->name); 446 char *result = xstrdup("");
351 printf("repo.path=%s\n", repo->path); 447 char *tmp;
448 int len;
449
450 for (f = cgit_snapshot_formats; f->suffix; f++) {
451 if (f->bit & bitmap) {
452 tmp = result;
453 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
454 free(tmp);
455 }
456 }
457 len = strlen(result);
458 if (len)
459 result[len - 1] = '\0';
460 return result;
461}
462
463char *get_first_line(char *txt)
464{
465 char *t = xstrdup(txt);
466 char *p = strchr(t, '\n');
467 if (p)
468 *p = '\0';
469 return t;
470}
471
472void print_repo(FILE *f, struct cgit_repo *repo)
473{
474 fprintf(f, "repo.url=%s\n", repo->url);
475 fprintf(f, "repo.name=%s\n", repo->name);
476 fprintf(f, "repo.path=%s\n", repo->path);
352 if (repo->owner) 477 if (repo->owner)
353 printf("repo.owner=%s\n", repo->owner); 478 fprintf(f, "repo.owner=%s\n", repo->owner);
354 if (repo->desc) 479 if (repo->desc) {
355 printf("repo.desc=%s\n", repo->desc); 480 char *tmp = get_first_line(repo->desc);
481 fprintf(f, "repo.desc=%s\n", tmp);
482 free(tmp);
483 }
356 if (repo->readme) 484 if (repo->readme)
357 printf("repo.readme=%s\n", repo->readme); 485 fprintf(f, "repo.readme=%s\n", repo->readme);
358 printf("\n"); 486 if (repo->defbranch)
487 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
488 if (repo->module_link)
489 fprintf(f, "repo.module-link=%s\n", repo->module_link);
490 if (repo->section)
491 fprintf(f, "repo.section=%s\n", repo->section);
492 if (repo->clone_url)
493 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
494 fprintf(f, "repo.enable-log-filecount=%d\n",
495 repo->enable_log_filecount);
496 fprintf(f, "repo.enable-log-linecount=%d\n",
497 repo->enable_log_linecount);
498 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
499 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
500 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
501 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
502 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
503 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
504 if (repo->snapshots != ctx.cfg.snapshots) {
505 char *tmp = build_snapshot_setting(repo->snapshots);
506 fprintf(f, "repo.snapshots=%s\n", tmp);
507 free(tmp);
508 }
509 if (repo->max_stats != ctx.cfg.max_stats)
510 fprintf(f, "repo.max-stats=%s\n",
511 cgit_find_stats_periodname(repo->max_stats));
512 fprintf(f, "\n");
359} 513}
360 514
361void print_repolist(struct cgit_repolist *list) 515void print_repolist(FILE *f, struct cgit_repolist *list, int start)
362{ 516{
363 int i; 517 int i;
364 518
365 for(i = 0; i < list->count; i++) 519 for(i = start; i < list->count; i++)
366 print_repo(&list->repos[i]); 520 print_repo(f, &list->repos[i]);
521}
522
523/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
524 * and return 0 on success.
525 */
526static int generate_cached_repolist(const char *path, const char *cached_rc)
527{
528 char *locked_rc;
529 int idx;
530 FILE *f;
531
532 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
533 f = fopen(locked_rc, "wx");
534 if (!f) {
535 /* Inform about the error unless the lockfile already existed,
536 * since that only means we've got concurrent requests.
537 */
538 if (errno != EEXIST)
539 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
540 locked_rc, strerror(errno), errno);
541 return errno;
542 }
543 idx = cgit_repolist.count;
544 scan_tree(path, repo_config);
545 print_repolist(f, &cgit_repolist, idx);
546 if (rename(locked_rc, cached_rc))
547 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
548 locked_rc, cached_rc, strerror(errno), errno);
549 fclose(f);
550 return 0;
367} 551}
368 552
553static void process_cached_repolist(const char *path)
554{
555 struct stat st;
556 char *cached_rc;
557 time_t age;
558
559 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root,
560 hash_str(path)));
561
562 if (stat(cached_rc, &st)) {
563 /* Nothing is cached, we need to scan without forking. And
564 * if we fail to generate a cached repolist, we need to
565 * invoke scan_tree manually.
566 */
567 if (generate_cached_repolist(path, cached_rc))
568 scan_tree(path, repo_config);
569 return;
570 }
571
572 parse_configfile(cached_rc, config_cb);
573
574 /* If the cached configfile hasn't expired, lets exit now */
575 age = time(NULL) - st.st_mtime;
576 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
577 return;
578
579 /* The cached repolist has been parsed, but it was old. So lets
580 * rescan the specified path and generate a new cached repolist
581 * in a child-process to avoid latency for the current request.
582 */
583 if (fork())
584 return;
585
586 exit(generate_cached_repolist(path, cached_rc));
587}
369 588
370static void cgit_parse_args(int argc, const char **argv) 589static void cgit_parse_args(int argc, const char **argv)
371{ 590{
372 int i; 591 int i;
373 int scan = 0; 592 int scan = 0;
374 593
375 for (i = 1; i < argc; i++) { 594 for (i = 1; i < argc; i++) {
376 if (!strncmp(argv[i], "--cache=", 8)) { 595 if (!strncmp(argv[i], "--cache=", 8)) {
377 ctx.cfg.cache_root = xstrdup(argv[i]+8); 596 ctx.cfg.cache_root = xstrdup(argv[i]+8);
378 } 597 }
379 if (!strcmp(argv[i], "--nocache")) { 598 if (!strcmp(argv[i], "--nocache")) {
380 ctx.cfg.nocache = 1; 599 ctx.cfg.nocache = 1;
381 } 600 }
601 if (!strcmp(argv[i], "--nohttp")) {
602 ctx.env.no_http = "1";
603 }
382 if (!strncmp(argv[i], "--query=", 8)) { 604 if (!strncmp(argv[i], "--query=", 8)) {
383 ctx.qry.raw = xstrdup(argv[i]+8); 605 ctx.qry.raw = xstrdup(argv[i]+8);
384 } 606 }
385 if (!strncmp(argv[i], "--repo=", 7)) { 607 if (!strncmp(argv[i], "--repo=", 7)) {
386 ctx.qry.repo = xstrdup(argv[i]+7); 608 ctx.qry.repo = xstrdup(argv[i]+7);
387 } 609 }
388 if (!strncmp(argv[i], "--page=", 7)) { 610 if (!strncmp(argv[i], "--page=", 7)) {
389 ctx.qry.page = xstrdup(argv[i]+7); 611 ctx.qry.page = xstrdup(argv[i]+7);
390 } 612 }
391 if (!strncmp(argv[i], "--head=", 7)) { 613 if (!strncmp(argv[i], "--head=", 7)) {
392 ctx.qry.head = xstrdup(argv[i]+7); 614 ctx.qry.head = xstrdup(argv[i]+7);
393 ctx.qry.has_symref = 1; 615 ctx.qry.has_symref = 1;
394 } 616 }
395 if (!strncmp(argv[i], "--sha1=", 7)) { 617 if (!strncmp(argv[i], "--sha1=", 7)) {
396 ctx.qry.sha1 = xstrdup(argv[i]+7); 618 ctx.qry.sha1 = xstrdup(argv[i]+7);
397 ctx.qry.has_sha1 = 1; 619 ctx.qry.has_sha1 = 1;
398 } 620 }
399 if (!strncmp(argv[i], "--ofs=", 6)) { 621 if (!strncmp(argv[i], "--ofs=", 6)) {
400 ctx.qry.ofs = atoi(argv[i]+6); 622 ctx.qry.ofs = atoi(argv[i]+6);
401 } 623 }
402 if (!strncmp(argv[i], "--scan-tree=", 12)) { 624 if (!strncmp(argv[i], "--scan-tree=", 12) ||
625 !strncmp(argv[i], "--scan-path=", 12)) {
626 /* HACK: the global snapshot bitmask defines the
627 * set of allowed snapshot formats, but the config
628 * file hasn't been parsed yet so the mask is
629 * currently 0. By setting all bits high before
630 * scanning we make sure that any in-repo cgitrc
631 * snapshot setting is respected by scan_tree().
632 * BTW: we assume that there'll never be more than
633 * 255 different snapshot formats supported by cgit...
634 */
635 ctx.cfg.snapshots = 0xFF;
403 scan++; 636 scan++;
404 scan_tree(argv[i] + 12); 637 scan_tree(argv[i] + 12, repo_config);
405 } 638 }
406 } 639 }
407 if (scan) { 640 if (scan) {
408 qsort(cgit_repolist.repos, cgit_repolist.count, 641 qsort(cgit_repolist.repos, cgit_repolist.count,
409 sizeof(struct cgit_repo), cmp_repos); 642 sizeof(struct cgit_repo), cmp_repos);
410 print_repolist(&cgit_repolist); 643 print_repolist(stdout, &cgit_repolist, 0);
411 exit(0); 644 exit(0);
412 } 645 }
413} 646}
414 647
415static int calc_ttl() 648static int calc_ttl()
416{ 649{
417 if (!ctx.repo) 650 if (!ctx.repo)
418 return ctx.cfg.cache_root_ttl; 651 return ctx.cfg.cache_root_ttl;
419 652
420 if (!ctx.qry.page) 653 if (!ctx.qry.page)
421 return ctx.cfg.cache_repo_ttl; 654 return ctx.cfg.cache_repo_ttl;
422 655
423 if (ctx.qry.has_symref) 656 if (ctx.qry.has_symref)
424 return ctx.cfg.cache_dynamic_ttl; 657 return ctx.cfg.cache_dynamic_ttl;
425 658
426 if (ctx.qry.has_sha1) 659 if (ctx.qry.has_sha1)
427 return ctx.cfg.cache_static_ttl; 660 return ctx.cfg.cache_static_ttl;
428 661
429 return ctx.cfg.cache_repo_ttl; 662 return ctx.cfg.cache_repo_ttl;
430} 663}
431 664
432int main(int argc, const char **argv) 665int main(int argc, const char **argv)
433{ 666{
434 const char *cgit_config_env = getenv("CGIT_CONFIG");
435 const char *path; 667 const char *path;
436 char *qry; 668 char *qry;
437 int err, ttl; 669 int err, ttl;
438 670
439 prepare_context(&ctx); 671 prepare_context(&ctx);
440 cgit_repolist.length = 0; 672 cgit_repolist.length = 0;
441 cgit_repolist.count = 0; 673 cgit_repolist.count = 0;
442 cgit_repolist.repos = NULL; 674 cgit_repolist.repos = NULL;
443 675
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); 676 cgit_parse_args(argc, argv);
449 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG, 677 parse_configfile(ctx.env.cgit_config, config_cb);
450 config_cb);
451 ctx.repo = NULL; 678 ctx.repo = NULL;
452 http_parse_querystring(ctx.qry.raw, querystring_cb); 679 http_parse_querystring(ctx.qry.raw, querystring_cb);
453 680
454 /* If virtual-root isn't specified in cgitrc, lets pretend 681 /* If virtual-root isn't specified in cgitrc, lets pretend
455 * that virtual-root equals SCRIPT_NAME. 682 * that virtual-root equals SCRIPT_NAME.
456 */ 683 */
457 if (!ctx.cfg.virtual_root) 684 if (!ctx.cfg.virtual_root)
458 ctx.cfg.virtual_root = ctx.cfg.script_name; 685 ctx.cfg.virtual_root = ctx.cfg.script_name;
459 686
460 /* If no url parameter is specified on the querystring, lets 687 /* If no url parameter is specified on the querystring, lets
461 * use PATH_INFO as url. This allows cgit to work with virtual 688 * use PATH_INFO as url. This allows cgit to work with virtual
462 * urls without the need for rewriterules in the webserver (as 689 * urls without the need for rewriterules in the webserver (as
463 * long as PATH_INFO is included in the cache lookup key). 690 * long as PATH_INFO is included in the cache lookup key).
464 */ 691 */
465 path = getenv("PATH_INFO"); 692 path = ctx.env.path_info;
466 if (!ctx.qry.url && path) { 693 if (!ctx.qry.url && path) {
467 if (path[0] == '/') 694 if (path[0] == '/')
468 path++; 695 path++;
469 ctx.qry.url = xstrdup(path); 696 ctx.qry.url = xstrdup(path);
470 if (ctx.qry.raw) { 697 if (ctx.qry.raw) {
471 qry = ctx.qry.raw; 698 qry = ctx.qry.raw;
472 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 699 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
473 free(qry); 700 free(qry);
474 } else 701 } else
475 ctx.qry.raw = ctx.qry.url; 702 ctx.qry.raw = xstrdup(ctx.qry.url);
476 cgit_parse_url(ctx.qry.url); 703 cgit_parse_url(ctx.qry.url);
477 } 704 }
478 705
479 ttl = calc_ttl(); 706 ttl = calc_ttl();
480 ctx.page.expires += ttl*60; 707 ctx.page.expires += ttl*60;
708 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
709 ctx.cfg.nocache = 1;
481 if (ctx.cfg.nocache) 710 if (ctx.cfg.nocache)
482 ctx.cfg.cache_size = 0; 711 ctx.cfg.cache_size = 0;
483 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 712 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
484 ctx.qry.raw, ttl, process_request, &ctx); 713 ctx.qry.raw, ttl, process_request, &ctx);
485 if (err) 714 if (err)
486 cgit_print_error(fmt("Error processing page: %s (%d)", 715 cgit_print_error(fmt("Error processing page: %s (%d)",
487 strerror(err), err)); 716 strerror(err), err));
488 return err; 717 return err;
489} 718}
diff --git a/cgit.css b/cgit.css
index adfc8ae..c47ebc9 100644
--- a/cgit.css
+++ b/cgit.css
@@ -134,49 +134,49 @@ table.list tr.nohover:hover {
134 134
135table.list th { 135table.list th {
136 font-weight: bold; 136 font-weight: bold;
137 /* color: #888; 137 /* color: #888;
138 border-top: dashed 1px #888; 138 border-top: dashed 1px #888;
139 border-bottom: dashed 1px #888; 139 border-bottom: dashed 1px #888;
140 */ 140 */
141 padding: 0.1em 0.5em 0.05em 0.5em; 141 padding: 0.1em 0.5em 0.05em 0.5em;
142 vertical-align: baseline; 142 vertical-align: baseline;
143} 143}
144 144
145table.list td { 145table.list td {
146 border: none; 146 border: none;
147 padding: 0.1em 0.5em 0.1em 0.5em; 147 padding: 0.1em 0.5em 0.1em 0.5em;
148} 148}
149 149
150table.list td.logsubject { 150table.list td.logsubject {
151 font-family: monospace; 151 font-family: monospace;
152 font-weight: bold; 152 font-weight: bold;
153} 153}
154 154
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 {
162 color: black; 162 color: black;
163} 163}
164 164
165table.list td a:hover { 165table.list td a:hover {
166 color: #00f; 166 color: #00f;
167} 167}
168 168
169img { 169img {
170 border: none; 170 border: none;
171} 171}
172 172
173input#switch-btn { 173input#switch-btn {
174 margin: 2px 0px 0px 0px; 174 margin: 2px 0px 0px 0px;
175} 175}
176 176
177td#sidebar input.txt { 177td#sidebar input.txt {
178 width: 100%; 178 width: 100%;
179 margin: 2px 0px 0px 0px; 179 margin: 2px 0px 0px 0px;
180} 180}
181 181
182table#grid { 182table#grid {
@@ -216,58 +216,58 @@ div.error {
216 margin: 1em 2em; 216 margin: 1em 2em;
217} 217}
218 218
219a.ls-blob, a.ls-dir, a.ls-mod { 219a.ls-blob, a.ls-dir, a.ls-mod {
220 font-family: monospace; 220 font-family: monospace;
221} 221}
222 222
223td.ls-size { 223td.ls-size {
224 text-align: right; 224 text-align: right;
225 font-family: monospace; 225 font-family: monospace;
226 width: 10em; 226 width: 10em;
227} 227}
228 228
229td.ls-mode { 229td.ls-mode {
230 font-family: monospace; 230 font-family: monospace;
231 width: 10em; 231 width: 10em;
232} 232}
233 233
234table.blob { 234table.blob {
235 margin-top: 0.5em; 235 margin-top: 0.5em;
236 border-top: solid 1px black; 236 border-top: solid 1px black;
237} 237}
238 238
239table.blob td.lines { 239table.blob td.lines {
240 margin: 0; padding: 0; 240 margin: 0; padding: 0 0 0 0.5em;
241 vertical-align: top; 241 vertical-align: top;
242 color: black; 242 color: black;
243} 243}
244 244
245table.blob td.linenumbers { 245table.blob td.linenumbers {
246 margin: 0; padding: 0; 246 margin: 0; padding: 0 0.5em 0 0.5em;
247 vertical-align: top; 247 vertical-align: top;
248 text-align: right;
248 border-right: 1px solid gray; 249 border-right: 1px solid gray;
249 background-color: #eee;
250} 250}
251 251
252table.blob pre { 252table.blob pre {
253 padding: 0; margin: 0; 253 padding: 0; margin: 0;
254} 254}
255 255
256table.blob a.no { 256table.blob a.no {
257 color: gray; 257 color: gray;
258 text-align: right; 258 text-align: right;
259 text-decoration: none; 259 text-decoration: none;
260} 260}
261 261
262table.blob a.no a:hover { 262table.blob a.no a:hover {
263 color: black; 263 color: black;
264} 264}
265 265
266table.bin-blob { 266table.bin-blob {
267 margin-top: 0.5em; 267 margin-top: 0.5em;
268 border: solid 1px black; 268 border: solid 1px black;
269} 269}
270 270
271table.bin-blob th { 271table.bin-blob th {
272 font-family: monospace; 272 font-family: monospace;
273 white-space: pre; 273 white-space: pre;
@@ -408,49 +408,49 @@ table.diff td div.hunk {
408 color: #009; 408 color: #009;
409} 409}
410 410
411table.diff td div.add { 411table.diff td div.add {
412 color: green; 412 color: green;
413} 413}
414 414
415table.diff td div.del { 415table.diff td div.del {
416 color: red; 416 color: red;
417} 417}
418 418
419.sha1 { 419.sha1 {
420 font-family: monospace; 420 font-family: monospace;
421 font-size: 90%; 421 font-size: 90%;
422} 422}
423 423
424.left { 424.left {
425 text-align: left; 425 text-align: left;
426} 426}
427 427
428.right { 428.right {
429 text-align: right; 429 text-align: right;
430} 430}
431 431
432table.list td.repogroup { 432table.list td.reposection {
433 font-style: italic; 433 font-style: italic;
434 color: #888; 434 color: #888;
435} 435}
436 436
437a.button { 437a.button {
438 font-size: 80%; 438 font-size: 80%;
439 padding: 0em 0.5em; 439 padding: 0em 0.5em;
440} 440}
441 441
442a.primary { 442a.primary {
443 font-size: 100%; 443 font-size: 100%;
444} 444}
445 445
446a.secondary { 446a.secondary {
447 font-size: 90%; 447 font-size: 90%;
448} 448}
449 449
450td.toplevel-repo { 450td.toplevel-repo {
451 451
452} 452}
453 453
454table.list td.sublevel-repo { 454table.list td.sublevel-repo {
455 padding-left: 1.5em; 455 padding-left: 1.5em;
456} 456}
diff --git a/cgit.h b/cgit.h
index 5f7af51..6c6c460 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,92 +1,108 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h>
18#include <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>
21 22
22 23
23/* 24/*
24 * Dateformats used on misc. pages 25 * Dateformats used on misc. pages
25 */ 26 */
26#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
27#define FMT_SHORTDATE "%Y-%m-%d" 28#define FMT_SHORTDATE "%Y-%m-%d"
28#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
29 30
30 31
31/* 32/*
32 * Limits used for relative dates 33 * Limits used for relative dates
33 */ 34 */
34#define TM_MIN 60 35#define TM_MIN 60
35#define TM_HOUR (TM_MIN * 60) 36#define TM_HOUR (TM_MIN * 60)
36#define TM_DAY (TM_HOUR * 24) 37#define TM_DAY (TM_HOUR * 24)
37#define TM_WEEK (TM_DAY * 7) 38#define TM_WEEK (TM_DAY * 7)
38#define TM_YEAR (TM_DAY * 365) 39#define TM_YEAR (TM_DAY * 365)
39#define TM_MONTH (TM_YEAR / 12.0) 40#define TM_MONTH (TM_YEAR / 12.0)
40 41
41 42
42/* 43/*
43 * Default encoding 44 * Default encoding
44 */ 45 */
45#define PAGE_ENCODING "UTF-8" 46#define PAGE_ENCODING "UTF-8"
46 47
47typedef void (*configfn)(const char *name, const char *value); 48typedef 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;
54 char *path; 64 char *path;
55 char *desc; 65 char *desc;
56 char *owner; 66 char *owner;
57 char *defbranch; 67 char *defbranch;
58 char *group;
59 char *module_link; 68 char *module_link;
60 char *readme; 69 char *readme;
70 char *section;
61 char *clone_url; 71 char *clone_url;
62 int snapshots; 72 int snapshots;
63 int enable_log_filecount; 73 int enable_log_filecount;
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
82typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
83 const char *value);
84
69struct cgit_repolist { 85struct cgit_repolist {
70 int length; 86 int length;
71 int count; 87 int count;
72 struct cgit_repo *repos; 88 struct cgit_repo *repos;
73}; 89};
74 90
75struct commitinfo { 91struct commitinfo {
76 struct commit *commit; 92 struct commit *commit;
77 char *author; 93 char *author;
78 char *author_email; 94 char *author_email;
79 unsigned long author_date; 95 unsigned long author_date;
80 char *committer; 96 char *committer;
81 char *committer_email; 97 char *committer_email;
82 unsigned long committer_date; 98 unsigned long committer_date;
83 char *subject; 99 char *subject;
84 char *msg; 100 char *msg;
85 char *msg_encoding; 101 char *msg_encoding;
86}; 102};
87 103
88struct taginfo { 104struct taginfo {
89 char *tagger; 105 char *tagger;
90 char *tagger_email; 106 char *tagger_email;
91 unsigned long tagger_date; 107 unsigned long tagger_date;
92 char *msg; 108 char *msg;
@@ -115,96 +131,124 @@ struct cgit_query {
115 char *page; 131 char *page;
116 char *search; 132 char *search;
117 char *grep; 133 char *grep;
118 char *head; 134 char *head;
119 char *sha1; 135 char *sha1;
120 char *sha2; 136 char *sha2;
121 char *path; 137 char *path;
122 char *name; 138 char *name;
123 char *mimetype; 139 char *mimetype;
124 char *url; 140 char *url;
125 char *period; 141 char *period;
126 int ofs; 142 int ofs;
127 int nohead; 143 int nohead;
128 char *sort; 144 char *sort;
129 int showmsg; 145 int showmsg;
130}; 146};
131 147
132struct cgit_config { 148struct cgit_config {
133 char *agefile; 149 char *agefile;
134 char *cache_root; 150 char *cache_root;
135 char *clone_prefix; 151 char *clone_prefix;
136 char *css; 152 char *css;
137 char *favicon; 153 char *favicon;
138 char *footer; 154 char *footer;
155 char *head_include;
139 char *header; 156 char *header;
140 char *index_header; 157 char *index_header;
141 char *index_info; 158 char *index_info;
142 char *logo; 159 char *logo;
143 char *logo_link; 160 char *logo_link;
144 char *module_link; 161 char *module_link;
145 char *repo_group;
146 char *robots; 162 char *robots;
147 char *root_title; 163 char *root_title;
148 char *root_desc; 164 char *root_desc;
149 char *root_readme; 165 char *root_readme;
150 char *script_name; 166 char *script_name;
167 char *section;
151 char *virtual_root; 168 char *virtual_root;
152 int cache_size; 169 int cache_size;
153 int cache_dynamic_ttl; 170 int cache_dynamic_ttl;
154 int cache_max_create_time; 171 int cache_max_create_time;
155 int cache_repo_ttl; 172 int cache_repo_ttl;
156 int cache_root_ttl; 173 int cache_root_ttl;
174 int cache_scanrc_ttl;
157 int cache_static_ttl; 175 int cache_static_ttl;
176 int embedded;
177 int enable_filter_overrides;
158 int enable_index_links; 178 int enable_index_links;
159 int enable_log_filecount; 179 int enable_log_filecount;
160 int enable_log_linecount; 180 int enable_log_linecount;
181 int enable_tree_linenumbers;
161 int local_time; 182 int local_time;
162 int max_repo_count; 183 int max_repo_count;
163 int max_commit_count; 184 int max_commit_count;
164 int max_lock_attempts; 185 int max_lock_attempts;
165 int max_msg_len; 186 int max_msg_len;
166 int max_repodesc_len; 187 int max_repodesc_len;
167 int max_stats; 188 int max_stats;
168 int nocache; 189 int nocache;
190 int noplainemail;
191 int noheader;
169 int renamelimit; 192 int renamelimit;
170 int snapshots; 193 int snapshots;
171 int summary_branches; 194 int summary_branches;
172 int summary_log; 195 int summary_log;
173 int summary_tags; 196 int summary_tags;
197 struct string_list mimetypes;
198 struct cgit_filter *about_filter;
199 struct cgit_filter *commit_filter;
200 struct cgit_filter *source_filter;
174}; 201};
175 202
176struct cgit_page { 203struct cgit_page {
177 time_t modified; 204 time_t modified;
178 time_t expires; 205 time_t expires;
179 size_t size; 206 size_t size;
180 char *mimetype; 207 char *mimetype;
181 char *charset; 208 char *charset;
182 char *filename; 209 char *filename;
210 char *etag;
183 char *title; 211 char *title;
212 int status;
213 char *statusmsg;
214};
215
216struct cgit_environment {
217 char *cgit_config;
218 char *http_host;
219 char *https;
220 char *no_http;
221 char *path_info;
222 char *query_string;
223 char *request_method;
224 char *script_name;
225 char *server_name;
226 char *server_port;
184}; 227};
185 228
186struct cgit_context { 229struct cgit_context {
230 struct cgit_environment env;
187 struct cgit_query qry; 231 struct cgit_query qry;
188 struct cgit_config cfg; 232 struct cgit_config cfg;
189 struct cgit_repo *repo; 233 struct cgit_repo *repo;
190 struct cgit_page page; 234 struct cgit_page page;
191}; 235};
192 236
193struct cgit_snapshot_format { 237struct cgit_snapshot_format {
194 const char *suffix; 238 const char *suffix;
195 const char *mimetype; 239 const char *mimetype;
196 write_archive_fn_t write_func; 240 write_archive_fn_t write_func;
197 int bit; 241 int bit;
198}; 242};
199 243
200extern const char *cgit_version; 244extern const char *cgit_version;
201 245
202extern struct cgit_repolist cgit_repolist; 246extern struct cgit_repolist cgit_repolist;
203extern struct cgit_context ctx; 247extern struct cgit_context ctx;
204extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 248extern const struct cgit_snapshot_format cgit_snapshot_formats[];
205 249
206extern struct cgit_repo *cgit_add_repo(const char *url); 250extern struct cgit_repo *cgit_add_repo(const char *url);
207extern struct cgit_repo *cgit_get_repoinfo(const char *url); 251extern struct cgit_repo *cgit_get_repoinfo(const char *url);
208extern void cgit_repo_config_cb(const char *name, const char *value); 252extern void cgit_repo_config_cb(const char *name, const char *value);
209 253
210extern int chk_zero(int result, char *msg); 254extern int chk_zero(int result, char *msg);
@@ -221,26 +265,30 @@ extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
221 265
222extern void *cgit_free_commitinfo(struct commitinfo *info); 266extern void *cgit_free_commitinfo(struct commitinfo *info);
223 267
224extern int cgit_diff_files(const unsigned char *old_sha1, 268extern int cgit_diff_files(const unsigned char *old_sha1,
225 const unsigned char *new_sha1, 269 const unsigned char *new_sha1,
226 unsigned long *old_size, unsigned long *new_size, 270 unsigned long *old_size, unsigned long *new_size,
227 int *binary, linediff_fn fn); 271 int *binary, linediff_fn fn);
228 272
229extern void cgit_diff_tree(const unsigned char *old_sha1, 273extern void cgit_diff_tree(const unsigned char *old_sha1,
230 const unsigned char *new_sha1, 274 const unsigned char *new_sha1,
231 filepair_fn fn, const char *prefix); 275 filepair_fn fn, const char *prefix);
232 276
233extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 277extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
234 278
235extern char *fmt(const char *format,...); 279extern char *fmt(const char *format,...);
236 280
237extern struct commitinfo *cgit_parse_commit(struct commit *commit); 281extern struct commitinfo *cgit_parse_commit(struct commit *commit);
238extern struct taginfo *cgit_parse_tag(struct tag *tag); 282extern struct taginfo *cgit_parse_tag(struct tag *tag);
239extern void cgit_parse_url(const char *url); 283extern void cgit_parse_url(const char *url);
240 284
241extern const char *cgit_repobasename(const char *reponame); 285extern const char *cgit_repobasename(const char *reponame);
242 286
243extern int cgit_parse_snapshots_mask(const char *str); 287extern int cgit_parse_snapshots_mask(const char *str);
244 288
289extern int cgit_open_filter(struct cgit_filter *filter);
290extern int cgit_close_filter(struct cgit_filter *filter);
291
292extern int readfile(const char *path, char **buf, size_t *size);
245 293
246#endif /* CGIT_H */ 294#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index fd299ae..4dc383d 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,285 +1,381 @@
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-scanrc-ttl::
58 Number which specifies the time-to-live, in minutes, for the result
59 of scanning a path for git repositories. Default value: "15".
60
61cache-size::
44 The maximum number of entries in the cgit cache. Default value: "0" 62 The maximum number of entries in the cgit cache. Default value: "0"
45 (i.e. caching is disabled). 63 (i.e. caching is disabled).
46 64
47cache-static-ttl 65cache-static-ttl::
48 Number which specifies the time-to-live, in minutes, for the cached 66 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed with a fixed SHA1. Default value: 67 version of repository pages accessed with a fixed SHA1. Default value:
50 "5". 68 "5".
51 69
52clone-prefix 70clone-prefix::
53 Space-separated list of common prefixes which, when combined with a 71 Space-separated list of common prefixes which, when combined with a
54 repository url, generates valid clone urls for the repository. This 72 repository url, generates valid clone urls for the repository. This
55 setting is only used if `repo.clone-url` is unspecified. Default value: 73 setting is only used if `repo.clone-url` is unspecified. Default value:
56 none. 74 none.
57 75
58css 76commit-filter::
77 Specifies a command which will be invoked to format commit messages.
78 The command will get the message on its STDIN, and the STDOUT from the
79 command will be included verbatim as the commit message, i.e. this can
80 be used to implement bugtracker integration. Default value: none.
81
82css::
59 Url which specifies the css document to include in all cgit pages. 83 Url which specifies the css document to include in all cgit pages.
60 Default value: "/cgit.css". 84 Default value: "/cgit.css".
61 85
62enable-index-links 86embedded::
87 Flag which, when set to "1", will make cgit generate a html fragment
88 suitable for embedding in other html pages. Default value: none. See
89 also: "noheader".
90
91enable-filter-overrides::
92 Flag which, when set to "1", allows all filter settings to be
93 overridden in repository-specific cgitrc files. Default value: none.
94
95enable-index-links::
63 Flag which, when set to "1", will make cgit generate extra links for 96 Flag which, when set to "1", will make cgit generate extra links for
64 each repo in the repository index (specifically, to the "summary", 97 each repo in the repository index (specifically, to the "summary",
65 "commit" and "tree" pages). Default value: "0". 98 "commit" and "tree" pages). Default value: "0".
66 99
67enable-log-filecount 100enable-log-filecount::
68 Flag which, when set to "1", will make cgit print the number of 101 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 102 modified files for each commit on the repository log page. Default
70 value: "0". 103 value: "0".
71 104
72enable-log-linecount 105enable-log-linecount::
73 Flag which, when set to "1", will make cgit print the number of added 106 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 107 and removed lines for each commit on the repository log page. Default
75 value: "0". 108 value: "0".
76 109
77favicon 110enable-tree-linenumbers::
111 Flag which, when set to "1", will make cgit generate linenumber links
112 for plaintext blobs printed in the tree view. Default value: "1".
113
114favicon::
78 Url used as link to a shortcut icon for cgit. If specified, it is 115 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 116 suggested to use the value "/favicon.ico" since certain browsers will
80 ignore other values. Default value: none. 117 ignore other values. Default value: none.
81 118
82footer 119footer::
83 The content of the file specified with this option will be included 120 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 121 verbatim at the bottom of all pages (i.e. it replaces the standard
85 "generated by..." message. Default value: none. 122 "generated by..." message. Default value: none.
86 123
87header 124head-include::
125 The content of the file specified with this option will be included
126 verbatim in the html HEAD section on all pages. Default value: none.
127
128header::
88 The content of the file specified with this option will be included 129 The content of the file specified with this option will be included
89 verbatim at the top of all pages. Default value: none. 130 verbatim at the top of all pages. Default value: none.
90 131
91include 132include::
92 Name of a configfile to include before the rest of the current config- 133 Name of a configfile to include before the rest of the current config-
93 file is parsed. Default value: none. 134 file is parsed. Default value: none.
94 135
95index-header 136index-header::
96 The content of the file specified with this option will be included 137 The content of the file specified with this option will be included
97 verbatim above the repository index. This setting is deprecated, and 138 verbatim above the repository index. This setting is deprecated, and
98 will not be supported by cgit-1.0 (use root-readme instead). Default 139 will not be supported by cgit-1.0 (use root-readme instead). Default
99 value: none. 140 value: none.
100 141
101index-info 142index-info::
102 The content of the file specified with this option will be included 143 The content of the file specified with this option will be included
103 verbatim below the heading on the repository index page. This setting 144 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 145 is deprecated, and will not be supported by cgit-1.0 (use root-desc
105 instead). Default value: none. 146 instead). Default value: none.
106 147
107local-time 148local-time::
108 Flag which, if set to "1", makes cgit print commit and tag times in the 149 Flag which, if set to "1", makes cgit print commit and tag times in the
109 servers timezone. Default value: "0". 150 servers timezone. Default value: "0".
110 151
111logo 152logo::
112 Url which specifies the source of an image which will be used as a logo 153 Url which specifies the source of an image which will be used as a logo
113 on all cgit pages. 154 on all cgit pages. Default value: "/cgit.png".
114 155
115logo-link 156logo-link::
116 Url loaded when clicking on the cgit logo image. If unspecified the 157 Url loaded when clicking on the cgit logo image. If unspecified the
117 calculated url of the repository index page will be used. Default 158 calculated url of the repository index page will be used. Default
118 value: none. 159 value: none.
119 160
120max-commit-count 161max-commit-count::
121 Specifies the number of entries to list per page in "log" view. Default 162 Specifies the number of entries to list per page in "log" view. Default
122 value: "50". 163 value: "50".
123 164
124max-message-length 165max-message-length::
125 Specifies the maximum number of commit message characters to display in 166 Specifies the maximum number of commit message characters to display in
126 "log" view. Default value: "80". 167 "log" view. Default value: "80".
127 168
128max-repo-count 169max-repo-count::
129 Specifies the number of entries to list per page on therepository 170 Specifies the number of entries to list per page on therepository
130 index page. Default value: "50". 171 index page. Default value: "50".
131 172
132max-repodesc-length 173max-repodesc-length::
133 Specifies the maximum number of repo description characters to display 174 Specifies the maximum number of repo description characters to display
134 on the repository index page. Default value: "80". 175 on the repository index page. Default value: "80".
135 176
136max-stats 177max-stats::
137 Set the default maximum statistics period. Valid values are "week", 178 Set the default maximum statistics period. Valid values are "week",
138 "month", "quarter" and "year". If unspecified, statistics are 179 "month", "quarter" and "year". If unspecified, statistics are
139 disabled. Default value: none. See also: "repo.max-stats". 180 disabled. Default value: none. See also: "repo.max-stats".
140 181
141module-link 182mimetype.<ext>::
183 Set the mimetype for the specified filename extension. This is used
184 by the `plain` command when returning blob content.
185
186module-link::
142 Text which will be used as the formatstring for a hyperlink when a 187 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 188 submodule is printed in a directory listing. The arguments for the
144 formatstring are the path and SHA1 of the submodule commit. Default 189 formatstring are the path and SHA1 of the submodule commit. Default
145 value: "./?repo=%s&page=commit&id=%s" 190 value: "./?repo=%s&page=commit&id=%s"
146 191
147nocache 192nocache::
148 If set to the value "1" caching will be disabled. This settings is 193 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 194 deprecated, and will not be honored starting with cgit-1.0. Default
150 value: "0". 195 value: "0".
151 196
152renamelimit 197noplainemail::
198 If set to "1" showing full author email adresses will be disabled.
199 Default value: "0".
200
201noheader::
202 Flag which, when set to "1", will make cgit omit the standard header
203 on all pages. Default value: none. See also: "embedded".
204
205renamelimit::
153 Maximum number of files to consider when detecting renames. The value 206 Maximum number of files to consider when detecting renames. The value
154 "-1" uses the compiletime value in git (for further info, look at 207 "-1" uses the compiletime value in git (for further info, look at
155 `man git-diff`). Default value: "-1". 208 `man git-diff`). Default value: "-1".
156 209
157repo.group 210repo.group::
158 A value for the current repository group, which all repositories 211 Legacy alias for "section". This option is deprecated and will not be
159 specified after this setting will inherit. Default value: none. 212 supported in cgit-1.0.
160 213
161robots 214robots::
162 Text used as content for the "robots" meta-tag. Default value: 215 Text used as content for the "robots" meta-tag. Default value:
163 "index, nofollow". 216 "index, nofollow".
164 217
165root-desc 218root-desc::
166 Text printed below the heading on the repository index page. Default 219 Text printed below the heading on the repository index page. Default
167 value: "a fast webinterface for the git dscm". 220 value: "a fast webinterface for the git dscm".
168 221
169root-readme: 222root-readme::
170 The content of the file specified with this option will be included 223 The content of the file specified with this option will be included
171 verbatim below the "about" link on the repository index page. Default 224 verbatim below the "about" link on the repository index page. Default
172 value: none. 225 value: none.
173 226
174root-title 227root-title::
175 Text printed as heading on the repository index page. Default value: 228 Text printed as heading on the repository index page. Default value:
176 "Git Repository Browser". 229 "Git Repository Browser".
177 230
178snapshots 231scan-path::
179 Text which specifies the default (and allowed) set of snapshot formats 232 A path which will be scanned for repositories. If caching is enabled,
180 supported by cgit. The value is a space-separated list of zero or more 233 the result will be cached as a cgitrc include-file in the cache
181 of the following values: 234 directory. Default value: none. See also: cache-scanrc-ttl.
182 "tar" uncompressed tar-file 235
183 "tar.gz"gzip-compressed tar-file 236section::
184 "tar.bz2"bzip-compressed tar-file 237 The name of the current repository section - all repositories defined
185 "zip" zip-file 238 after this option will inherit the current section name. Default value:
186 Default value: none. 239 none.
187 240
188summary-branches 241snapshots::
242 Text which specifies the default set of snapshot formats generated by
243 cgit. The value is a space-separated list of zero or more of the
244 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
245
246source-filter::
247 Specifies a command which will be invoked to format plaintext blobs
248 in the tree view. The command will get the blob content on its STDIN
249 and the name of the blob as its only command line argument. The STDOUT
250 from the command will be included verbatim as the blob contents, i.e.
251 this can be used to implement e.g. syntax highlighting. Default value:
252 none.
253
254summary-branches::
189 Specifies the number of branches to display in the repository "summary" 255 Specifies the number of branches to display in the repository "summary"
190 view. Default value: "10". 256 view. Default value: "10".
191 257
192summary-log 258summary-log::
193 Specifies the number of log entries to display in the repository 259 Specifies the number of log entries to display in the repository
194 "summary" view. Default value: "10". 260 "summary" view. Default value: "10".
195 261
196summary-tags 262summary-tags::
197 Specifies the number of tags to display in the repository "summary" 263 Specifies the number of tags to display in the repository "summary"
198 view. Default value: "10". 264 view. Default value: "10".
199 265
200virtual-root 266virtual-root::
201 Url which, if specified, will be used as root for all cgit links. It 267 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 268 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 269 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
204 value: none. 270 value: none.
205 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 271 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
206 same kind of virtual urls, so this option will probably be deprecated. 272 same kind of virtual urls, so this option will probably be deprecated.
207 273
208REPOSITORY SETTINGS 274REPOSITORY SETTINGS
209------------------- 275-------------------
210repo.clone-url 276repo.about-filter::
277 Override the default about-filter. Default value: none. See also:
278 "enable-filter-overrides".
279
280repo.clone-url::
211 A list of space-separated urls which can be used to clone this repo. 281 A list of space-separated urls which can be used to clone this repo.
212 Default value: none. 282 Default value: none.
213 283
214repo.defbranch 284repo.commit-filter::
285 Override the default commit-filter. Default value: none. See also:
286 "enable-filter-overrides".
287
288repo.defbranch::
215 The name of the default branch for this repository. If no such branch 289 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 290 exists in the repository, the first branch name (when sorted) is used
217 as default instead. Default value: "master". 291 as default instead. Default value: "master".
218 292
219repo.desc 293repo.desc::
220 The value to show as repository description. Default value: none. 294 The value to show as repository description. Default value: none.
221 295
222repo.enable-log-filecount 296repo.enable-log-filecount::
223 A flag which can be used to disable the global setting 297 A flag which can be used to disable the global setting
224 `enable-log-filecount'. Default value: none. 298 `enable-log-filecount'. Default value: none.
225 299
226repo.enable-log-linecount 300repo.enable-log-linecount::
227 A flag which can be used to disable the global setting 301 A flag which can be used to disable the global setting
228 `enable-log-linecount'. Default value: none. 302 `enable-log-linecount'. Default value: none.
229 303
230repo.max-stats 304repo.max-stats::
231 Override the default maximum statistics period. Valid values are equal 305 Override the default maximum statistics period. Valid values are equal
232 to the values specified for the global "max-stats" setting. Default 306 to the values specified for the global "max-stats" setting. Default
233 value: none. 307 value: none.
234 308
235repo.name 309repo.name::
236 The value to show as repository name. Default value: <repo.url>. 310 The value to show as repository name. Default value: <repo.url>.
237 311
238repo.owner 312repo.owner::
239 A value used to identify the owner of the repository. Default value: 313 A value used to identify the owner of the repository. Default value:
240 none. 314 none.
241 315
242repo.path 316repo.path::
243 An absolute path to the repository directory. For non-bare repositories 317 An absolute path to the repository directory. For non-bare repositories
244 this is the .git-directory. Default value: none. 318 this is the .git-directory. Default value: none.
245 319
246repo.readme 320repo.readme::
247 A path (relative to <repo.path>) which specifies a file to include 321 A path (relative to <repo.path>) which specifies a file to include
248 verbatim as the "About" page for this repo. Default value: none. 322 verbatim as the "About" page for this repo. Default value: none.
249 323
250repo.snapshots 324repo.snapshots::
251 A mask of allowed snapshot-formats for this repo, restricted by the 325 A mask of allowed snapshot-formats for this repo, restricted by the
252 "snapshots" global setting. Default value: <snapshots>. 326 "snapshots" global setting. Default value: <snapshots>.
253 327
254repo.url 328repo.section::
329 Override the current section name for this repository. Default value:
330 none.
331
332repo.source-filter::
333 Override the default source-filter. Default value: none. See also:
334 "enable-filter-overrides".
335
336repo.url::
255 The relative url used to access the repository. This must be the first 337 The relative url used to access the repository. This must be the first
256 setting specified for each repo. Default value: none. 338 setting specified for each repo. Default value: none.
257 339
258 340
341REPOSITORY-SPECIFIC CGITRC FILE
342-------------------------------
343When the option "scan-path" is used to auto-discover git repositories, cgit
344will try to parse the file "cgitrc" within any found repository. Such a
345repo-specific config file may contain any of the repo-specific options
346described above, except "repo.url" and "repo.path". Additionally, the "filter"
347options are only acknowledged in repo-specific config files when
348"enable-filter-overrides" is set to "1".
349
350Note: the "repo." prefix is dropped from the option names in repo-specific
351config files, e.g. "repo.desc" becomes "desc".
352
353
259EXAMPLE CGITRC FILE 354EXAMPLE CGITRC FILE
260------------------- 355-------------------
261 356
357....
262# Enable caching of up to 1000 output entriess 358# Enable caching of up to 1000 output entriess
263cache-size=1000 359cache-size=1000
264 360
265 361
266# Specify some default clone prefixes 362# Specify some default clone prefixes
267clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 363clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
268 364
269# Specify the css url 365# Specify the css url
270css=/css/cgit.css 366css=/css/cgit.css
271 367
272 368
273# Show extra links for each repository on the index page 369# Show extra links for each repository on the index page
274enable-index-links=1 370enable-index-links=1
275 371
276 372
277# Show number of affected files per commit on the log pages 373# Show number of affected files per commit on the log pages
278enable-log-filecount=1 374enable-log-filecount=1
279 375
280 376
281# Show number of added/removed lines per commit on the log pages 377# Show number of added/removed lines per commit on the log pages
282enable-log-linecount=1 378enable-log-linecount=1
283 379
284 380
285# Add a cgit favicon 381# Add a cgit favicon
@@ -290,48 +386,61 @@ favicon=/favicon.ico
290logo=/img/mylogo.png 386logo=/img/mylogo.png
291 387
292 388
293# Enable statistics per week, month and quarter 389# Enable statistics per week, month and quarter
294max-stats=quarter 390max-stats=quarter
295 391
296 392
297# Set the title and heading of the repository index page 393# Set the title and heading of the repository index page
298root-title=foobar.com git repositories 394root-title=foobar.com git repositories
299 395
300 396
301# Set a subheading for the repository index page 397# Set a subheading for the repository index page
302root-desc=tracking the foobar development 398root-desc=tracking the foobar development
303 399
304 400
305# Include some more info about foobar.com on the index page 401# Include some more info about foobar.com on the index page
306root-readme=/var/www/htdocs/about.html 402root-readme=/var/www/htdocs/about.html
307 403
308 404
309# Allow download of tar.gz, tar.bz2 and zip-files 405# Allow download of tar.gz, tar.bz2 and zip-files
310snapshots=tar.gz tar.bz2 zip 406snapshots=tar.gz tar.bz2 zip
311 407
312 408
313## 409##
410## List of common mimetypes
411##
412
413mimetype.git=image/git
414mimetype.html=text/html
415mimetype.jpg=image/jpeg
416mimetype.jpeg=image/jpeg
417mimetype.pdf=application/pdf
418mimetype.png=image/png
419mimetype.svg=image/svg+xml
420
421
422##
314## List of repositories. 423## List of repositories.
315## PS: Any repositories listed when repo.group is unset will not be 424## PS: Any repositories listed when repo.group is unset will not be
316## displayed under a group heading 425## displayed under a group heading
317## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 426## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
318## and included like this: 427## and included like this:
319## include=/etc/cgitrepos 428## include=/etc/cgitrepos
320## 429##
321 430
322 431
323repo.url=foo 432repo.url=foo
324repo.path=/pub/git/foo.git 433repo.path=/pub/git/foo.git
325repo.desc=the master foo repository 434repo.desc=the master foo repository
326repo.owner=fooman@foobar.com 435repo.owner=fooman@foobar.com
327repo.readme=info/web/about.html 436repo.readme=info/web/about.html
328 437
329 438
330repo.url=bar 439repo.url=bar
331repo.path=/pub/git/bar.git 440repo.path=/pub/git/bar.git
332repo.desc=the bars for your foo 441repo.desc=the bars for your foo
333repo.owner=barman@foobar.com 442repo.owner=barman@foobar.com
334repo.readme=info/web/about.html 443repo.readme=info/web/about.html
335 444
336 445
337# The next repositories will be displayed under the 'extras' heading 446# The next repositories will be displayed under the 'extras' heading
@@ -347,42 +456,43 @@ repo.path=/pub/git/wiz.git
347repo.desc=the wizard of foo 456repo.desc=the wizard of foo
348 457
349 458
350# Add some mirrored repositories 459# Add some mirrored repositories
351repo.group=mirrors 460repo.group=mirrors
352 461
353 462
354repo.url=git 463repo.url=git
355repo.path=/pub/git/git.git 464repo.path=/pub/git/git.git
356repo.desc=the dscm 465repo.desc=the dscm
357 466
358 467
359repo.url=linux 468repo.url=linux
360repo.path=/pub/git/linux.git 469repo.path=/pub/git/linux.git
361repo.desc=the kernel 470repo.desc=the kernel
362 471
363# Disable adhoc downloads of this repo 472# Disable adhoc downloads of this repo
364repo.snapshots=0 473repo.snapshots=0
365 474
366# Disable line-counts for this repo 475# Disable line-counts for this repo
367repo.enable-log-linecount=0 476repo.enable-log-linecount=0
368 477
369# Restrict the max statistics period for this repo 478# Restrict the max statistics period for this repo
370repo.max-stats=month 479repo.max-stats=month
480....
371 481
372 482
373BUGS 483BUGS
374---- 484----
375Comments currently cannot appear on the same line as a setting; the comment 485Comments currently cannot appear on the same line as a setting; the comment
376will be included as part of the value. E.g. this line: 486will be included as part of the value. E.g. this line:
377 487
378 robots=index # allow indexing 488 robots=index # allow indexing
379 489
380will generate the following html element: 490will generate the following html element:
381 491
382 <meta name='robots' content='index # allow indexing'/> 492 <meta name='robots' content='index # allow indexing'/>
383 493
384 494
385 495
386AUTHOR 496AUTHOR
387------ 497------
388Lars Hjemli <hjemli@gmail.com> 498Lars Hjemli <hjemli@gmail.com>
diff --git a/cmd.c b/cmd.c
index cf97da7..766f903 100644
--- a/cmd.c
+++ b/cmd.c
@@ -18,49 +18,49 @@
18#include "ui-log.h" 18#include "ui-log.h"
19#include "ui-patch.h" 19#include "ui-patch.h"
20#include "ui-plain.h" 20#include "ui-plain.h"
21#include "ui-refs.h" 21#include "ui-refs.h"
22#include "ui-repolist.h" 22#include "ui-repolist.h"
23#include "ui-snapshot.h" 23#include "ui-snapshot.h"
24#include "ui-stats.h" 24#include "ui-stats.h"
25#include "ui-summary.h" 25#include "ui-summary.h"
26#include "ui-tag.h" 26#include "ui-tag.h"
27#include "ui-tree.h" 27#include "ui-tree.h"
28 28
29static void HEAD_fn(struct cgit_context *ctx) 29static void HEAD_fn(struct cgit_context *ctx)
30{ 30{
31 cgit_clone_head(ctx); 31 cgit_clone_head(ctx);
32} 32}
33 33
34static void atom_fn(struct cgit_context *ctx) 34static void atom_fn(struct cgit_context *ctx)
35{ 35{
36 cgit_print_atom(ctx->qry.head, ctx->qry.path, 10); 36 cgit_print_atom(ctx->qry.head, ctx->qry.path, 10);
37} 37}
38 38
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}
46 46
47static void blob_fn(struct cgit_context *ctx) 47static void blob_fn(struct cgit_context *ctx)
48{ 48{
49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); 49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head);
50} 50}
51 51
52static void commit_fn(struct cgit_context *ctx) 52static void commit_fn(struct cgit_context *ctx)
53{ 53{
54 cgit_print_commit(ctx->qry.sha1); 54 cgit_print_commit(ctx->qry.sha1);
55} 55}
56 56
57static void diff_fn(struct cgit_context *ctx) 57static void diff_fn(struct cgit_context *ctx)
58{ 58{
59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); 59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path);
60} 60}
61 61
62static void info_fn(struct cgit_context *ctx) 62static void info_fn(struct cgit_context *ctx)
63{ 63{
64 cgit_clone_info(ctx); 64 cgit_clone_info(ctx);
65} 65}
66 66
diff --git a/filters/commit-links.sh b/filters/commit-links.sh
new file mode 100755
index 0000000..165a533
--- a/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
--- a/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 7fb6bcff2dece2ff9fbc5ebfe526d9b2a7e764c
diff --git a/scan-tree.c b/scan-tree.c
index 47f3988..dbca797 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -1,137 +1,146 @@
1#include "cgit.h" 1#include "cgit.h"
2#include "configfile.h"
2#include "html.h" 3#include "html.h"
3 4
4#define MAX_PATH 4096 5#define MAX_PATH 4096
5 6
6/* return 1 if path contains a objects/ directory and a HEAD file */ 7/* return 1 if path contains a objects/ directory and a HEAD file */
7static int is_git_dir(const char *path) 8static int is_git_dir(const char *path)
8{ 9{
9 struct stat st; 10 struct stat st;
10 static char buf[MAX_PATH]; 11 static char buf[MAX_PATH];
11 12
12 if (snprintf(buf, MAX_PATH, "%s/objects", path) >= MAX_PATH) { 13 if (snprintf(buf, MAX_PATH, "%s/objects", path) >= MAX_PATH) {
13 fprintf(stderr, "Insanely long path: %s\n", path); 14 fprintf(stderr, "Insanely long path: %s\n", path);
14 return 0; 15 return 0;
15 } 16 }
16 if (stat(buf, &st)) { 17 if (stat(buf, &st)) {
17 if (errno != ENOENT) 18 if (errno != ENOENT)
18 fprintf(stderr, "Error checking path %s: %s (%d)\n", 19 fprintf(stderr, "Error checking path %s: %s (%d)\n",
19 path, strerror(errno), errno); 20 path, strerror(errno), errno);
20 return 0; 21 return 0;
21 } 22 }
22 if (!S_ISDIR(st.st_mode)) 23 if (!S_ISDIR(st.st_mode))
23 return 0; 24 return 0;
24 25
25 sprintf(buf, "%s/HEAD", path); 26 sprintf(buf, "%s/HEAD", path);
26 if (stat(buf, &st)) { 27 if (stat(buf, &st)) {
27 if (errno != ENOENT) 28 if (errno != ENOENT)
28 fprintf(stderr, "Error checking path %s: %s (%d)\n", 29 fprintf(stderr, "Error checking path %s: %s (%d)\n",
29 path, strerror(errno), errno); 30 path, strerror(errno), errno);
30 return 0; 31 return 0;
31 } 32 }
32 if (!S_ISREG(st.st_mode)) 33 if (!S_ISREG(st.st_mode))
33 return 0; 34 return 0;
34 35
35 return 1; 36 return 1;
36} 37}
37 38
38char *readfile(const char *path) 39struct cgit_repo *repo;
39{ 40repo_config_fn config_fn;
40 FILE *f;
41 static char buf[MAX_PATH];
42 41
43 if (!(f = fopen(path, "r"))) 42static void repo_config(const char *name, const char *value)
44 return NULL; 43{
45 buf[0] = 0; 44 config_fn(repo, name, value);
46 fgets(buf, MAX_PATH, f);
47 fclose(f);
48 return buf;
49} 45}
50 46
51static void add_repo(const char *base, const char *path) 47static void add_repo(const char *base, const char *path, repo_config_fn fn)
52{ 48{
53 struct cgit_repo *repo;
54 struct stat st; 49 struct stat st;
55 struct passwd *pwd; 50 struct passwd *pwd;
56 char *p; 51 char *p;
52 size_t size;
57 53
58 if (stat(path, &st)) { 54 if (stat(path, &st)) {
59 fprintf(stderr, "Error accessing %s: %s (%d)\n", 55 fprintf(stderr, "Error accessing %s: %s (%d)\n",
60 path, strerror(errno), errno); 56 path, strerror(errno), errno);
61 return; 57 return;
62 } 58 }
63 if ((pwd = getpwuid(st.st_uid)) == NULL) { 59 if ((pwd = getpwuid(st.st_uid)) == NULL) {
64 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", 60 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
65 path, strerror(errno), errno); 61 path, strerror(errno), errno);
66 return; 62 return;
67 } 63 }
68 if (base == path) 64 if (base == path)
69 p = fmt("%s", path); 65 p = fmt("%s", path);
70 else 66 else
71 p = fmt("%s", path + strlen(base) + 1); 67 p = fmt("%s", path + strlen(base) + 1);
72 68
73 if (!strcmp(p + strlen(p) - 5, "/.git")) 69 if (!strcmp(p + strlen(p) - 5, "/.git"))
74 p[strlen(p) - 5] = '\0'; 70 p[strlen(p) - 5] = '\0';
75 71
76 repo = cgit_add_repo(xstrdup(p)); 72 repo = cgit_add_repo(xstrdup(p));
77 repo->name = repo->url; 73 repo->name = repo->url;
78 repo->path = xstrdup(path); 74 repo->path = xstrdup(path);
75 p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL;
76 if (p)
77 *p = '\0';
79 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : ""); 78 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : "");
80 79
81 p = fmt("%s/description", path); 80 p = fmt("%s/description", path);
82 if (!stat(p, &st)) 81 if (!stat(p, &st))
83 repo->desc = xstrdup(readfile(p)); 82 readfile(p, &repo->desc, &size);
84 83
85 p = fmt("%s/README.html", path); 84 p = fmt("%s/README.html", path);
86 if (!stat(p, &st)) 85 if (!stat(p, &st))
87 repo->readme = "README.html"; 86 repo->readme = "README.html";
87
88 p = fmt("%s/cgitrc", path);
89 if (!stat(p, &st)) {
90 config_fn = fn;
91 parse_configfile(xstrdup(p), &repo_config);
92 }
88} 93}
89 94
90static void scan_path(const char *base, const char *path) 95static void scan_path(const char *base, const char *path, repo_config_fn fn)
91{ 96{
92 DIR *dir; 97 DIR *dir;
93 struct dirent *ent; 98 struct dirent *ent;
94 char *buf; 99 char *buf;
95 struct stat st; 100 struct stat st;
96 101
97 if (is_git_dir(path)) { 102 if (is_git_dir(path)) {
98 add_repo(base, path); 103 add_repo(base, path, fn);
104 return;
105 }
106 if (is_git_dir(fmt("%s/.git", path))) {
107 add_repo(base, fmt("%s/.git", path), fn);
99 return; 108 return;
100 } 109 }
101 dir = opendir(path); 110 dir = opendir(path);
102 if (!dir) { 111 if (!dir) {
103 fprintf(stderr, "Error opening directory %s: %s (%d)\n", 112 fprintf(stderr, "Error opening directory %s: %s (%d)\n",
104 path, strerror(errno), errno); 113 path, strerror(errno), errno);
105 return; 114 return;
106 } 115 }
107 while((ent = readdir(dir)) != NULL) { 116 while((ent = readdir(dir)) != NULL) {
108 if (ent->d_name[0] == '.') { 117 if (ent->d_name[0] == '.') {
109 if (ent->d_name[1] == '\0') 118 if (ent->d_name[1] == '\0')
110 continue; 119 continue;
111 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0') 120 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
112 continue; 121 continue;
113 } 122 }
114 buf = malloc(strlen(path) + strlen(ent->d_name) + 2); 123 buf = malloc(strlen(path) + strlen(ent->d_name) + 2);
115 if (!buf) { 124 if (!buf) {
116 fprintf(stderr, "Alloc error on %s: %s (%d)\n", 125 fprintf(stderr, "Alloc error on %s: %s (%d)\n",
117 path, strerror(errno), errno); 126 path, strerror(errno), errno);
118 exit(1); 127 exit(1);
119 } 128 }
120 sprintf(buf, "%s/%s", path, ent->d_name); 129 sprintf(buf, "%s/%s", path, ent->d_name);
121 if (stat(buf, &st)) { 130 if (stat(buf, &st)) {
122 fprintf(stderr, "Error checking path %s: %s (%d)\n", 131 fprintf(stderr, "Error checking path %s: %s (%d)\n",
123 buf, strerror(errno), errno); 132 buf, strerror(errno), errno);
124 free(buf); 133 free(buf);
125 continue; 134 continue;
126 } 135 }
127 if (S_ISDIR(st.st_mode)) 136 if (S_ISDIR(st.st_mode))
128 scan_path(base, buf); 137 scan_path(base, buf, fn);
129 free(buf); 138 free(buf);
130 } 139 }
131 closedir(dir); 140 closedir(dir);
132} 141}
133 142
134void scan_tree(const char *path) 143void scan_tree(const char *path, repo_config_fn fn)
135{ 144{
136 scan_path(path, path); 145 scan_path(path, path, fn);
137} 146}
diff --git a/scan-tree.h b/scan-tree.h
index b103b16..11539f4 100644
--- a/scan-tree.h
+++ b/scan-tree.h
@@ -1,3 +1,3 @@
1 1
2 2
3extern void scan_tree(const char *path); 3extern void scan_tree(const char *path, repo_config_fn fn);
diff --git a/shared.c b/shared.c
index cce0af4..d7b2d5a 100644
--- a/shared.c
+++ b/shared.c
@@ -27,62 +27,66 @@ int chk_positive(int result, char *msg)
27} 27}
28 28
29int chk_non_negative(int result, char *msg) 29int chk_non_negative(int result, char *msg)
30{ 30{
31 if (result < 0) 31 if (result < 0)
32 die("%s: %s",msg, strerror(errno)); 32 die("%s: %s",msg, strerror(errno));
33 return result; 33 return result;
34} 34}
35 35
36struct cgit_repo *cgit_add_repo(const char *url) 36struct cgit_repo *cgit_add_repo(const char *url)
37{ 37{
38 struct cgit_repo *ret; 38 struct cgit_repo *ret;
39 39
40 if (++cgit_repolist.count > cgit_repolist.length) { 40 if (++cgit_repolist.count > cgit_repolist.length) {
41 if (cgit_repolist.length == 0) 41 if (cgit_repolist.length == 0)
42 cgit_repolist.length = 8; 42 cgit_repolist.length = 8;
43 else 43 else
44 cgit_repolist.length *= 2; 44 cgit_repolist.length *= 2;
45 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 45 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
46 cgit_repolist.length * 46 cgit_repolist.length *
47 sizeof(struct cgit_repo)); 47 sizeof(struct cgit_repo));
48 } 48 }
49 49
50 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 50 ret = &cgit_repolist.repos[cgit_repolist.count-1];
51 memset(ret, 0, sizeof(struct cgit_repo));
51 ret->url = trim_end(url, '/'); 52 ret->url = trim_end(url, '/');
52 ret->name = ret->url; 53 ret->name = ret->url;
53 ret->path = NULL; 54 ret->path = NULL;
54 ret->desc = "[no description]"; 55 ret->desc = "[no description]";
55 ret->owner = NULL; 56 ret->owner = NULL;
56 ret->group = ctx.cfg.repo_group; 57 ret->section = ctx.cfg.section;
57 ret->defbranch = "master"; 58 ret->defbranch = "master";
58 ret->snapshots = ctx.cfg.snapshots; 59 ret->snapshots = ctx.cfg.snapshots;
59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->max_stats = ctx.cfg.max_stats; 62 ret->max_stats = ctx.cfg.max_stats;
62 ret->module_link = ctx.cfg.module_link; 63 ret->module_link = ctx.cfg.module_link;
63 ret->readme = NULL; 64 ret->readme = NULL;
64 ret->mtime = -1; 65 ret->mtime = -1;
66 ret->about_filter = ctx.cfg.about_filter;
67 ret->commit_filter = ctx.cfg.commit_filter;
68 ret->source_filter = ctx.cfg.source_filter;
65 return ret; 69 return ret;
66} 70}
67 71
68struct cgit_repo *cgit_get_repoinfo(const char *url) 72struct cgit_repo *cgit_get_repoinfo(const char *url)
69{ 73{
70 int i; 74 int i;
71 struct cgit_repo *repo; 75 struct cgit_repo *repo;
72 76
73 for (i=0; i<cgit_repolist.count; i++) { 77 for (i=0; i<cgit_repolist.count; i++) {
74 repo = &cgit_repolist.repos[i]; 78 repo = &cgit_repolist.repos[i];
75 if (!strcmp(repo->url, url)) 79 if (!strcmp(repo->url, url))
76 return repo; 80 return repo;
77 } 81 }
78 return NULL; 82 return NULL;
79} 83}
80 84
81void *cgit_free_commitinfo(struct commitinfo *info) 85void *cgit_free_commitinfo(struct commitinfo *info)
82{ 86{
83 free(info->author); 87 free(info->author);
84 free(info->author_email); 88 free(info->author_email);
85 free(info->committer); 89 free(info->committer);
86 free(info->committer_email); 90 free(info->committer_email);
87 free(info->subject); 91 free(info->subject);
88 free(info->msg); 92 free(info->msg);
@@ -334,24 +338,80 @@ int cgit_parse_snapshots_mask(const char *str)
334 const struct cgit_snapshot_format *f; 338 const struct cgit_snapshot_format *f;
335 static const char *delim = " \t,:/|;"; 339 static const char *delim = " \t,:/|;";
336 int tl, sl, rv = 0; 340 int tl, sl, rv = 0;
337 341
338 /* favor legacy setting */ 342 /* favor legacy setting */
339 if(atoi(str)) 343 if(atoi(str))
340 return 1; 344 return 1;
341 for(;;) { 345 for(;;) {
342 str += strspn(str,delim); 346 str += strspn(str,delim);
343 tl = strcspn(str,delim); 347 tl = strcspn(str,delim);
344 if (!tl) 348 if (!tl)
345 break; 349 break;
346 for (f = cgit_snapshot_formats; f->suffix; f++) { 350 for (f = cgit_snapshot_formats; f->suffix; f++) {
347 sl = strlen(f->suffix); 351 sl = strlen(f->suffix);
348 if((tl == sl && !strncmp(f->suffix, str, tl)) || 352 if((tl == sl && !strncmp(f->suffix, str, tl)) ||
349 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) { 353 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) {
350 rv |= f->bit; 354 rv |= f->bit;
351 break; 355 break;
352 } 356 }
353 } 357 }
354 str += tl; 358 str += tl;
355 } 359 }
356 return rv; 360 return rv;
357} 361}
362
363int cgit_open_filter(struct cgit_filter *filter)
364{
365
366 filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
367 "Unable to duplicate STDOUT");
368 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess");
369 filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
370 if (filter->pid == 0) {
371 close(filter->pipe_fh[1]);
372 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO),
373 "Unable to use pipe as STDIN");
374 execvp(filter->cmd, filter->argv);
375 die("Unable to exec subprocess %s: %s (%d)", filter->cmd,
376 strerror(errno), errno);
377 }
378 close(filter->pipe_fh[0]);
379 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO),
380 "Unable to use pipe as STDOUT");
381 close(filter->pipe_fh[1]);
382 return 0;
383}
384
385int cgit_close_filter(struct cgit_filter *filter)
386{
387 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
388 "Unable to restore STDOUT");
389 close(filter->old_stdout);
390 if (filter->pid < 0)
391 return 0;
392 waitpid(filter->pid, &filter->exitstatus, 0);
393 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus))
394 return 0;
395 die("Subprocess %s exited abnormally", filter->cmd);
396}
397
398/* Read the content of the specified file into a newly allocated buffer,
399 * zeroterminate the buffer and return 0 on success, errno otherwise.
400 */
401int readfile(const char *path, char **buf, size_t *size)
402{
403 int fd;
404 struct stat st;
405
406 fd = open(path, O_RDONLY);
407 if (fd == -1)
408 return errno;
409 if (fstat(fd, &st))
410 return errno;
411 if (!S_ISREG(st.st_mode))
412 return EISDIR;
413 *buf = xmalloc(st.st_size + 1);
414 *size = read_in_full(fd, *buf, st.st_size);
415 (*buf)[*size] = '\0';
416 return (*size == st.st_size ? 0 : errno);
417}
diff --git a/ui-atom.c b/ui-atom.c
index a6ea3ee..808b2d0 100644
--- a/ui-atom.c
+++ b/ui-atom.c
@@ -11,69 +11,70 @@
11#include "ui-shared.h" 11#include "ui-shared.h"
12 12
13void add_entry(struct commit *commit, char *host) 13void add_entry(struct commit *commit, char *host)
14{ 14{
15 char delim = '&'; 15 char delim = '&';
16 char *hex; 16 char *hex;
17 char *mail, *t, *t2; 17 char *mail, *t, *t2;
18 struct commitinfo *info; 18 struct commitinfo *info;
19 19
20 info = cgit_parse_commit(commit); 20 info = cgit_parse_commit(commit);
21 hex = sha1_to_hex(commit->object.sha1); 21 hex = sha1_to_hex(commit->object.sha1);
22 html("<entry>\n"); 22 html("<entry>\n");
23 html("<title>"); 23 html("<title>");
24 html_txt(info->subject); 24 html_txt(info->subject);
25 html("</title>\n"); 25 html("</title>\n");
26 html("<updated>"); 26 html("<updated>");
27 cgit_print_date(info->author_date, FMT_ATOMDATE, ctx.cfg.local_time); 27 cgit_print_date(info->author_date, FMT_ATOMDATE, ctx.cfg.local_time);
28 html("</updated>\n"); 28 html("</updated>\n");
29 html("<author>\n"); 29 html("<author>\n");
30 if (info->author) { 30 if (info->author) {
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 && !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)
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-commit.c b/ui-commit.c
index 41ce70e..f5b0ae5 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -14,93 +14,105 @@
14 14
15void cgit_print_commit(char *hex) 15void cgit_print_commit(char *hex)
16{ 16{
17 struct commit *commit, *parent; 17 struct commit *commit, *parent;
18 struct commitinfo *info; 18 struct commitinfo *info;
19 struct commit_list *p; 19 struct commit_list *p;
20 unsigned char sha1[20]; 20 unsigned char sha1[20];
21 char *tmp; 21 char *tmp;
22 int parents = 0; 22 int parents = 0;
23 23
24 if (!hex) 24 if (!hex)
25 hex = ctx.qry.head; 25 hex = ctx.qry.head;
26 26
27 if (get_sha1(hex, sha1)) { 27 if (get_sha1(hex, sha1)) {
28 cgit_print_error(fmt("Bad object id: %s", hex)); 28 cgit_print_error(fmt("Bad object id: %s", hex));
29 return; 29 return;
30 } 30 }
31 commit = lookup_commit_reference(sha1); 31 commit = lookup_commit_reference(sha1);
32 if (!commit) { 32 if (!commit) {
33 cgit_print_error(fmt("Bad commit reference: %s", hex)); 33 cgit_print_error(fmt("Bad commit reference: %s", hex));
34 return; 34 return;
35 } 35 }
36 info = cgit_parse_commit(commit); 36 info = cgit_parse_commit(commit);
37 37
38 load_ref_decorations(); 38 load_ref_decorations(DECORATE_FULL_REFS);
39 39
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");
55 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 59 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
56 tmp = sha1_to_hex(commit->object.sha1); 60 tmp = sha1_to_hex(commit->object.sha1);
57 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); 61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp);
58 html(" ("); 62 html(" (");
59 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 63 cgit_patch_link("patch", NULL, NULL, NULL, tmp);
60 html(")</td></tr>\n"); 64 html(")</td></tr>\n");
61 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 65 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
62 tmp = xstrdup(hex); 66 tmp = xstrdup(hex);
63 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 67 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
64 ctx.qry.head, tmp, NULL); 68 ctx.qry.head, tmp, NULL);
65 html("</td></tr>\n"); 69 html("</td></tr>\n");
66 for (p = commit->parents; p ; p = p->next) { 70 for (p = commit->parents; p ; p = p->next) {
67 parent = lookup_commit_reference(p->item->object.sha1); 71 parent = lookup_commit_reference(p->item->object.sha1);
68 if (!parent) { 72 if (!parent) {
69 html("<tr><td colspan='3'>"); 73 html("<tr><td colspan='3'>");
70 cgit_print_error("Error reading parent commit"); 74 cgit_print_error("Error reading parent commit");
71 html("</td></tr>"); 75 html("</td></tr>");
72 continue; 76 continue;
73 } 77 }
74 html("<tr><th>parent</th>" 78 html("<tr><th>parent</th>"
75 "<td colspan='2' class='sha1'>"); 79 "<td colspan='2' class='sha1'>");
76 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL,
77 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 81 ctx.qry.head, sha1_to_hex(p->item->object.sha1));
78 html(" ("); 82 html(" (");
79 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
80 sha1_to_hex(p->item->object.sha1), NULL); 84 sha1_to_hex(p->item->object.sha1), NULL);
81 html(")</td></tr>"); 85 html(")</td></tr>");
82 parents++; 86 parents++;
83 } 87 }
84 if (ctx.repo->snapshots) { 88 if (ctx.repo->snapshots) {
85 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 89 html("<tr><th>download</th><td colspan='2' class='sha1'>");
86 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 90 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
87 hex, ctx.repo->snapshots); 91 hex, ctx.repo->snapshots);
88 html("</td></tr>"); 92 html("</td></tr>");
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)
100 tmp = sha1_to_hex(commit->parents->item->object.sha1); 112 tmp = sha1_to_hex(commit->parents->item->object.sha1);
101 else 113 else
102 tmp = NULL; 114 tmp = NULL;
103 cgit_print_diff(ctx.qry.sha1, tmp, NULL); 115 cgit_print_diff(ctx.qry.sha1, tmp, NULL);
104 } 116 }
105 cgit_free_commitinfo(info); 117 cgit_free_commitinfo(info);
106} 118}
diff --git a/ui-log.c b/ui-log.c
index ba2ab03..f3132c9 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -32,48 +32,52 @@ void inspect_files(struct diff_filepair *pair)
32 32
33 files++; 33 files++;
34 if (ctx.repo->enable_log_linecount) 34 if (ctx.repo->enable_log_linecount)
35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, 35 cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size,
36 &new_size, &binary, count_lines); 36 &new_size, &binary, count_lines);
37} 37}
38 38
39void show_commit_decorations(struct commit *commit) 39void show_commit_decorations(struct commit *commit)
40{ 40{
41 struct name_decoration *deco; 41 struct name_decoration *deco;
42 static char buf[1024]; 42 static char buf[1024];
43 43
44 buf[sizeof(buf) - 1] = 0; 44 buf[sizeof(buf) - 1] = 0;
45 deco = lookup_decoration(&name_decoration, &commit->object); 45 deco = lookup_decoration(&name_decoration, &commit->object);
46 while (deco) { 46 while (deco) {
47 if (!prefixcmp(deco->name, "refs/heads/")) { 47 if (!prefixcmp(deco->name, "refs/heads/")) {
48 strncpy(buf, deco->name + 11, sizeof(buf) - 1); 48 strncpy(buf, deco->name + 11, sizeof(buf) - 1);
49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, 49 cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL,
50 0, NULL, NULL, ctx.qry.showmsg); 50 0, NULL, NULL, ctx.qry.showmsg);
51 } 51 }
52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) { 52 else if (!prefixcmp(deco->name, "tag: refs/tags/")) {
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,
59 sha1_to_hex(commit->object.sha1), NULL, 63 sha1_to_hex(commit->object.sha1), NULL,
60 0, NULL, NULL, ctx.qry.showmsg); 64 0, NULL, NULL, ctx.qry.showmsg);
61 } 65 }
62 else { 66 else {
63 strncpy(buf, deco->name, sizeof(buf) - 1); 67 strncpy(buf, deco->name, sizeof(buf) - 1);
64 cgit_commit_link(buf, NULL, "deco", ctx.qry.head, 68 cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
65 sha1_to_hex(commit->object.sha1)); 69 sha1_to_hex(commit->object.sha1));
66 } 70 }
67 deco = deco->next; 71 deco = deco->next;
68 } 72 }
69} 73}
70 74
71void print_commit(struct commit *commit) 75void print_commit(struct commit *commit)
72{ 76{
73 struct commitinfo *info; 77 struct commitinfo *info;
74 char *tmp; 78 char *tmp;
75 int cols = 2; 79 int cols = 2;
76 80
77 info = cgit_parse_commit(commit); 81 info = cgit_parse_commit(commit);
78 htmlf("<tr%s><td>", 82 htmlf("<tr%s><td>",
79 ctx.qry.showmsg ? " class='logheader'" : ""); 83 ctx.qry.showmsg ? " class='logheader'" : "");
@@ -136,49 +140,49 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
136 const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; 140 const char *argv[] = {NULL, NULL, NULL, NULL, NULL};
137 int argc = 2; 141 int argc = 2;
138 int i, columns = 3; 142 int i, columns = 3;
139 143
140 if (!tip) 144 if (!tip)
141 tip = ctx.qry.head; 145 tip = ctx.qry.head;
142 146
143 argv[1] = disambiguate_ref(tip); 147 argv[1] = disambiguate_ref(tip);
144 148
145 if (grep && pattern && (!strcmp(grep, "grep") || 149 if (grep && pattern && (!strcmp(grep, "grep") ||
146 !strcmp(grep, "author") || 150 !strcmp(grep, "author") ||
147 !strcmp(grep, "committer"))) 151 !strcmp(grep, "committer")))
148 argv[argc++] = fmt("--%s=%s", grep, pattern); 152 argv[argc++] = fmt("--%s=%s", grep, pattern);
149 153
150 if (path) { 154 if (path) {
151 argv[argc++] = "--"; 155 argv[argc++] = "--";
152 argv[argc++] = path; 156 argv[argc++] = path;
153 } 157 }
154 init_revisions(&rev, NULL); 158 init_revisions(&rev, NULL);
155 rev.abbrev = DEFAULT_ABBREV; 159 rev.abbrev = DEFAULT_ABBREV;
156 rev.commit_format = CMIT_FMT_DEFAULT; 160 rev.commit_format = CMIT_FMT_DEFAULT;
157 rev.verbose_header = 1; 161 rev.verbose_header = 1;
158 rev.show_root_diff = 0; 162 rev.show_root_diff = 0;
159 setup_revisions(argc, argv, &rev, NULL); 163 setup_revisions(argc, argv, &rev, NULL);
160 load_ref_decorations(); 164 load_ref_decorations(DECORATE_FULL_REFS);
161 rev.show_decorations = 1; 165 rev.show_decorations = 1;
162 rev.grep_filter.regflags |= REG_ICASE; 166 rev.grep_filter.regflags |= REG_ICASE;
163 compile_grep_patterns(&rev.grep_filter); 167 compile_grep_patterns(&rev.grep_filter);
164 prepare_revision_walk(&rev); 168 prepare_revision_walk(&rev);
165 169
166 if (pager) 170 if (pager)
167 html("<table class='list nowrap'>"); 171 html("<table class='list nowrap'>");
168 172
169 html("<tr class='nohover'><th class='left'>Age</th>" 173 html("<tr class='nohover'><th class='left'>Age</th>"
170 "<th class='left'>Commit message"); 174 "<th class='left'>Commit message");
171 if (pager) { 175 if (pager) {
172 html(" ("); 176 html(" (");
173 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, 177 cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
174 NULL, ctx.qry.head, ctx.qry.sha1, 178 NULL, ctx.qry.head, ctx.qry.sha1,
175 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, 179 ctx.qry.path, ctx.qry.ofs, ctx.qry.grep,
176 ctx.qry.search, ctx.qry.showmsg ? 0 : 1); 180 ctx.qry.search, ctx.qry.showmsg ? 0 : 1);
177 html(")"); 181 html(")");
178 } 182 }
179 html("</th><th class='left'>Author</th>"); 183 html("</th><th class='left'>Author</th>");
180 if (ctx.repo->enable_log_filecount) { 184 if (ctx.repo->enable_log_filecount) {
181 html("<th class='left'>Files</th>"); 185 html("<th class='left'>Files</th>");
182 columns++; 186 columns++;
183 if (ctx.repo->enable_log_linecount) { 187 if (ctx.repo->enable_log_linecount) {
184 html("<th class='left'>Lines</th>"); 188 html("<th class='left'>Lines</th>");
diff --git a/ui-patch.c b/ui-patch.c
index 5d665d3..2a8f7a5 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -87,39 +87,43 @@ void cgit_print_patch(char *hex)
87 if (!hex) 87 if (!hex)
88 hex = ctx.qry.head; 88 hex = ctx.qry.head;
89 89
90 if (get_sha1(hex, sha1)) { 90 if (get_sha1(hex, sha1)) {
91 cgit_print_error(fmt("Bad object id: %s", hex)); 91 cgit_print_error(fmt("Bad object id: %s", hex));
92 return; 92 return;
93 } 93 }
94 commit = lookup_commit_reference(sha1); 94 commit = lookup_commit_reference(sha1);
95 if (!commit) { 95 if (!commit) {
96 cgit_print_error(fmt("Bad commit reference: %s", hex)); 96 cgit_print_error(fmt("Bad commit reference: %s", hex));
97 return; 97 return;
98 } 98 }
99 info = cgit_parse_commit(commit); 99 info = cgit_parse_commit(commit);
100 100
101 if (commit->parents && commit->parents->item) 101 if (commit->parents && commit->parents->item)
102 hashcpy(old_sha1, commit->parents->item->object.sha1); 102 hashcpy(old_sha1, commit->parents->item->object.sha1);
103 else 103 else
104 hashclr(old_sha1); 104 hashclr(old_sha1);
105 105
106 patchname = fmt("%s.patch", sha1_to_hex(sha1)); 106 patchname = fmt("%s.patch", sha1_to_hex(sha1));
107 ctx.page.mimetype = "text/plain"; 107 ctx.page.mimetype = "text/plain";
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);
115 if (info->msg && *info->msg) { 119 if (info->msg && *info->msg) {
116 htmlf("%s", info->msg); 120 htmlf("%s", info->msg);
117 if (info->msg[strlen(info->msg) - 1] != '\n') 121 if (info->msg[strlen(info->msg) - 1] != '\n')
118 html("\n"); 122 html("\n");
119 } 123 }
120 html("---\n"); 124 html("---\n");
121 cgit_diff_tree(old_sha1, sha1, filepair_cb, NULL); 125 cgit_diff_tree(old_sha1, sha1, filepair_cb, NULL);
122 html("--\n"); 126 html("--\n");
123 htmlf("cgit %s\n", CGIT_VERSION); 127 htmlf("cgit %s\n", CGIT_VERSION);
124 cgit_free_commitinfo(info); 128 cgit_free_commitinfo(info);
125} 129}
diff --git a/ui-plain.c b/ui-plain.c
index e08b15b..a4ce077 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -1,60 +1,74 @@
1/* ui-plain.c: functions for output of plain blobs by path 1/* ui-plain.c: functions for output of plain blobs by path
2 * 2 *
3 * Copyright (C) 2008 Lars Hjemli 3 * Copyright (C) 2008 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12 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, *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) {
25 html_status(404, "Not found", 0); 26 html_status(404, "Not found", 0);
26 return; 27 return;
27 } 28 }
28 29
29 buf = read_sha1_file(sha1, &type, &size); 30 buf = read_sha1_file(sha1, &type, &size);
30 if (!buf) { 31 if (!buf) {
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;
40} 54}
41 55
42static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 56static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
43 const char *pathname, unsigned mode, int stage, 57 const char *pathname, unsigned mode, int stage,
44 void *cbdata) 58 void *cbdata)
45{ 59{
46 if (S_ISDIR(mode)) 60 if (S_ISDIR(mode))
47 return READ_TREE_RECURSIVE; 61 return READ_TREE_RECURSIVE;
48 62
49 if (S_ISREG(mode) && !strncmp(base, match_path, baselen) && 63 if (S_ISREG(mode) && !strncmp(base, match_path, baselen) &&
50 !strcmp(pathname, match_path + baselen)) 64 !strcmp(pathname, match_path + baselen))
51 print_object(sha1, pathname); 65 print_object(sha1, pathname);
52 66
53 return 0; 67 return 0;
54} 68}
55 69
56void cgit_print_plain(struct cgit_context *ctx) 70void cgit_print_plain(struct cgit_context *ctx)
57{ 71{
58 const char *rev = ctx->qry.sha1; 72 const char *rev = ctx->qry.sha1;
59 unsigned char sha1[20]; 73 unsigned char sha1[20];
60 struct commit *commit; 74 struct commit *commit;
diff --git a/ui-refs.c b/ui-refs.c
index 25da00a..d3b4f6e 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -25,50 +25,61 @@ static int cmp_age(int age1, int age2)
25 25
26 return -1; 26 return -1;
27} 27}
28 28
29static int cmp_ref_name(const void *a, const void *b) 29static int cmp_ref_name(const void *a, const void *b)
30{ 30{
31 struct refinfo *r1 = *(struct refinfo **)a; 31 struct refinfo *r1 = *(struct refinfo **)a;
32 struct refinfo *r2 = *(struct refinfo **)b; 32 struct refinfo *r2 = *(struct refinfo **)b;
33 33
34 return strcmp(r1->refname, r2->refname); 34 return strcmp(r1->refname, r2->refname);
35} 35}
36 36
37static int cmp_branch_age(const void *a, const void *b) 37static int cmp_branch_age(const void *a, const void *b)
38{ 38{
39 struct refinfo *r1 = *(struct refinfo **)a; 39 struct refinfo *r1 = *(struct refinfo **)a;
40 struct refinfo *r2 = *(struct refinfo **)b; 40 struct refinfo *r2 = *(struct refinfo **)b;
41 41
42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date); 42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date);
43} 43}
44 44
45static int cmp_tag_age(const void *a, const void *b) 45static 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)
54{ 65{
55 struct commitinfo *info = ref->commit; 66 struct commitinfo *info = ref->commit;
56 char *name = (char *)ref->refname; 67 char *name = (char *)ref->refname;
57 68
58 if (!info) 69 if (!info)
59 return 1; 70 return 1;
60 html("<tr><td>"); 71 html("<tr><td>");
61 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
62 ctx.qry.showmsg); 73 ctx.qry.showmsg);
63 html("</td><td>"); 74 html("</td><td>");
64 75
65 if (ref->object->type == OBJ_COMMIT) { 76 if (ref->object->type == OBJ_COMMIT) {
66 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 77 cgit_commit_link(info->subject, NULL, NULL, name, NULL);
67 html("</td><td>"); 78 html("</td><td>");
68 html_txt(info->author); 79 html_txt(info->author);
69 html("</td><td colspan='2'>"); 80 html("</td><td colspan='2'>");
70 cgit_print_age(info->commit->date, -1, NULL); 81 cgit_print_age(info->commit->date, -1, NULL);
71 } else { 82 } else {
72 html("</td><td></td><td>"); 83 html("</td><td></td><td>");
73 cgit_object_link(ref->object); 84 cgit_object_link(ref->object);
74 } 85 }
@@ -124,48 +135,54 @@ static int print_tag(struct refinfo *ref)
124 html("<tr><td>"); 135 html("<tr><td>");
125 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 136 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
126 html("</td><td>"); 137 html("</td><td>");
127 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT)) 138 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
128 print_tag_downloads(ctx.repo, name); 139 print_tag_downloads(ctx.repo, name);
129 else 140 else
130 cgit_object_link(tag->tagged); 141 cgit_object_link(tag->tagged);
131 html("</td><td>"); 142 html("</td><td>");
132 if (info->tagger) 143 if (info->tagger)
133 html(info->tagger); 144 html(info->tagger);
134 html("</td><td colspan='2'>"); 145 html("</td><td colspan='2'>");
135 if (info->tagger_date > 0) 146 if (info->tagger_date > 0)
136 cgit_print_age(info->tagger_date, -1, NULL); 147 cgit_print_age(info->tagger_date, -1, NULL);
137 html("</td></tr>\n"); 148 html("</td></tr>\n");
138 } else { 149 } else {
139 if (!header) 150 if (!header)
140 print_tag_header(); 151 print_tag_header();
141 html("<tr><td>"); 152 html("<tr><td>");
142 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 153 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
143 html("</td><td>"); 154 html("</td><td>");
144 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT)) 155 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT))
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;
151} 168}
152 169
153static void print_refs_link(char *path) 170static void print_refs_link(char *path)
154{ 171{
155 html("<tr class='nohover'><td colspan='4'>"); 172 html("<tr class='nohover'><td colspan='4'>");
156 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); 173 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
157 html("</td></tr>"); 174 html("</td></tr>");
158} 175}
159 176
160void cgit_print_branches(int maxcount) 177void cgit_print_branches(int maxcount)
161{ 178{
162 struct reflist list; 179 struct reflist list;
163 int i; 180 int i;
164 181
165 html("<tr class='nohover'><th class='left'>Branch</th>" 182 html("<tr class='nohover'><th class='left'>Branch</th>"
166 "<th class='left'>Commit message</th>" 183 "<th class='left'>Commit message</th>"
167 "<th class='left'>Author</th>" 184 "<th class='left'>Author</th>"
168 "<th class='left' colspan='2'>Age</th></tr>\n"); 185 "<th class='left' colspan='2'>Age</th></tr>\n");
169 186
170 list.refs = NULL; 187 list.refs = NULL;
171 list.alloc = list.count = 0; 188 list.alloc = list.count = 0;
diff --git a/ui-repolist.c b/ui-repolist.c
index 3aedde5..3ef2e99 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -1,57 +1,58 @@
1/* ui-repolist.c: functions for generating the repolist page 1/* ui-repolist.c: functions for generating the repolist page
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9/* This is needed for strcasestr to be defined by <string.h> */ 9/* This is needed for strcasestr to be defined by <string.h> */
10#define _GNU_SOURCE 1 10#define _GNU_SOURCE 1
11#include <string.h> 11#include <string.h>
12 12
13#include <time.h> 13#include <time.h>
14 14
15#include "cgit.h" 15#include "cgit.h"
16#include "html.h" 16#include "html.h"
17#include "ui-shared.h" 17#include "ui-shared.h"
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)
37{ 38{
38 char *path; 39 char *path;
39 struct stat s; 40 struct stat s;
40 struct cgit_repo *r = (struct cgit_repo *)repo; 41 struct cgit_repo *r = (struct cgit_repo *)repo;
41 42
42 if (repo->mtime != -1) { 43 if (repo->mtime != -1) {
43 *mtime = repo->mtime; 44 *mtime = repo->mtime;
44 return 1; 45 return 1;
45 } 46 }
46 path = fmt("%s/%s", repo->path, ctx.cfg.agefile); 47 path = fmt("%s/%s", repo->path, ctx.cfg.agefile);
47 if (stat(path, &s) == 0) { 48 if (stat(path, &s) == 0) {
48 *mtime = read_agefile(path); 49 *mtime = read_agefile(path);
49 r->mtime = *mtime; 50 r->mtime = *mtime;
50 return 1; 51 return 1;
51 } 52 }
52 53
53 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); 54 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch);
54 if (stat(path, &s) == 0) 55 if (stat(path, &s) == 0)
55 *mtime = s.st_mtime; 56 *mtime = s.st_mtime;
56 else 57 else
57 *mtime = 0; 58 *mtime = 0;
@@ -114,166 +115,190 @@ void print_header(int columns)
114} 115}
115 116
116 117
117void print_pager(int items, int pagelen, char *search) 118void print_pager(int items, int pagelen, char *search)
118{ 119{
119 int i; 120 int i;
120 html("<div class='pager'>"); 121 html("<div class='pager'>");
121 for(i = 0; i * pagelen < items; i++) 122 for(i = 0; i * pagelen < items; i++)
122 cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL, 123 cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL,
123 search, i * pagelen); 124 search, i * pagelen);
124 html("</div>"); 125 html("</div>");
125} 126}
126 127
127static int cmp(const char *s1, const char *s2) 128static int cmp(const char *s1, const char *s2)
128{ 129{
129 if (s1 && s2) 130 if (s1 && s2)
130 return strcmp(s1, s2); 131 return strcmp(s1, s2);
131 if (s1 && !s2) 132 if (s1 && !s2)
132 return -1; 133 return -1;
133 if (s2 && !s1) 134 if (s2 && !s1)
134 return 1; 135 return 1;
135 return 0; 136 return 0;
136} 137}
137 138
139static int sort_section(const void *a, const void *b)
140{
141 const struct cgit_repo *r1 = a;
142 const struct cgit_repo *r2 = b;
143 int result;
144
145 result = cmp(r1->section, r2->section);
146 if (!result)
147 result = cmp(r1->name, r2->name);
148 return result;
149}
150
138static int sort_name(const void *a, const void *b) 151static int sort_name(const void *a, const void *b)
139{ 152{
140 const struct cgit_repo *r1 = a; 153 const struct cgit_repo *r1 = a;
141 const struct cgit_repo *r2 = b; 154 const struct cgit_repo *r2 = b;
142 155
143 return cmp(r1->name, r2->name); 156 return cmp(r1->name, r2->name);
144} 157}
145 158
146static int sort_desc(const void *a, const void *b) 159static int sort_desc(const void *a, const void *b)
147{ 160{
148 const struct cgit_repo *r1 = a; 161 const struct cgit_repo *r1 = a;
149 const struct cgit_repo *r2 = b; 162 const struct cgit_repo *r2 = b;
150 163
151 return cmp(r1->desc, r2->desc); 164 return cmp(r1->desc, r2->desc);
152} 165}
153 166
154static int sort_owner(const void *a, const void *b) 167static int sort_owner(const void *a, const void *b)
155{ 168{
156 const struct cgit_repo *r1 = a; 169 const struct cgit_repo *r1 = a;
157 const struct cgit_repo *r2 = b; 170 const struct cgit_repo *r2 = b;
158 171
159 return cmp(r1->owner, r2->owner); 172 return cmp(r1->owner, r2->owner);
160} 173}
161 174
162static int sort_idle(const void *a, const void *b) 175static int sort_idle(const void *a, const void *b)
163{ 176{
164 const struct cgit_repo *r1 = a; 177 const struct cgit_repo *r1 = a;
165 const struct cgit_repo *r2 = b; 178 const struct cgit_repo *r2 = b;
166 time_t t1, t2; 179 time_t t1, t2;
167 180
168 t1 = t2 = 0; 181 t1 = t2 = 0;
169 get_repo_modtime(r1, &t1); 182 get_repo_modtime(r1, &t1);
170 get_repo_modtime(r2, &t2); 183 get_repo_modtime(r2, &t2);
171 return t2 - t1; 184 return t2 - t1;
172} 185}
173 186
174struct sortcolumn { 187struct sortcolumn {
175 const char *name; 188 const char *name;
176 int (*fn)(const void *a, const void *b); 189 int (*fn)(const void *a, const void *b);
177}; 190};
178 191
179struct sortcolumn sortcolumn[] = { 192struct sortcolumn sortcolumn[] = {
193 {"section", sort_section},
180 {"name", sort_name}, 194 {"name", sort_name},
181 {"desc", sort_desc}, 195 {"desc", sort_desc},
182 {"owner", sort_owner}, 196 {"owner", sort_owner},
183 {"idle", sort_idle}, 197 {"idle", sort_idle},
184 {NULL, NULL} 198 {NULL, NULL}
185}; 199};
186 200
187int sort_repolist(char *field) 201int sort_repolist(char *field)
188{ 202{
189 struct sortcolumn *column; 203 struct sortcolumn *column;
190 204
191 for (column = &sortcolumn[0]; column->name; column++) { 205 for (column = &sortcolumn[0]; column->name; column++) {
192 if (strcmp(field, column->name)) 206 if (strcmp(field, column->name))
193 continue; 207 continue;
194 qsort(cgit_repolist.repos, cgit_repolist.count, 208 qsort(cgit_repolist.repos, cgit_repolist.count,
195 sizeof(struct cgit_repo), column->fn); 209 sizeof(struct cgit_repo), column->fn);
196 return 1; 210 return 1;
197 } 211 }
198 return 0; 212 return 0;
199} 213}
200 214
201 215
202void cgit_print_repolist() 216void cgit_print_repolist()
203{ 217{
204 int i, columns = 4, hits = 0, header = 0; 218 int i, columns = 4, hits = 0, header = 0;
205 char *last_group = NULL; 219 char *last_section = NULL;
220 char *section;
206 int sorted = 0; 221 int sorted = 0;
207 222
208 if (ctx.cfg.enable_index_links) 223 if (ctx.cfg.enable_index_links)
209 columns++; 224 columns++;
210 225
211 ctx.page.title = ctx.cfg.root_title; 226 ctx.page.title = ctx.cfg.root_title;
212 cgit_print_http_headers(&ctx); 227 cgit_print_http_headers(&ctx);
213 cgit_print_docstart(&ctx); 228 cgit_print_docstart(&ctx);
214 cgit_print_pageheader(&ctx); 229 cgit_print_pageheader(&ctx);
215 230
216 if (ctx.cfg.index_header) 231 if (ctx.cfg.index_header)
217 html_include(ctx.cfg.index_header); 232 html_include(ctx.cfg.index_header);
218 233
219 if(ctx.qry.sort) 234 if(ctx.qry.sort)
220 sorted = sort_repolist(ctx.qry.sort); 235 sorted = sort_repolist(ctx.qry.sort);
236 else
237 sort_repolist("section");
221 238
222 html("<table summary='repository list' class='list nowrap'>"); 239 html("<table summary='repository list' class='list nowrap'>");
223 for (i=0; i<cgit_repolist.count; i++) { 240 for (i=0; i<cgit_repolist.count; i++) {
224 ctx.repo = &cgit_repolist.repos[i]; 241 ctx.repo = &cgit_repolist.repos[i];
225 if (!(is_match(ctx.repo) && is_in_url(ctx.repo))) 242 if (!(is_match(ctx.repo) && is_in_url(ctx.repo)))
226 continue; 243 continue;
227 hits++; 244 hits++;
228 if (hits <= ctx.qry.ofs) 245 if (hits <= ctx.qry.ofs)
229 continue; 246 continue;
230 if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count) 247 if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count)
231 continue; 248 continue;
232 if (!header++) 249 if (!header++)
233 print_header(columns); 250 print_header(columns);
251 section = ctx.repo->section;
252 if (section && !strcmp(section, ""))
253 section = NULL;
234 if (!sorted && 254 if (!sorted &&
235 ((last_group == NULL && ctx.repo->group != NULL) || 255 ((last_section == NULL && section != NULL) ||
236 (last_group != NULL && ctx.repo->group == NULL) || 256 (last_section != NULL && section == NULL) ||
237 (last_group != NULL && ctx.repo->group != NULL && 257 (last_section != NULL && section != NULL &&
238 strcmp(ctx.repo->group, last_group)))) { 258 strcmp(section, last_section)))) {
239 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>", 259 htmlf("<tr class='nohover'><td colspan='%d' class='reposection'>",
240 columns); 260 columns);
241 html_txt(ctx.repo->group); 261 html_txt(section);
242 html("</td></tr>"); 262 html("</td></tr>");
243 last_group = ctx.repo->group; 263 last_section = section;
244 } 264 }
245 htmlf("<tr><td class='%s'>", 265 htmlf("<tr><td class='%s'>",
246 !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); 266 !sorted && section ? "sublevel-repo" : "toplevel-repo");
247 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); 267 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
248 html("</td><td>"); 268 html("</td><td>");
249 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); 269 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL);
250 html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc); 270 html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc);
251 html_link_close(); 271 html_link_close();
252 html("</td><td>"); 272 html("</td><td>");
253 html_txt(ctx.repo->owner); 273 html_txt(ctx.repo->owner);
254 html("</td><td>"); 274 html("</td><td>");
255 print_modtime(ctx.repo); 275 print_modtime(ctx.repo);
256 html("</td>"); 276 html("</td>");
257 if (ctx.cfg.enable_index_links) { 277 if (ctx.cfg.enable_index_links) {
258 html("<td>"); 278 html("<td>");
259 cgit_summary_link("summary", NULL, "button", NULL); 279 cgit_summary_link("summary", NULL, "button", NULL);
260 cgit_log_link("log", NULL, "button", NULL, NULL, NULL, 280 cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
261 0, NULL, NULL, ctx.qry.showmsg); 281 0, NULL, NULL, ctx.qry.showmsg);
262 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); 282 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
263 html("</td>"); 283 html("</td>");
264 } 284 }
265 html("</tr>\n"); 285 html("</tr>\n");
266 } 286 }
267 html("</table>"); 287 html("</table>");
268 if (!hits) 288 if (!hits)
269 cgit_print_error("No repositories found"); 289 cgit_print_error("No repositories found");
270 else if (hits > ctx.cfg.max_repo_count) 290 else if (hits > ctx.cfg.max_repo_count)
271 print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search); 291 print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search);
272 cgit_print_docend(); 292 cgit_print_docend();
273} 293}
274 294
275void cgit_print_site_readme() 295void cgit_print_site_readme()
276{ 296{
277 if (ctx.cfg.root_readme) 297 if (!ctx.cfg.root_readme)
278 html_include(ctx.cfg.root_readme); 298 return;
299 if (ctx.cfg.about_filter)
300 cgit_open_filter(ctx.cfg.about_filter);
301 html_include(ctx.cfg.root_readme);
302 if (ctx.cfg.about_filter)
303 cgit_close_filter(ctx.cfg.about_filter);
279} 304}
diff --git a/ui-shared.c b/ui-shared.c
index 40060ba..07d5dd4 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -13,66 +13,65 @@
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_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()
58{ 57{
59 if (ctx.cfg.virtual_root) 58 if (ctx.cfg.virtual_root)
60 return fmt("%s/", ctx.cfg.virtual_root); 59 return fmt("%s/", ctx.cfg.virtual_root);
61 else 60 else
62 return ctx.cfg.script_name; 61 return ctx.cfg.script_name;
63} 62}
64 63
65char *cgit_repourl(const char *reponame) 64char *cgit_repourl(const char *reponame)
66{ 65{
67 if (ctx.cfg.virtual_root) { 66 if (ctx.cfg.virtual_root) {
68 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame); 67 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
69 } else { 68 } else {
70 return fmt("?r=%s", reponame); 69 return fmt("?r=%s", reponame);
71 } 70 }
72} 71}
73 72
74char *cgit_fileurl(const char *reponame, const char *pagename, 73char *cgit_fileurl(const char *reponame, const char *pagename,
75 const char *filename, const char *query) 74 const char *filename, const char *query)
76{ 75{
77 char *tmp; 76 char *tmp;
78 char *delim; 77 char *delim;
@@ -435,107 +434,132 @@ void cgit_print_age(time_t t, time_t max_relative, char *format)
435 secs * 1.0 / TM_HOUR); 434 secs * 1.0 / TM_HOUR);
436 return; 435 return;
437 } 436 }
438 if (secs < TM_WEEK * 2) { 437 if (secs < TM_WEEK * 2) {
439 htmlf("<span class='age-days'>%.0f days</span>", 438 htmlf("<span class='age-days'>%.0f days</span>",
440 secs * 1.0 / TM_DAY); 439 secs * 1.0 / TM_DAY);
441 return; 440 return;
442 } 441 }
443 if (secs < TM_MONTH * 2) { 442 if (secs < TM_MONTH * 2) {
444 htmlf("<span class='age-weeks'>%.0f weeks</span>", 443 htmlf("<span class='age-weeks'>%.0f weeks</span>",
445 secs * 1.0 / TM_WEEK); 444 secs * 1.0 / TM_WEEK);
446 return; 445 return;
447 } 446 }
448 if (secs < TM_YEAR * 2) { 447 if (secs < TM_YEAR * 2) {
449 htmlf("<span class='age-months'>%.0f months</span>", 448 htmlf("<span class='age-months'>%.0f months</span>",
450 secs * 1.0 / TM_MONTH); 449 secs * 1.0 / TM_MONTH);
451 return; 450 return;
452 } 451 }
453 htmlf("<span class='age-years'>%.0f years</span>", 452 htmlf("<span class='age-years'>%.0f years</span>",
454 secs * 1.0 / TM_YEAR); 453 secs * 1.0 / TM_YEAR);
455} 454}
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);
462 else if (ctx->page.mimetype) 466 else if (ctx->page.mimetype)
463 htmlf("Content-Type: %s\n", ctx->page.mimetype); 467 htmlf("Content-Type: %s\n", ctx->page.mimetype);
464 if (ctx->page.size) 468 if (ctx->page.size)
465 htmlf("Content-Length: %ld\n", ctx->page.size); 469 htmlf("Content-Length: %ld\n", ctx->page.size);
466 if (ctx->page.filename) 470 if (ctx->page.filename)
467 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 471 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
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");
479 html("<head>\n"); 493 html("<head>\n");
480 html("<title>"); 494 html("<title>");
481 html_txt(ctx->page.title); 495 html_txt(ctx->page.title);
482 html("</title>\n"); 496 html("</title>\n");
483 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 497 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
484 if (ctx->cfg.robots && *ctx->cfg.robots) 498 if (ctx->cfg.robots && *ctx->cfg.robots)
485 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 499 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
486 html("<link rel='stylesheet' type='text/css' href='"); 500 html("<link rel='stylesheet' type='text/css' href='");
487 html_attr(ctx->cfg.css); 501 html_attr(ctx->cfg.css);
488 html("'/>\n"); 502 html("'/>\n");
489 if (ctx->cfg.favicon) { 503 if (ctx->cfg.favicon) {
490 html("<link rel='shortcut icon' href='"); 504 html("<link rel='shortcut icon' href='");
491 html_attr(ctx->cfg.favicon); 505 html_attr(ctx->cfg.favicon);
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)
504 html_include(ctx->cfg.header); 521 html_include(ctx->cfg.header);
505} 522}
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 {
513 htmlf("<div class='footer'>generated by cgit %s at ", 536 htmlf("<div class='footer'>generated by cgit %s at ",
514 cgit_version); 537 cgit_version);
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
521int print_branch_option(const char *refname, const unsigned char *sha1, 545int print_branch_option(const char *refname, const unsigned char *sha1,
522 int flags, void *cb_data) 546 int flags, void *cb_data)
523{ 547{
524 char *name = (char *)refname; 548 char *name = (char *)refname;
525 html_option(name, name, ctx.qry.head); 549 html_option(name, name, ctx.qry.head);
526 return 0; 550 return 0;
527} 551}
528 552
529int print_archive_ref(const char *refname, const unsigned char *sha1, 553int print_archive_ref(const char *refname, const unsigned char *sha1,
530 int flags, void *cb_data) 554 int flags, void *cb_data)
531{ 555{
532 struct tag *tag; 556 struct tag *tag;
533 struct taginfo *info; 557 struct taginfo *info;
534 struct object *obj; 558 struct object *obj;
535 char buf[256], *url; 559 char buf[256], *url;
536 unsigned char fileid[20]; 560 unsigned char fileid[20];
537 int *header = (int *)cb_data; 561 int *header = (int *)cb_data;
538 562
539 if (prefixcmp(refname, "refs/archives")) 563 if (prefixcmp(refname, "refs/archives"))
540 return 0; 564 return 0;
541 strncpy(buf, refname+14, sizeof(buf)); 565 strncpy(buf, refname+14, sizeof(buf));
@@ -581,98 +605,105 @@ void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page)
581 html_hidden("h", ctx.qry.head); 605 html_hidden("h", ctx.qry.head);
582 606
583 if (ctx.qry.sha1) 607 if (ctx.qry.sha1)
584 html_hidden("id", ctx.qry.sha1); 608 html_hidden("id", ctx.qry.sha1);
585 if (ctx.qry.sha2) 609 if (ctx.qry.sha2)
586 html_hidden("id2", ctx.qry.sha2); 610 html_hidden("id2", ctx.qry.sha2);
587 if (ctx.qry.showmsg) 611 if (ctx.qry.showmsg)
588 html_hidden("showmsg", "1"); 612 html_hidden("showmsg", "1");
589 613
590 if (incl_search) { 614 if (incl_search) {
591 if (ctx.qry.grep) 615 if (ctx.qry.grep)
592 html_hidden("qt", ctx.qry.grep); 616 html_hidden("qt", ctx.qry.grep);
593 if (ctx.qry.search) 617 if (ctx.qry.search)
594 html_hidden("q", ctx.qry.search); 618 html_hidden("q", ctx.qry.search);
595 } 619 }
596} 620}
597 621
598const char *fallback_cmd = "repolist"; 622const char *fallback_cmd = "repolist";
599 623
600char *hc(struct cgit_cmd *cmd, const char *page) 624char *hc(struct cgit_cmd *cmd, const char *page)
601{ 625{
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
615 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) { 634 if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) {
616 html("<td class='logo' rowspan='2'><a href='"); 635 html("<td class='logo' rowspan='2'><a href='");
617 if (ctx->cfg.logo_link) 636 if (ctx->cfg.logo_link)
618 html_attr(ctx->cfg.logo_link); 637 html_attr(ctx->cfg.logo_link);
619 else 638 else
620 html_attr(cgit_rooturl()); 639 html_attr(cgit_rooturl());
621 html("'><img src='"); 640 html("'><img src='");
622 html_attr(ctx->cfg.logo); 641 html_attr(ctx->cfg.logo);
623 html("' alt='cgit logo'/></a></td>\n"); 642 html("' alt='cgit logo'/></a></td>\n");
624 } 643 }
625 644
626 html("<td class='main'>"); 645 html("<td class='main'>");
627 if (ctx->repo) { 646 if (ctx->repo) {
628 cgit_index_link("index", NULL, NULL, NULL, 0); 647 cgit_index_link("index", NULL, NULL, NULL, 0);
629 html(" : "); 648 html(" : ");
630 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 649 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
631 html("</td><td class='form'>"); 650 html("</td><td class='form'>");
632 html("<form method='get' action=''>\n"); 651 html("<form method='get' action=''>\n");
633 cgit_add_hidden_formfields(0, 1, ctx->qry.page); 652 cgit_add_hidden_formfields(0, 1, ctx->qry.page);
634 html("<select name='h' onchange='this.form.submit();'>\n"); 653 html("<select name='h' onchange='this.form.submit();'>\n");
635 for_each_branch_ref(print_branch_option, ctx->qry.head); 654 for_each_branch_ref(print_branch_option, ctx->qry.head);
636 html("</select> "); 655 html("</select> ");
637 html("<input type='submit' name='' value='switch'/>"); 656 html("<input type='submit' name='' value='switch'/>");
638 html("</form>"); 657 html("</form>");
639 } else 658 } else
640 html_txt(ctx->cfg.root_title); 659 html_txt(ctx->cfg.root_title);
641 html("</td></tr>\n"); 660 html("</td></tr>\n");
642 661
643 html("<tr><td class='sub'>"); 662 html("<tr><td class='sub'>");
644 if (ctx->repo) { 663 if (ctx->repo) {
645 html_txt(ctx->repo->desc); 664 html_txt(ctx->repo->desc);
646 html("</td><td class='sub right'>"); 665 html("</td><td class='sub right'>");
647 html_txt(ctx->repo->owner); 666 html_txt(ctx->repo->owner);
648 } else { 667 } else {
649 if (ctx->cfg.root_desc) 668 if (ctx->cfg.root_desc)
650 html_txt(ctx->cfg.root_desc); 669 html_txt(ctx->cfg.root_desc);
651 else if (ctx->cfg.index_info) 670 else if (ctx->cfg.index_info)
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) {
658 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 689 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
659 ctx->qry.head); 690 ctx->qry.head);
660 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 691 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
661 ctx->qry.sha1, NULL); 692 ctx->qry.sha1, NULL);
662 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 693 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
663 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); 694 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
664 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 695 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
665 ctx->qry.sha1, NULL); 696 ctx->qry.sha1, NULL);
666 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 697 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
667 ctx->qry.head, ctx->qry.sha1); 698 ctx->qry.head, ctx->qry.sha1);
668 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 699 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
669 ctx->qry.sha1, ctx->qry.sha2, NULL); 700 ctx->qry.sha1, ctx->qry.sha2, NULL);
670 if (ctx->repo->max_stats) 701 if (ctx->repo->max_stats)
671 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 702 cgit_stats_link("stats", NULL, hc(cmd, "stats"),
672 ctx->qry.head, NULL); 703 ctx->qry.head, NULL);
673 if (ctx->repo->readme) 704 if (ctx->repo->readme)
674 reporevlink("about", "about", NULL, 705 reporevlink("about", "about", NULL,
675 hc(cmd, "about"), ctx->qry.head, NULL, 706 hc(cmd, "about"), ctx->qry.head, NULL,
676 NULL); 707 NULL);
677 html("</td><td class='form'>"); 708 html("</td><td class='form'>");
678 html("<form class='right' method='get' action='"); 709 html("<form class='right' method='get' action='");
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 5372f5d..4136b3e 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -1,69 +1,48 @@
1/* ui-snapshot.c: generate snapshot of a commit 1/* ui-snapshot.c: generate snapshot of a commit
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "html.h" 10#include "html.h"
11#include "ui-shared.h" 11#include "ui-shared.h"
12 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
49static int write_tar_gzip_archive(struct archiver_args *args) 28static int write_tar_gzip_archive(struct archiver_args *args)
50{ 29{
51 return write_compressed_tar_archive(args,"gzip"); 30 return write_compressed_tar_archive(args,"gzip");
52} 31}
53 32
54static int write_tar_bzip2_archive(struct archiver_args *args) 33static int write_tar_bzip2_archive(struct archiver_args *args)
55{ 34{
56 return write_compressed_tar_archive(args,"bzip2"); 35 return write_compressed_tar_archive(args,"bzip2");
57} 36}
58 37
59const struct cgit_snapshot_format cgit_snapshot_formats[] = { 38const struct cgit_snapshot_format cgit_snapshot_formats[] = {
60 { ".zip", "application/x-zip", write_zip_archive, 0x1 }, 39 { ".zip", "application/x-zip", write_zip_archive, 0x1 },
61 { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x2 }, 40 { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x2 },
62 { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x4 }, 41 { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x4 },
63 { ".tar", "application/x-tar", write_tar_archive, 0x8 }, 42 { ".tar", "application/x-tar", write_tar_archive, 0x8 },
64 {} 43 {}
65}; 44};
66 45
67static const struct cgit_snapshot_format *get_format(const char *filename) 46static const struct cgit_snapshot_format *get_format(const char *filename)
68{ 47{
69 const struct cgit_snapshot_format *fmt; 48 const struct cgit_snapshot_format *fmt;
diff --git a/ui-stats.c b/ui-stats.c
index 9fc06d3..bdaf9cc 100644
--- a/ui-stats.c
+++ b/ui-stats.c
@@ -133,48 +133,56 @@ struct cgit_period periods[] = {
133/* Given a period code or name, return a period index (1, 2, 3 or 4) 133/* Given a period code or name, return a period index (1, 2, 3 or 4)
134 * and update the period pointer to the correcsponding struct. 134 * and update the period pointer to the correcsponding struct.
135 * If no matching code is found, return 0. 135 * If no matching code is found, return 0.
136 */ 136 */
137int cgit_find_stats_period(const char *expr, struct cgit_period **period) 137int cgit_find_stats_period(const char *expr, struct cgit_period **period)
138{ 138{
139 int i; 139 int i;
140 char code = '\0'; 140 char code = '\0';
141 141
142 if (!expr) 142 if (!expr)
143 return 0; 143 return 0;
144 144
145 if (strlen(expr) == 1) 145 if (strlen(expr) == 1)
146 code = expr[0]; 146 code = expr[0];
147 147
148 for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++) 148 for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++)
149 if (periods[i].code == code || !strcmp(periods[i].name, expr)) { 149 if (periods[i].code == code || !strcmp(periods[i].name, expr)) {
150 if (period) 150 if (period)
151 *period = &periods[i]; 151 *period = &periods[i];
152 return i+1; 152 return i+1;
153 } 153 }
154 return 0; 154 return 0;
155} 155}
156 156
157const char *cgit_find_stats_periodname(int idx)
158{
159 if (idx > 0 && idx < 4)
160 return periods[idx - 1].name;
161 else
162 return "";
163}
164
157static void add_commit(struct string_list *authors, struct commit *commit, 165static void add_commit(struct string_list *authors, struct commit *commit,
158 struct cgit_period *period) 166 struct cgit_period *period)
159{ 167{
160 struct commitinfo *info; 168 struct commitinfo *info;
161 struct string_list_item *author, *item; 169 struct string_list_item *author, *item;
162 struct authorstat *authorstat; 170 struct authorstat *authorstat;
163 struct string_list *items; 171 struct string_list *items;
164 char *tmp; 172 char *tmp;
165 struct tm *date; 173 struct tm *date;
166 time_t t; 174 time_t t;
167 175
168 info = cgit_parse_commit(commit); 176 info = cgit_parse_commit(commit);
169 tmp = xstrdup(info->author); 177 tmp = xstrdup(info->author);
170 author = string_list_insert(tmp, authors); 178 author = string_list_insert(tmp, authors);
171 if (!author->util) 179 if (!author->util)
172 author->util = xcalloc(1, sizeof(struct authorstat)); 180 author->util = xcalloc(1, sizeof(struct authorstat));
173 else 181 else
174 free(tmp); 182 free(tmp);
175 authorstat = author->util; 183 authorstat = author->util;
176 items = &authorstat->list; 184 items = &authorstat->list;
177 t = info->committer_date; 185 t = info->committer_date;
178 date = gmtime(&t); 186 date = gmtime(&t);
179 period->trunc(date); 187 period->trunc(date);
180 tmp = xstrdup(period->pretty(date)); 188 tmp = xstrdup(period->pretty(date));
diff --git a/ui-stats.h b/ui-stats.h
index 4f13dba..f0761ba 100644
--- a/ui-stats.h
+++ b/ui-stats.h
@@ -1,27 +1,28 @@
1#ifndef UI_STATS_H 1#ifndef UI_STATS_H
2#define UI_STATS_H 2#define UI_STATS_H
3 3
4#include "cgit.h" 4#include "cgit.h"
5 5
6struct cgit_period { 6struct cgit_period {
7 const char code; 7 const char code;
8 const char *name; 8 const char *name;
9 int max_periods; 9 int max_periods;
10 int count; 10 int count;
11 11
12 /* Convert a tm value to the first day in the period */ 12 /* Convert a tm value to the first day in the period */
13 void (*trunc)(struct tm *tm); 13 void (*trunc)(struct tm *tm);
14 14
15 /* Update tm value to start of next/previous period */ 15 /* Update tm value to start of next/previous period */
16 void (*dec)(struct tm *tm); 16 void (*dec)(struct tm *tm);
17 void (*inc)(struct tm *tm); 17 void (*inc)(struct tm *tm);
18 18
19 /* Pretty-print a tm value */ 19 /* Pretty-print a tm value */
20 char *(*pretty)(struct tm *tm); 20 char *(*pretty)(struct tm *tm);
21}; 21};
22 22
23extern int cgit_find_stats_period(const char *expr, struct cgit_period **period); 23extern int cgit_find_stats_period(const char *expr, struct cgit_period **period);
24extern const char *cgit_find_stats_periodname(int idx);
24 25
25extern void cgit_show_stats(struct cgit_context *ctx); 26extern void cgit_show_stats(struct cgit_context *ctx);
26 27
27#endif /* UI_STATS_H */ 28#endif /* UI_STATS_H */
diff --git a/ui-summary.c b/ui-summary.c
index ede4a62..a2c018e 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -45,32 +45,48 @@ static void print_urls(char *txt, char *suffix)
45 print_url(h, suffix); 45 print_url(h, suffix);
46 *t = c; 46 *t = c;
47 h = t; 47 h = t;
48 } 48 }
49} 49}
50 50
51void cgit_print_summary() 51void cgit_print_summary()
52{ 52{
53 html("<table summary='repository info' class='list nowrap'>"); 53 html("<table summary='repository info' class='list nowrap'>");
54 cgit_print_branches(ctx.cfg.summary_branches); 54 cgit_print_branches(ctx.cfg.summary_branches);
55 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 55 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
56 cgit_print_tags(ctx.cfg.summary_tags); 56 cgit_print_tags(ctx.cfg.summary_tags);
57 if (ctx.cfg.summary_log > 0) { 57 if (ctx.cfg.summary_log > 0) {
58 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 58 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
59 cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, 59 cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL,
60 NULL, NULL, 0); 60 NULL, NULL, 0);
61 } 61 }
62 if (ctx.repo->clone_url) 62 if (ctx.repo->clone_url)
63 print_urls(ctx.repo->clone_url, NULL); 63 print_urls(ctx.repo->clone_url, NULL);
64 else if (ctx.cfg.clone_prefix) 64 else if (ctx.cfg.clone_prefix)
65 print_urls(ctx.cfg.clone_prefix, ctx.repo->url); 65 print_urls(ctx.cfg.clone_prefix, ctx.repo->url);
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
@@ -1,7 +1,7 @@
1#ifndef UI_SUMMARY_H 1#ifndef UI_SUMMARY_H
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
@@ -46,47 +46,47 @@ void cgit_print_tag(char *revname)
46 } 46 }
47 obj = parse_object(sha1); 47 obj = parse_object(sha1);
48 if (!obj) { 48 if (!obj) {
49 cgit_print_error(fmt("Bad object id: %s", sha1_to_hex(sha1))); 49 cgit_print_error(fmt("Bad object id: %s", sha1_to_hex(sha1)));
50 return; 50 return;
51 } 51 }
52 if (obj->type == OBJ_TAG) { 52 if (obj->type == OBJ_TAG) {
53 tag = lookup_tag(sha1); 53 tag = lookup_tag(sha1);
54 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) { 54 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) {
55 cgit_print_error(fmt("Bad tag object: %s", revname)); 55 cgit_print_error(fmt("Bad tag object: %s", revname));
56 return; 56 return;
57 } 57 }
58 html("<table class='commit-info'>\n"); 58 html("<table class='commit-info'>\n");
59 htmlf("<tr><td>Tag name</td><td>"); 59 htmlf("<tr><td>Tag name</td><td>");
60 html_txt(revname); 60 html_txt(revname);
61 htmlf(" (%s)</td></tr>\n", sha1_to_hex(sha1)); 61 htmlf(" (%s)</td></tr>\n", sha1_to_hex(sha1));
62 if (info->tagger_date > 0) { 62 if (info->tagger_date > 0) {
63 html("<tr><td>Tag date</td><td>"); 63 html("<tr><td>Tag date</td><td>");
64 cgit_print_date(info->tagger_date, FMT_LONGDATE, ctx.cfg.local_time); 64 cgit_print_date(info->tagger_date, FMT_LONGDATE, ctx.cfg.local_time);
65 html("</td></tr>\n"); 65 html("</td></tr>\n");
66 } 66 }
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 }
74 html("</td></tr>\n"); 74 html("</td></tr>\n");
75 } 75 }
76 html("<tr><td>Tagged object</td><td>"); 76 html("<tr><td>Tagged object</td><td>");
77 cgit_object_link(tag->tagged); 77 cgit_object_link(tag->tagged);
78 html("</td></tr>\n"); 78 html("</td></tr>\n");
79 html("</table>\n"); 79 html("</table>\n");
80 print_tag_content(info->msg); 80 print_tag_content(info->msg);
81 } else { 81 } else {
82 html("<table class='commit-info'>\n"); 82 html("<table class='commit-info'>\n");
83 htmlf("<tr><td>Tag name</td><td>"); 83 htmlf("<tr><td>Tag name</td><td>");
84 html_txt(revname); 84 html_txt(revname);
85 html("</td></tr>\n"); 85 html("</td></tr>\n");
86 html("<tr><td>Tagged object</td><td>"); 86 html("<tr><td>Tagged object</td><td>");
87 cgit_object_link(obj); 87 cgit_object_link(obj);
88 html("</td></tr>\n"); 88 html("</td></tr>\n");
89 html("</table>\n"); 89 html("</table>\n");
90 } 90 }
91 return; 91 return;
92} 92}
diff --git a/ui-tree.c b/ui-tree.c
index 553dbaa..f53ab64 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,162 +1,185 @@
1/* ui-tree.c: functions for tree output 1/* ui-tree.c: functions for tree output
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include <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(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 html("<tr><td class='linenumbers'><pre>"); 25
26 idx = 0; 26 if (ctx.cfg.enable_tree_linenumbers) {
27 lineno = 0; 27 html("<tr><td class='linenumbers'><pre>");
28 28 idx = 0;
29 if (size) { 29 lineno = 0;
30 htmlf(numberfmt, ++lineno); 30
31 while(idx < size - 1) { // skip absolute last newline 31 if (size) {
32 if (buf[idx] == '\n') 32 htmlf(numberfmt, ++lineno);
33 htmlf(numberfmt, ++lineno); 33 while(idx < size - 1) { // skip absolute last newline
34 idx++; 34 if (buf[idx] == '\n')
35 htmlf(numberfmt, ++lineno);
36 idx++;
37 }
35 } 38 }
39 html("</pre></td>\n");
40 }
41 else {
42 html("<tr>\n");
43 }
44
45 if (ctx.repo->source_filter) {
46 html("<td class='lines'><pre><code>");
47 ctx.repo->source_filter->argv[1] = xstrdup(name);
48 cgit_open_filter(ctx.repo->source_filter);
49 write(STDOUT_FILENO, buf, size);
50 cgit_close_filter(ctx.repo->source_filter);
51 html("</code></pre></td></tr></table>\n");
52 return;
36 } 53 }
37 html("</pre></td>\n"); 54
38 html("<td class='lines'><pre><code>"); 55 html("<td class='lines'><pre><code>");
39 html_txt(buf); 56 html_txt(buf);
40 html("</code></pre></td></tr></table>\n"); 57 html("</code></pre></td></tr></table>\n");
41} 58}
42 59
43#define ROWLEN 32 60#define ROWLEN 32
44 61
45static void print_binary_buffer(char *buf, unsigned long size) 62static void print_binary_buffer(char *buf, unsigned long size)
46{ 63{
47 unsigned long ofs, idx; 64 unsigned long ofs, idx;
48 static char ascii[ROWLEN + 1]; 65 static char ascii[ROWLEN + 1];
49 66
50 html("<table summary='blob content' class='bin-blob'>\n"); 67 html("<table summary='blob content' class='bin-blob'>\n");
51 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>"); 68 html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>");
52 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { 69 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
53 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs); 70 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs);
54 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 71 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
55 htmlf("%*s%02x", 72 htmlf("%*s%02x",
56 idx == 16 ? 4 : 1, "", 73 idx == 16 ? 4 : 1, "",
57 buf[idx] & 0xff); 74 buf[idx] & 0xff);
58 html(" </td><td class='hex'>"); 75 html(" </td><td class='hex'>");
59 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 76 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
60 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; 77 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
61 ascii[idx] = '\0'; 78 ascii[idx] = '\0';
62 html_txt(ascii); 79 html_txt(ascii);
63 html("</td></tr>\n"); 80 html("</td></tr>\n");
64 } 81 }
65 html("</table>\n"); 82 html("</table>\n");
66} 83}
67 84
68static void print_object(const unsigned char *sha1, char *path) 85static void print_object(const unsigned char *sha1, char *path, const char *basename)
69{ 86{
70 enum object_type type; 87 enum object_type type;
71 char *buf; 88 char *buf;
72 unsigned long size; 89 unsigned long size;
73 90
74 type = sha1_object_info(sha1, &size); 91 type = sha1_object_info(sha1, &size);
75 if (type == OBJ_BAD) { 92 if (type == OBJ_BAD) {
76 cgit_print_error(fmt("Bad object name: %s", 93 cgit_print_error(fmt("Bad object name: %s",
77 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
78 return; 95 return;
79 } 96 }
80 97
81 buf = read_sha1_file(sha1, &type, &size); 98 buf = read_sha1_file(sha1, &type, &size);
82 if (!buf) { 99 if (!buf) {
83 cgit_print_error(fmt("Error reading object %s", 100 cgit_print_error(fmt("Error reading object %s",
84 sha1_to_hex(sha1))); 101 sha1_to_hex(sha1)));
85 return; 102 return;
86 } 103 }
87 104
88 html(" ("); 105 html(" (");
89 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 106 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
90 curr_rev, path); 107 curr_rev, path);
91 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 108 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1));
92 109
93 if (buffer_is_binary(buf, size)) 110 if (buffer_is_binary(buf, size))
94 print_binary_buffer(buf, size); 111 print_binary_buffer(buf, size);
95 else 112 else
96 print_text_buffer(buf, size); 113 print_text_buffer(basename, buf, size);
97} 114}
98 115
99 116
100static int ls_item(const unsigned char *sha1, const char *base, int baselen, 117static int ls_item(const unsigned char *sha1, const char *base, int baselen,
101 const char *pathname, unsigned int mode, int stage, 118 const char *pathname, unsigned int mode, int stage,
102 void *cbdata) 119 void *cbdata)
103{ 120{
104 char *name; 121 char *name;
105 char *fullpath; 122 char *fullpath;
123 char *class;
106 enum object_type type; 124 enum object_type type;
107 unsigned long size = 0; 125 unsigned long size = 0;
108 126
109 name = xstrdup(pathname); 127 name = xstrdup(pathname);
110 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 128 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
111 ctx.qry.path ? "/" : "", name); 129 ctx.qry.path ? "/" : "", name);
112 130
113 if (!S_ISGITLINK(mode)) { 131 if (!S_ISGITLINK(mode)) {
114 type = sha1_object_info(sha1, &size); 132 type = sha1_object_info(sha1, &size);
115 if (type == OBJ_BAD) { 133 if (type == OBJ_BAD) {
116 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 134 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
117 name, 135 name,
118 sha1_to_hex(sha1)); 136 sha1_to_hex(sha1));
119 return 0; 137 return 0;
120 } 138 }
121 } 139 }
122 140
123 html("<tr><td class='ls-mode'>"); 141 html("<tr><td class='ls-mode'>");
124 cgit_print_filemode(mode); 142 cgit_print_filemode(mode);
125 html("</td><td>"); 143 html("</td><td>");
126 if (S_ISGITLINK(mode)) { 144 if (S_ISGITLINK(mode)) {
127 htmlf("<a class='ls-mod' href='"); 145 htmlf("<a class='ls-mod' href='");
128 html_attr(fmt(ctx.repo->module_link, 146 html_attr(fmt(ctx.repo->module_link,
129 name, 147 name,
130 sha1_to_hex(sha1))); 148 sha1_to_hex(sha1)));
131 html("'>"); 149 html("'>");
132 html_txt(name); 150 html_txt(name);
133 html("</a>"); 151 html("</a>");
134 } else if (S_ISDIR(mode)) { 152 } else if (S_ISDIR(mode)) {
135 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 153 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
136 curr_rev, fullpath); 154 curr_rev, fullpath);
137 } else { 155 } else {
138 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head, 156 class = strrchr(name, '.');
157 if (class != NULL) {
158 class = fmt("ls-blob %s", class + 1);
159 } else
160 class = "ls-blob";
161 cgit_tree_link(name, NULL, class, ctx.qry.head,
139 curr_rev, fullpath); 162 curr_rev, fullpath);
140 } 163 }
141 htmlf("</td><td class='ls-size'>%li</td>", size); 164 htmlf("</td><td class='ls-size'>%li</td>", size);
142 165
143 html("<td>"); 166 html("<td>");
144 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 167 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
145 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 168 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
146 if (ctx.repo->max_stats) 169 if (ctx.repo->max_stats)
147 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 170 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
148 fullpath); 171 fullpath);
149 html("</td></tr>\n"); 172 html("</td></tr>\n");
150 free(name); 173 free(name);
151 return 0; 174 return 0;
152} 175}
153 176
154static void ls_head() 177static void ls_head()
155{ 178{
156 html("<table summary='tree listing' class='list'>\n"); 179 html("<table summary='tree listing' class='list'>\n");
157 html("<tr class='nohover'>"); 180 html("<tr class='nohover'>");
158 html("<th class='left'>Mode</th>"); 181 html("<th class='left'>Mode</th>");
159 html("<th class='left'>Name</th>"); 182 html("<th class='left'>Name</th>");
160 html("<th class='right'>Size</th>"); 183 html("<th class='right'>Size</th>");
161 html("<th/>"); 184 html("<th/>");
162 html("</tr>\n"); 185 html("</tr>\n");
@@ -192,49 +215,49 @@ static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
192 const char *pathname, unsigned mode, int stage, 215 const char *pathname, unsigned mode, int stage,
193 void *cbdata) 216 void *cbdata)
194{ 217{
195 static int state; 218 static int state;
196 static char buffer[PATH_MAX]; 219 static char buffer[PATH_MAX];
197 char *url; 220 char *url;
198 221
199 if (state == 0) { 222 if (state == 0) {
200 memcpy(buffer, base, baselen); 223 memcpy(buffer, base, baselen);
201 strcpy(buffer+baselen, pathname); 224 strcpy(buffer+baselen, pathname);
202 url = cgit_pageurl(ctx.qry.repo, "tree", 225 url = cgit_pageurl(ctx.qry.repo, "tree",
203 fmt("h=%s&amp;path=%s", curr_rev, buffer)); 226 fmt("h=%s&amp;path=%s", curr_rev, buffer));
204 html("/"); 227 html("/");
205 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head, 228 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
206 curr_rev, buffer); 229 curr_rev, buffer);
207 230
208 if (strcmp(match_path, buffer)) 231 if (strcmp(match_path, buffer))
209 return READ_TREE_RECURSIVE; 232 return READ_TREE_RECURSIVE;
210 233
211 if (S_ISDIR(mode)) { 234 if (S_ISDIR(mode)) {
212 state = 1; 235 state = 1;
213 ls_head(); 236 ls_head();
214 return READ_TREE_RECURSIVE; 237 return READ_TREE_RECURSIVE;
215 } else { 238 } else {
216 print_object(sha1, buffer); 239 print_object(sha1, buffer, pathname);
217 return 0; 240 return 0;
218 } 241 }
219 } 242 }
220 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 243 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
221 return 0; 244 return 0;
222} 245}
223 246
224 247
225/* 248/*
226 * Show a tree or a blob 249 * Show a tree or a blob
227 * rev: the commit pointing at the root tree object 250 * rev: the commit pointing at the root tree object
228 * path: path to tree or blob 251 * path: path to tree or blob
229 */ 252 */
230void cgit_print_tree(const char *rev, char *path) 253void cgit_print_tree(const char *rev, char *path)
231{ 254{
232 unsigned char sha1[20]; 255 unsigned char sha1[20];
233 struct commit *commit; 256 struct commit *commit;
234 const char *paths[] = {path, NULL}; 257 const char *paths[] = {path, NULL};
235 258
236 if (!rev) 259 if (!rev)
237 rev = ctx.qry.head; 260 rev = ctx.qry.head;
238 261
239 curr_rev = xstrdup(rev); 262 curr_rev = xstrdup(rev);
240 if (get_sha1(rev, sha1)) { 263 if (get_sha1(rev, sha1)) {