summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--.gitignore5
-rw-r--r--Makefile21
-rw-r--r--cgit-doc.css3
-rw-r--r--cgit.c87
-rw-r--r--cgit.css2
-rw-r--r--cgit.h41
-rw-r--r--cgitrc.5.txt187
-rw-r--r--cmd.c2
-rwxr-xr-xfilters/commit-links.sh12
-rwxr-xr-xfilters/syntax-highlighting.sh39
m---------git0
-rw-r--r--shared.c38
-rw-r--r--ui-atom.c8
-rw-r--r--ui-blob.c8
-rw-r--r--ui-commit.c20
-rw-r--r--ui-log.c4
-rw-r--r--ui-patch.c6
-rw-r--r--ui-plain.c18
-rw-r--r--ui-refs.c19
-rw-r--r--ui-repolist.c9
-rw-r--r--ui-shared.c81
-rw-r--r--ui-shared.h1
-rw-r--r--ui-snapshot.c35
-rw-r--r--ui-summary.c28
-rw-r--r--ui-summary.h2
-rw-r--r--ui-tag.c2
-rw-r--r--ui-tree.c26
27 files changed, 553 insertions, 151 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 33c606d..1f9893a 100644
--- a/Makefile
+++ b/Makefile
@@ -1,20 +1,20 @@
1CGIT_VERSION = v0.8.2.1 1CGIT_VERSION = v0.8.2.1
2CGIT_SCRIPT_NAME = cgit.cgi 2CGIT_SCRIPT_NAME = cgit.cgi
3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit 3CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH) 4CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
5CGIT_CONFIG = /etc/cgitrc 5CGIT_CONFIG = /etc/cgitrc
6CACHE_ROOT = /var/cache/cgit 6CACHE_ROOT = /var/cache/cgit
7SHA1_HEADER = <openssl/sha.h> 7SHA1_HEADER = <openssl/sha.h>
8GIT_VER = 1.6.1.1 8GIT_VER = 1.6.3.4
9GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 9GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
10INSTALL = install 10INSTALL = install
11 11
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
@@ -91,25 +91,26 @@ OBJECTS += 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)"'
@@ -140,17 +141,31 @@ test: all
140install: all 141install: all
141 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH) 142 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
142 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 143 $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
143 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) 144 $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
144 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css 145 $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
145 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png 146 $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
146 147
147uninstall: 148uninstall:
148 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) 149 rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
149 rm -f $(CGIT_DATA_PATH)/cgit.css 150 rm -f $(CGIT_DATA_PATH)/cgit.css
150 rm -f $(CGIT_DATA_PATH)/cgit.png 151 rm -f $(CGIT_DATA_PATH)/cgit.png
151 152
152clean: 153doc: man-doc html-doc pdf-doc
154
155man-doc: cgitrc.5.txt
156 a2x -f manpage cgitrc.5.txt
157
158html-doc: cgitrc.5.txt
159 a2x -f xhtml --stylesheet=cgit-doc.css cgitrc.5.txt
160
161pdf-doc: cgitrc.5.txt
162 a2x -f pdf cgitrc.5.txt
163
164clean: clean-doc
153 rm -f cgit VERSION *.o *.d 165 rm -f cgit VERSION *.o *.d
154 166
167clean-doc:
168 rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo
169
155get-git: 170get-git:
156 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git 171 curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git
diff --git a/cgit-doc.css b/cgit-doc.css
new file mode 100644
index 0000000..5a399b6
--- a/dev/null
+++ b/cgit-doc.css
@@ -0,0 +1,3 @@
1div.variablelist dt {
2 margin-top: 1em;
3}
diff --git a/cgit.c b/cgit.c
index 5301840..5816f3d 100644
--- a/cgit.c
+++ b/cgit.c
@@ -8,102 +8,141 @@
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
20void config_cb(const char *name, const char *value) 43void config_cb(const char *name, const char *value)
21{ 44{
22 if (!strcmp(name, "root-title")) 45 if (!strcmp(name, "root-title"))
23 ctx.cfg.root_title = xstrdup(value); 46 ctx.cfg.root_title = xstrdup(value);
24 else if (!strcmp(name, "root-desc")) 47 else if (!strcmp(name, "root-desc"))
25 ctx.cfg.root_desc = xstrdup(value); 48 ctx.cfg.root_desc = xstrdup(value);
26 else if (!strcmp(name, "root-readme")) 49 else if (!strcmp(name, "root-readme"))
27 ctx.cfg.root_readme = xstrdup(value); 50 ctx.cfg.root_readme = xstrdup(value);
28 else if (!strcmp(name, "css")) 51 else if (!strcmp(name, "css"))
29 ctx.cfg.css = xstrdup(value); 52 ctx.cfg.css = xstrdup(value);
30 else if (!strcmp(name, "favicon")) 53 else if (!strcmp(name, "favicon"))
31 ctx.cfg.favicon = xstrdup(value); 54 ctx.cfg.favicon = xstrdup(value);
32 else if (!strcmp(name, "footer")) 55 else if (!strcmp(name, "footer"))
33 ctx.cfg.footer = xstrdup(value); 56 ctx.cfg.footer = xstrdup(value);
57 else if (!strcmp(name, "head-include"))
58 ctx.cfg.head_include = xstrdup(value);
34 else if (!strcmp(name, "header")) 59 else if (!strcmp(name, "header"))
35 ctx.cfg.header = xstrdup(value); 60 ctx.cfg.header = xstrdup(value);
36 else if (!strcmp(name, "logo")) 61 else if (!strcmp(name, "logo"))
37 ctx.cfg.logo = xstrdup(value); 62 ctx.cfg.logo = xstrdup(value);
38 else if (!strcmp(name, "index-header")) 63 else if (!strcmp(name, "index-header"))
39 ctx.cfg.index_header = xstrdup(value); 64 ctx.cfg.index_header = xstrdup(value);
40 else if (!strcmp(name, "index-info")) 65 else if (!strcmp(name, "index-info"))
41 ctx.cfg.index_info = xstrdup(value); 66 ctx.cfg.index_info = xstrdup(value);
42 else if (!strcmp(name, "logo-link")) 67 else if (!strcmp(name, "logo-link"))
43 ctx.cfg.logo_link = xstrdup(value); 68 ctx.cfg.logo_link = xstrdup(value);
44 else if (!strcmp(name, "module-link")) 69 else if (!strcmp(name, "module-link"))
45 ctx.cfg.module_link = xstrdup(value); 70 ctx.cfg.module_link = xstrdup(value);
46 else if (!strcmp(name, "virtual-root")) { 71 else if (!strcmp(name, "virtual-root")) {
47 ctx.cfg.virtual_root = trim_end(value, '/'); 72 ctx.cfg.virtual_root = trim_end(value, '/');
48 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 73 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
49 ctx.cfg.virtual_root = ""; 74 ctx.cfg.virtual_root = "";
50 } else if (!strcmp(name, "nocache")) 75 } else if (!strcmp(name, "nocache"))
51 ctx.cfg.nocache = atoi(value); 76 ctx.cfg.nocache = atoi(value);
77 else if (!strcmp(name, "noplainemail"))
78 ctx.cfg.noplainemail = atoi(value);
79 else if (!strcmp(name, "noheader"))
80 ctx.cfg.noheader = atoi(value);
52 else if (!strcmp(name, "snapshots")) 81 else if (!strcmp(name, "snapshots"))
53 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 82 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
54 else if (!strcmp(name, "enable-index-links")) 83 else if (!strcmp(name, "enable-index-links"))
55 ctx.cfg.enable_index_links = atoi(value); 84 ctx.cfg.enable_index_links = atoi(value);
56 else if (!strcmp(name, "enable-log-filecount")) 85 else if (!strcmp(name, "enable-log-filecount"))
57 ctx.cfg.enable_log_filecount = atoi(value); 86 ctx.cfg.enable_log_filecount = atoi(value);
58 else if (!strcmp(name, "enable-log-linecount")) 87 else if (!strcmp(name, "enable-log-linecount"))
59 ctx.cfg.enable_log_linecount = atoi(value); 88 ctx.cfg.enable_log_linecount = atoi(value);
60 else if (!strcmp(name, "max-stats")) 89 else if (!strcmp(name, "max-stats"))
61 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 90 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
62 else if (!strcmp(name, "cache-size")) 91 else if (!strcmp(name, "cache-size"))
63 ctx.cfg.cache_size = atoi(value); 92 ctx.cfg.cache_size = atoi(value);
64 else if (!strcmp(name, "cache-root")) 93 else if (!strcmp(name, "cache-root"))
65 ctx.cfg.cache_root = xstrdup(value); 94 ctx.cfg.cache_root = xstrdup(value);
66 else if (!strcmp(name, "cache-root-ttl")) 95 else if (!strcmp(name, "cache-root-ttl"))
67 ctx.cfg.cache_root_ttl = atoi(value); 96 ctx.cfg.cache_root_ttl = atoi(value);
68 else if (!strcmp(name, "cache-repo-ttl")) 97 else if (!strcmp(name, "cache-repo-ttl"))
69 ctx.cfg.cache_repo_ttl = atoi(value); 98 ctx.cfg.cache_repo_ttl = atoi(value);
70 else if (!strcmp(name, "cache-static-ttl")) 99 else if (!strcmp(name, "cache-static-ttl"))
71 ctx.cfg.cache_static_ttl = atoi(value); 100 ctx.cfg.cache_static_ttl = atoi(value);
72 else if (!strcmp(name, "cache-dynamic-ttl")) 101 else if (!strcmp(name, "cache-dynamic-ttl"))
73 ctx.cfg.cache_dynamic_ttl = atoi(value); 102 ctx.cfg.cache_dynamic_ttl = atoi(value);
103 else if (!strcmp(name, "about-filter"))
104 ctx.cfg.about_filter = new_filter(value, 0);
105 else if (!strcmp(name, "commit-filter"))
106 ctx.cfg.commit_filter = new_filter(value, 0);
107 else if (!strcmp(name, "embedded"))
108 ctx.cfg.embedded = atoi(value);
74 else if (!strcmp(name, "max-message-length")) 109 else if (!strcmp(name, "max-message-length"))
75 ctx.cfg.max_msg_len = atoi(value); 110 ctx.cfg.max_msg_len = atoi(value);
76 else if (!strcmp(name, "max-repodesc-length")) 111 else if (!strcmp(name, "max-repodesc-length"))
77 ctx.cfg.max_repodesc_len = atoi(value); 112 ctx.cfg.max_repodesc_len = atoi(value);
78 else if (!strcmp(name, "max-repo-count")) 113 else if (!strcmp(name, "max-repo-count"))
79 ctx.cfg.max_repo_count = atoi(value); 114 ctx.cfg.max_repo_count = atoi(value);
80 else if (!strcmp(name, "max-commit-count")) 115 else if (!strcmp(name, "max-commit-count"))
81 ctx.cfg.max_commit_count = atoi(value); 116 ctx.cfg.max_commit_count = atoi(value);
117 else if (!strcmp(name, "source-filter"))
118 ctx.cfg.source_filter = new_filter(value, 1);
82 else if (!strcmp(name, "summary-log")) 119 else if (!strcmp(name, "summary-log"))
83 ctx.cfg.summary_log = atoi(value); 120 ctx.cfg.summary_log = atoi(value);
84 else if (!strcmp(name, "summary-branches")) 121 else if (!strcmp(name, "summary-branches"))
85 ctx.cfg.summary_branches = atoi(value); 122 ctx.cfg.summary_branches = atoi(value);
86 else if (!strcmp(name, "summary-tags")) 123 else if (!strcmp(name, "summary-tags"))
87 ctx.cfg.summary_tags = atoi(value); 124 ctx.cfg.summary_tags = atoi(value);
88 else if (!strcmp(name, "agefile")) 125 else if (!strcmp(name, "agefile"))
89 ctx.cfg.agefile = xstrdup(value); 126 ctx.cfg.agefile = xstrdup(value);
90 else if (!strcmp(name, "renamelimit")) 127 else if (!strcmp(name, "renamelimit"))
91 ctx.cfg.renamelimit = atoi(value); 128 ctx.cfg.renamelimit = atoi(value);
92 else if (!strcmp(name, "robots")) 129 else if (!strcmp(name, "robots"))
93 ctx.cfg.robots = xstrdup(value); 130 ctx.cfg.robots = xstrdup(value);
94 else if (!strcmp(name, "clone-prefix")) 131 else if (!strcmp(name, "clone-prefix"))
95 ctx.cfg.clone_prefix = xstrdup(value); 132 ctx.cfg.clone_prefix = xstrdup(value);
96 else if (!strcmp(name, "local-time")) 133 else if (!strcmp(name, "local-time"))
97 ctx.cfg.local_time = atoi(value); 134 ctx.cfg.local_time = atoi(value);
135 else if (!prefixcmp(name, "mimetype."))
136 add_mimetype(name + 9, value);
98 else if (!strcmp(name, "repo.group")) 137 else if (!strcmp(name, "repo.group"))
99 ctx.cfg.repo_group = xstrdup(value); 138 ctx.cfg.repo_group = xstrdup(value);
100 else if (!strcmp(name, "repo.url")) 139 else if (!strcmp(name, "repo.url"))
101 ctx.repo = cgit_add_repo(value); 140 ctx.repo = cgit_add_repo(value);
102 else if (!strcmp(name, "repo.name")) 141 else if (!strcmp(name, "repo.name"))
103 ctx.repo->name = xstrdup(value); 142 ctx.repo->name = xstrdup(value);
104 else if (ctx.repo && !strcmp(name, "repo.path")) 143 else if (ctx.repo && !strcmp(name, "repo.path"))
105 ctx.repo->path = trim_end(value, '/'); 144 ctx.repo->path = trim_end(value, '/');
106 else if (ctx.repo && !strcmp(name, "repo.clone-url")) 145 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
107 ctx.repo->clone_url = xstrdup(value); 146 ctx.repo->clone_url = xstrdup(value);
108 else if (ctx.repo && !strcmp(name, "repo.desc")) 147 else if (ctx.repo && !strcmp(name, "repo.desc"))
109 ctx.repo->desc = xstrdup(value); 148 ctx.repo->desc = xstrdup(value);
@@ -112,24 +151,30 @@ void config_cb(const char *name, const char *value)
112 else if (ctx.repo && !strcmp(name, "repo.defbranch")) 151 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
113 ctx.repo->defbranch = xstrdup(value); 152 ctx.repo->defbranch = xstrdup(value);
114 else if (ctx.repo && !strcmp(name, "repo.snapshots")) 153 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
115 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ 154 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
116 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) 155 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
117 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 156 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
118 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) 157 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
119 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 158 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
120 else if (ctx.repo && !strcmp(name, "repo.max-stats")) 159 else if (ctx.repo && !strcmp(name, "repo.max-stats"))
121 ctx.repo->max_stats = cgit_find_stats_period(value, NULL); 160 ctx.repo->max_stats = cgit_find_stats_period(value, NULL);
122 else if (ctx.repo && !strcmp(name, "repo.module-link")) 161 else if (ctx.repo && !strcmp(name, "repo.module-link"))
123 ctx.repo->module_link= xstrdup(value); 162 ctx.repo->module_link= xstrdup(value);
163 else if (ctx.repo && !strcmp(name, "repo.about-filter"))
164 ctx.repo->about_filter = new_filter(value, 0);
165 else if (ctx.repo && !strcmp(name, "repo.commit-filter"))
166 ctx.repo->commit_filter = new_filter(value, 0);
167 else if (ctx.repo && !strcmp(name, "repo.source-filter"))
168 ctx.repo->source_filter = new_filter(value, 1);
124 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 169 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
125 if (*value == '/') 170 if (*value == '/')
126 ctx.repo->readme = xstrdup(value); 171 ctx.repo->readme = xstrdup(value);
127 else 172 else
128 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 173 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
129 } else if (!strcmp(name, "include")) 174 } else if (!strcmp(name, "include"))
130 parse_configfile(value, config_cb); 175 parse_configfile(value, config_cb);
131} 176}
132 177
133static void querystring_cb(const char *name, const char *value) 178static void querystring_cb(const char *name, const char *value)
134{ 179{
135 if (!value) 180 if (!value)
@@ -164,60 +209,83 @@ static void querystring_cb(const char *name, const char *value)
164 ctx.qry.name = xstrdup(value); 209 ctx.qry.name = xstrdup(value);
165 } else if (!strcmp(name, "mimetype")) { 210 } else if (!strcmp(name, "mimetype")) {
166 ctx.qry.mimetype = xstrdup(value); 211 ctx.qry.mimetype = xstrdup(value);
167 } else if (!strcmp(name, "s")){ 212 } else if (!strcmp(name, "s")){
168 ctx.qry.sort = xstrdup(value); 213 ctx.qry.sort = xstrdup(value);
169 } else if (!strcmp(name, "showmsg")) { 214 } else if (!strcmp(name, "showmsg")) {
170 ctx.qry.showmsg = atoi(value); 215 ctx.qry.showmsg = atoi(value);
171 } else if (!strcmp(name, "period")) { 216 } else if (!strcmp(name, "period")) {
172 ctx.qry.period = xstrdup(value); 217 ctx.qry.period = xstrdup(value);
173 } 218 }
174} 219}
175 220
221char *xstrdupn(const char *str)
222{
223 return (str ? xstrdup(str) : NULL);
224}
225
176static void prepare_context(struct cgit_context *ctx) 226static void prepare_context(struct cgit_context *ctx)
177{ 227{
178 memset(ctx, 0, sizeof(ctx)); 228 memset(ctx, 0, sizeof(ctx));
179 ctx->cfg.agefile = "info/web/last-modified"; 229 ctx->cfg.agefile = "info/web/last-modified";
180 ctx->cfg.nocache = 0; 230 ctx->cfg.nocache = 0;
181 ctx->cfg.cache_size = 0; 231 ctx->cfg.cache_size = 0;
182 ctx->cfg.cache_dynamic_ttl = 5; 232 ctx->cfg.cache_dynamic_ttl = 5;
183 ctx->cfg.cache_max_create_time = 5; 233 ctx->cfg.cache_max_create_time = 5;
184 ctx->cfg.cache_repo_ttl = 5; 234 ctx->cfg.cache_repo_ttl = 5;
185 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 235 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
186 ctx->cfg.cache_root_ttl = 5; 236 ctx->cfg.cache_root_ttl = 5;
187 ctx->cfg.cache_static_ttl = -1; 237 ctx->cfg.cache_static_ttl = -1;
188 ctx->cfg.css = "/cgit.css"; 238 ctx->cfg.css = "/cgit.css";
189 ctx->cfg.logo = "/git-logo.png"; 239 ctx->cfg.logo = "/cgit.png";
190 ctx->cfg.local_time = 0; 240 ctx->cfg.local_time = 0;
191 ctx->cfg.max_repo_count = 50; 241 ctx->cfg.max_repo_count = 50;
192 ctx->cfg.max_commit_count = 50; 242 ctx->cfg.max_commit_count = 50;
193 ctx->cfg.max_lock_attempts = 5; 243 ctx->cfg.max_lock_attempts = 5;
194 ctx->cfg.max_msg_len = 80; 244 ctx->cfg.max_msg_len = 80;
195 ctx->cfg.max_repodesc_len = 80; 245 ctx->cfg.max_repodesc_len = 80;
196 ctx->cfg.max_stats = 0; 246 ctx->cfg.max_stats = 0;
197 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 247 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
198 ctx->cfg.renamelimit = -1; 248 ctx->cfg.renamelimit = -1;
199 ctx->cfg.robots = "index, nofollow"; 249 ctx->cfg.robots = "index, nofollow";
200 ctx->cfg.root_title = "Git repository browser"; 250 ctx->cfg.root_title = "Git repository browser";
201 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 251 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
202 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 252 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
203 ctx->cfg.summary_branches = 10; 253 ctx->cfg.summary_branches = 10;
204 ctx->cfg.summary_log = 10; 254 ctx->cfg.summary_log = 10;
205 ctx->cfg.summary_tags = 10; 255 ctx->cfg.summary_tags = 10;
256 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
257 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
258 ctx->env.https = xstrdupn(getenv("HTTPS"));
259 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
260 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
261 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
262 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
263 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
264 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
265 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
206 ctx->page.mimetype = "text/html"; 266 ctx->page.mimetype = "text/html";
207 ctx->page.charset = PAGE_ENCODING; 267 ctx->page.charset = PAGE_ENCODING;
208 ctx->page.filename = NULL; 268 ctx->page.filename = NULL;
209 ctx->page.size = 0; 269 ctx->page.size = 0;
210 ctx->page.modified = time(NULL); 270 ctx->page.modified = time(NULL);
211 ctx->page.expires = ctx->page.modified; 271 ctx->page.expires = ctx->page.modified;
272 ctx->page.etag = NULL;
273 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
274 if (ctx->env.script_name)
275 ctx->cfg.script_name = ctx->env.script_name;
276 if (ctx->env.query_string)
277 ctx->qry.raw = ctx->env.query_string;
278 if (!ctx->env.cgit_config)
279 ctx->env.cgit_config = CGIT_CONFIG;
212} 280}
213 281
214struct refmatch { 282struct refmatch {
215 char *req_ref; 283 char *req_ref;
216 char *first_ref; 284 char *first_ref;
217 int match; 285 int match;
218}; 286};
219 287
220int find_current_ref(const char *refname, const unsigned char *sha1, 288int find_current_ref(const char *refname, const unsigned char *sha1,
221 int flags, void *cb_data) 289 int flags, void *cb_data)
222{ 290{
223 struct refmatch *info; 291 struct refmatch *info;
@@ -279,24 +347,26 @@ static int prepare_repo_cmd(struct cgit_context *ctx)
279 if (!ctx->qry.head) { 347 if (!ctx->qry.head) {
280 cgit_print_http_headers(ctx); 348 cgit_print_http_headers(ctx);
281 cgit_print_docstart(ctx); 349 cgit_print_docstart(ctx);
282 cgit_print_pageheader(ctx); 350 cgit_print_pageheader(ctx);
283 cgit_print_error("Repository seems to be empty"); 351 cgit_print_error("Repository seems to be empty");
284 cgit_print_docend(); 352 cgit_print_docend();
285 return 1; 353 return 1;
286 } 354 }
287 355
288 if (get_sha1(ctx->qry.head, sha1)) { 356 if (get_sha1(ctx->qry.head, sha1)) {
289 tmp = xstrdup(ctx->qry.head); 357 tmp = xstrdup(ctx->qry.head);
290 ctx->qry.head = ctx->repo->defbranch; 358 ctx->qry.head = ctx->repo->defbranch;
359 ctx->page.status = 404;
360 ctx->page.statusmsg = "not found";
291 cgit_print_http_headers(ctx); 361 cgit_print_http_headers(ctx);
292 cgit_print_docstart(ctx); 362 cgit_print_docstart(ctx);
293 cgit_print_pageheader(ctx); 363 cgit_print_pageheader(ctx);
294 cgit_print_error(fmt("Invalid branch: %s", tmp)); 364 cgit_print_error(fmt("Invalid branch: %s", tmp));
295 cgit_print_docend(); 365 cgit_print_docend();
296 return 1; 366 return 1;
297 } 367 }
298 return 0; 368 return 0;
299} 369}
300 370
301static void process_request(void *cbdata) 371static void process_request(void *cbdata)
302{ 372{
@@ -370,24 +440,27 @@ void print_repolist(struct cgit_repolist *list)
370static void cgit_parse_args(int argc, const char **argv) 440static void cgit_parse_args(int argc, const char **argv)
371{ 441{
372 int i; 442 int i;
373 int scan = 0; 443 int scan = 0;
374 444
375 for (i = 1; i < argc; i++) { 445 for (i = 1; i < argc; i++) {
376 if (!strncmp(argv[i], "--cache=", 8)) { 446 if (!strncmp(argv[i], "--cache=", 8)) {
377 ctx.cfg.cache_root = xstrdup(argv[i]+8); 447 ctx.cfg.cache_root = xstrdup(argv[i]+8);
378 } 448 }
379 if (!strcmp(argv[i], "--nocache")) { 449 if (!strcmp(argv[i], "--nocache")) {
380 ctx.cfg.nocache = 1; 450 ctx.cfg.nocache = 1;
381 } 451 }
452 if (!strcmp(argv[i], "--nohttp")) {
453 ctx.env.no_http = "1";
454 }
382 if (!strncmp(argv[i], "--query=", 8)) { 455 if (!strncmp(argv[i], "--query=", 8)) {
383 ctx.qry.raw = xstrdup(argv[i]+8); 456 ctx.qry.raw = xstrdup(argv[i]+8);
384 } 457 }
385 if (!strncmp(argv[i], "--repo=", 7)) { 458 if (!strncmp(argv[i], "--repo=", 7)) {
386 ctx.qry.repo = xstrdup(argv[i]+7); 459 ctx.qry.repo = xstrdup(argv[i]+7);
387 } 460 }
388 if (!strncmp(argv[i], "--page=", 7)) { 461 if (!strncmp(argv[i], "--page=", 7)) {
389 ctx.qry.page = xstrdup(argv[i]+7); 462 ctx.qry.page = xstrdup(argv[i]+7);
390 } 463 }
391 if (!strncmp(argv[i], "--head=", 7)) { 464 if (!strncmp(argv[i], "--head=", 7)) {
392 ctx.qry.head = xstrdup(argv[i]+7); 465 ctx.qry.head = xstrdup(argv[i]+7);
393 ctx.qry.has_symref = 1; 466 ctx.qry.has_symref = 1;
@@ -422,68 +495,64 @@ static int calc_ttl()
422 495
423 if (ctx.qry.has_symref) 496 if (ctx.qry.has_symref)
424 return ctx.cfg.cache_dynamic_ttl; 497 return ctx.cfg.cache_dynamic_ttl;
425 498
426 if (ctx.qry.has_sha1) 499 if (ctx.qry.has_sha1)
427 return ctx.cfg.cache_static_ttl; 500 return ctx.cfg.cache_static_ttl;
428 501
429 return ctx.cfg.cache_repo_ttl; 502 return ctx.cfg.cache_repo_ttl;
430} 503}
431 504
432int main(int argc, const char **argv) 505int main(int argc, const char **argv)
433{ 506{
434 const char *cgit_config_env = getenv("CGIT_CONFIG");
435 const char *path; 507 const char *path;
436 char *qry; 508 char *qry;
437 int err, ttl; 509 int err, ttl;
438 510
439 prepare_context(&ctx); 511 prepare_context(&ctx);
440 cgit_repolist.length = 0; 512 cgit_repolist.length = 0;
441 cgit_repolist.count = 0; 513 cgit_repolist.count = 0;
442 cgit_repolist.repos = NULL; 514 cgit_repolist.repos = NULL;
443 515
444 if (getenv("SCRIPT_NAME"))
445 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME"));
446 if (getenv("QUERY_STRING"))
447 ctx.qry.raw = xstrdup(getenv("QUERY_STRING"));
448 cgit_parse_args(argc, argv); 516 cgit_parse_args(argc, argv);
449 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG, 517 parse_configfile(ctx.env.cgit_config, config_cb);
450 config_cb);
451 ctx.repo = NULL; 518 ctx.repo = NULL;
452 http_parse_querystring(ctx.qry.raw, querystring_cb); 519 http_parse_querystring(ctx.qry.raw, querystring_cb);
453 520
454 /* If virtual-root isn't specified in cgitrc, lets pretend 521 /* If virtual-root isn't specified in cgitrc, lets pretend
455 * that virtual-root equals SCRIPT_NAME. 522 * that virtual-root equals SCRIPT_NAME.
456 */ 523 */
457 if (!ctx.cfg.virtual_root) 524 if (!ctx.cfg.virtual_root)
458 ctx.cfg.virtual_root = ctx.cfg.script_name; 525 ctx.cfg.virtual_root = ctx.cfg.script_name;
459 526
460 /* If no url parameter is specified on the querystring, lets 527 /* If no url parameter is specified on the querystring, lets
461 * use PATH_INFO as url. This allows cgit to work with virtual 528 * use PATH_INFO as url. This allows cgit to work with virtual
462 * urls without the need for rewriterules in the webserver (as 529 * urls without the need for rewriterules in the webserver (as
463 * long as PATH_INFO is included in the cache lookup key). 530 * long as PATH_INFO is included in the cache lookup key).
464 */ 531 */
465 path = getenv("PATH_INFO"); 532 path = ctx.env.path_info;
466 if (!ctx.qry.url && path) { 533 if (!ctx.qry.url && path) {
467 if (path[0] == '/') 534 if (path[0] == '/')
468 path++; 535 path++;
469 ctx.qry.url = xstrdup(path); 536 ctx.qry.url = xstrdup(path);
470 if (ctx.qry.raw) { 537 if (ctx.qry.raw) {
471 qry = ctx.qry.raw; 538 qry = ctx.qry.raw;
472 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 539 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
473 free(qry); 540 free(qry);
474 } else 541 } else
475 ctx.qry.raw = ctx.qry.url; 542 ctx.qry.raw = ctx.qry.url;
476 cgit_parse_url(ctx.qry.url); 543 cgit_parse_url(ctx.qry.url);
477 } 544 }
478 545
479 ttl = calc_ttl(); 546 ttl = calc_ttl();
480 ctx.page.expires += ttl*60; 547 ctx.page.expires += ttl*60;
548 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
549 ctx.cfg.nocache = 1;
481 if (ctx.cfg.nocache) 550 if (ctx.cfg.nocache)
482 ctx.cfg.cache_size = 0; 551 ctx.cfg.cache_size = 0;
483 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 552 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
484 ctx.qry.raw, ttl, process_request, &ctx); 553 ctx.qry.raw, ttl, process_request, &ctx);
485 if (err) 554 if (err)
486 cgit_print_error(fmt("Error processing page: %s (%d)", 555 cgit_print_error(fmt("Error processing page: %s (%d)",
487 strerror(err), err)); 556 strerror(err), err));
488 return err; 557 return err;
489} 558}
diff --git a/cgit.css b/cgit.css
index adfc8ae..e3b32e7 100644
--- a/cgit.css
+++ b/cgit.css
@@ -146,25 +146,25 @@ table.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;
diff --git a/cgit.h b/cgit.h
index 5f7af51..d90ccdc 100644
--- a/cgit.h
+++ b/cgit.h
@@ -6,24 +6,25 @@
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
@@ -39,40 +40,52 @@
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; 68 char *group;
59 char *module_link; 69 char *module_link;
60 char *readme; 70 char *readme;
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
69struct cgit_repolist { 82struct cgit_repolist {
70 int length; 83 int length;
71 int count; 84 int count;
72 struct cgit_repo *repos; 85 struct cgit_repo *repos;
73}; 86};
74 87
75struct commitinfo { 88struct commitinfo {
76 struct commit *commit; 89 struct commit *commit;
77 char *author; 90 char *author;
78 char *author_email; 91 char *author_email;
@@ -127,72 +140,97 @@ struct cgit_query {
127 int nohead; 140 int nohead;
128 char *sort; 141 char *sort;
129 int showmsg; 142 int showmsg;
130}; 143};
131 144
132struct cgit_config { 145struct cgit_config {
133 char *agefile; 146 char *agefile;
134 char *cache_root; 147 char *cache_root;
135 char *clone_prefix; 148 char *clone_prefix;
136 char *css; 149 char *css;
137 char *favicon; 150 char *favicon;
138 char *footer; 151 char *footer;
152 char *head_include;
139 char *header; 153 char *header;
140 char *index_header; 154 char *index_header;
141 char *index_info; 155 char *index_info;
142 char *logo; 156 char *logo;
143 char *logo_link; 157 char *logo_link;
144 char *module_link; 158 char *module_link;
145 char *repo_group; 159 char *repo_group;
146 char *robots; 160 char *robots;
147 char *root_title; 161 char *root_title;
148 char *root_desc; 162 char *root_desc;
149 char *root_readme; 163 char *root_readme;
150 char *script_name; 164 char *script_name;
151 char *virtual_root; 165 char *virtual_root;
152 int cache_size; 166 int cache_size;
153 int cache_dynamic_ttl; 167 int cache_dynamic_ttl;
154 int cache_max_create_time; 168 int cache_max_create_time;
155 int cache_repo_ttl; 169 int cache_repo_ttl;
156 int cache_root_ttl; 170 int cache_root_ttl;
157 int cache_static_ttl; 171 int cache_static_ttl;
172 int embedded;
158 int enable_index_links; 173 int enable_index_links;
159 int enable_log_filecount; 174 int enable_log_filecount;
160 int enable_log_linecount; 175 int enable_log_linecount;
161 int local_time; 176 int local_time;
162 int max_repo_count; 177 int max_repo_count;
163 int max_commit_count; 178 int max_commit_count;
164 int max_lock_attempts; 179 int max_lock_attempts;
165 int max_msg_len; 180 int max_msg_len;
166 int max_repodesc_len; 181 int max_repodesc_len;
167 int max_stats; 182 int max_stats;
168 int nocache; 183 int nocache;
184 int noplainemail;
185 int noheader;
169 int renamelimit; 186 int renamelimit;
170 int snapshots; 187 int snapshots;
171 int summary_branches; 188 int summary_branches;
172 int summary_log; 189 int summary_log;
173 int summary_tags; 190 int summary_tags;
191 struct string_list mimetypes;
192 struct cgit_filter *about_filter;
193 struct cgit_filter *commit_filter;
194 struct cgit_filter *source_filter;
174}; 195};
175 196
176struct cgit_page { 197struct cgit_page {
177 time_t modified; 198 time_t modified;
178 time_t expires; 199 time_t expires;
179 size_t size; 200 size_t size;
180 char *mimetype; 201 char *mimetype;
181 char *charset; 202 char *charset;
182 char *filename; 203 char *filename;
204 char *etag;
183 char *title; 205 char *title;
206 int status;
207 char *statusmsg;
208};
209
210struct cgit_environment {
211 char *cgit_config;
212 char *http_host;
213 char *https;
214 char *no_http;
215 char *path_info;
216 char *query_string;
217 char *request_method;
218 char *script_name;
219 char *server_name;
220 char *server_port;
184}; 221};
185 222
186struct cgit_context { 223struct cgit_context {
224 struct cgit_environment env;
187 struct cgit_query qry; 225 struct cgit_query qry;
188 struct cgit_config cfg; 226 struct cgit_config cfg;
189 struct cgit_repo *repo; 227 struct cgit_repo *repo;
190 struct cgit_page page; 228 struct cgit_page page;
191}; 229};
192 230
193struct cgit_snapshot_format { 231struct cgit_snapshot_format {
194 const char *suffix; 232 const char *suffix;
195 const char *mimetype; 233 const char *mimetype;
196 write_archive_fn_t write_func; 234 write_archive_fn_t write_func;
197 int bit; 235 int bit;
198}; 236};
@@ -233,14 +271,17 @@ extern void cgit_diff_tree(const unsigned char *old_sha1,
233extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 271extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
234 272
235extern char *fmt(const char *format,...); 273extern char *fmt(const char *format,...);
236 274
237extern struct commitinfo *cgit_parse_commit(struct commit *commit); 275extern struct commitinfo *cgit_parse_commit(struct commit *commit);
238extern struct taginfo *cgit_parse_tag(struct tag *tag); 276extern struct taginfo *cgit_parse_tag(struct tag *tag);
239extern void cgit_parse_url(const char *url); 277extern void cgit_parse_url(const char *url);
240 278
241extern const char *cgit_repobasename(const char *reponame); 279extern const char *cgit_repobasename(const char *reponame);
242 280
243extern int cgit_parse_snapshots_mask(const char *str); 281extern int cgit_parse_snapshots_mask(const char *str);
244 282
283extern int cgit_open_filter(struct cgit_filter *filter);
284extern int cgit_close_filter(struct cgit_filter *filter);
285
245 286
246#endif /* CGIT_H */ 287#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index fd299ae..3c35b02 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,273 +1,332 @@
1CGITRC 1CGITRC(5)
2====== 2========
3 3
4 4
5NAME 5NAME
6---- 6----
7 cgitrc - runtime configuration for cgit 7cgitrc - runtime configuration for cgit
8 8
9 9
10DESCRIPTION 10SYNOPSIS
11----------- 11--------
12Cgitrc contains all runtime settings for cgit, including the list of git 12Cgitrc contains all runtime settings for cgit, including the list of git
13repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank 13repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
14lines, and lines starting with '#', are ignored. 14lines, and lines starting with '#', are ignored.
15 15
16 16
17LOCATION
18--------
19The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
20runtime, cgit will consult the environment variable CGIT_CONFIG and, if
21defined, use its value instead.
22
23
17GLOBAL SETTINGS 24GLOBAL SETTINGS
18--------------- 25---------------
19agefile 26about-filter::
27 Specifies a command which will be invoked to format the content of
28 about pages (both top-level and for each repository). The command will
29 get the content of the about-file on its STDIN, and the STDOUT from the
30 command will be included verbatim on the about page. Default value:
31 none.
32
33agefile::
20 Specifies a path, relative to each repository path, which can be used 34 Specifies a path, relative to each repository path, which can be used
21 to specify the date and time of the youngest commit in the repository. 35 to specify the date and time of the youngest commit in the repository.
22 The first line in the file is used as input to the "parse_date" 36 The first line in the file is used as input to the "parse_date"
23 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 37 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
24 hh:mm:ss". Default value: "info/web/last-modified". 38 hh:mm:ss". Default value: "info/web/last-modified".
25 39
26cache-root 40cache-root::
27 Path used to store the cgit cache entries. Default value: 41 Path used to store the cgit cache entries. Default value:
28 "/var/cache/cgit". 42 "/var/cache/cgit".
29 43
30cache-dynamic-ttl 44cache-dynamic-ttl::
31 Number which specifies the time-to-live, in minutes, for the cached 45 Number which specifies the time-to-live, in minutes, for the cached
32 version of repository pages accessed without a fixed SHA1. Default 46 version of repository pages accessed without a fixed SHA1. Default
33 value: "5". 47 value: "5".
34 48
35cache-repo-ttl 49cache-repo-ttl::
36 Number which specifies the time-to-live, in minutes, for the cached 50 Number which specifies the time-to-live, in minutes, for the cached
37 version of the repository summary page. Default value: "5". 51 version of the repository summary page. Default value: "5".
38 52
39cache-root-ttl 53cache-root-ttl::
40 Number which specifies the time-to-live, in minutes, for the cached 54 Number which specifies the time-to-live, in minutes, for the cached
41 version of the repository index page. Default value: "5". 55 version of the repository index page. Default value: "5".
42 56
43cache-size 57cache-size::
44 The maximum number of entries in the cgit cache. Default value: "0" 58 The maximum number of entries in the cgit cache. Default value: "0"
45 (i.e. caching is disabled). 59 (i.e. caching is disabled).
46 60
47cache-static-ttl 61cache-static-ttl::
48 Number which specifies the time-to-live, in minutes, for the cached 62 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed with a fixed SHA1. Default value: 63 version of repository pages accessed with a fixed SHA1. Default value:
50 "5". 64 "5".
51 65
52clone-prefix 66clone-prefix::
53 Space-separated list of common prefixes which, when combined with a 67 Space-separated list of common prefixes which, when combined with a
54 repository url, generates valid clone urls for the repository. This 68 repository url, generates valid clone urls for the repository. This
55 setting is only used if `repo.clone-url` is unspecified. Default value: 69 setting is only used if `repo.clone-url` is unspecified. Default value:
56 none. 70 none.
57 71
58css 72commit-filter::
73 Specifies a command which will be invoked to format commit messages.
74 The command will get the message on its STDIN, and the STDOUT from the
75 command will be included verbatim as the commit message, i.e. this can
76 be used to implement bugtracker integration. Default value: none.
77
78css::
59 Url which specifies the css document to include in all cgit pages. 79 Url which specifies the css document to include in all cgit pages.
60 Default value: "/cgit.css". 80 Default value: "/cgit.css".
61 81
62enable-index-links 82embedded::
83 Flag which, when set to "1", will make cgit generate a html fragment
84 suitable for embedding in other html pages. Default value: none. See
85 also: "noheader".
86
87enable-index-links::
63 Flag which, when set to "1", will make cgit generate extra links for 88 Flag which, when set to "1", will make cgit generate extra links for
64 each repo in the repository index (specifically, to the "summary", 89 each repo in the repository index (specifically, to the "summary",
65 "commit" and "tree" pages). Default value: "0". 90 "commit" and "tree" pages). Default value: "0".
66 91
67enable-log-filecount 92enable-log-filecount::
68 Flag which, when set to "1", will make cgit print the number of 93 Flag which, when set to "1", will make cgit print the number of
69 modified files for each commit on the repository log page. Default 94 modified files for each commit on the repository log page. Default
70 value: "0". 95 value: "0".
71 96
72enable-log-linecount 97enable-log-linecount::
73 Flag which, when set to "1", will make cgit print the number of added 98 Flag which, when set to "1", will make cgit print the number of added
74 and removed lines for each commit on the repository log page. Default 99 and removed lines for each commit on the repository log page. Default
75 value: "0". 100 value: "0".
76 101
77favicon 102favicon::
78 Url used as link to a shortcut icon for cgit. If specified, it is 103 Url used as link to a shortcut icon for cgit. If specified, it is
79 suggested to use the value "/favicon.ico" since certain browsers will 104 suggested to use the value "/favicon.ico" since certain browsers will
80 ignore other values. Default value: none. 105 ignore other values. Default value: none.
81 106
82footer 107footer::
83 The content of the file specified with this option will be included 108 The content of the file specified with this option will be included
84 verbatim at the bottom of all pages (i.e. it replaces the standard 109 verbatim at the bottom of all pages (i.e. it replaces the standard
85 "generated by..." message. Default value: none. 110 "generated by..." message. Default value: none.
86 111
87header 112head-include::
113 The content of the file specified with this option will be included
114 verbatim in the html HEAD section on all pages. Default value: none.
115
116header::
88 The content of the file specified with this option will be included 117 The content of the file specified with this option will be included
89 verbatim at the top of all pages. Default value: none. 118 verbatim at the top of all pages. Default value: none.
90 119
91include 120include::
92 Name of a configfile to include before the rest of the current config- 121 Name of a configfile to include before the rest of the current config-
93 file is parsed. Default value: none. 122 file is parsed. Default value: none.
94 123
95index-header 124index-header::
96 The content of the file specified with this option will be included 125 The content of the file specified with this option will be included
97 verbatim above the repository index. This setting is deprecated, and 126 verbatim above the repository index. This setting is deprecated, and
98 will not be supported by cgit-1.0 (use root-readme instead). Default 127 will not be supported by cgit-1.0 (use root-readme instead). Default
99 value: none. 128 value: none.
100 129
101index-info 130index-info::
102 The content of the file specified with this option will be included 131 The content of the file specified with this option will be included
103 verbatim below the heading on the repository index page. This setting 132 verbatim below the heading on the repository index page. This setting
104 is deprecated, and will not be supported by cgit-1.0 (use root-desc 133 is deprecated, and will not be supported by cgit-1.0 (use root-desc
105 instead). Default value: none. 134 instead). Default value: none.
106 135
107local-time 136local-time::
108 Flag which, if set to "1", makes cgit print commit and tag times in the 137 Flag which, if set to "1", makes cgit print commit and tag times in the
109 servers timezone. Default value: "0". 138 servers timezone. Default value: "0".
110 139
111logo 140logo::
112 Url which specifies the source of an image which will be used as a logo 141 Url which specifies the source of an image which will be used as a logo
113 on all cgit pages. 142 on all cgit pages. Default value: "/cgit.png".
114 143
115logo-link 144logo-link::
116 Url loaded when clicking on the cgit logo image. If unspecified the 145 Url loaded when clicking on the cgit logo image. If unspecified the
117 calculated url of the repository index page will be used. Default 146 calculated url of the repository index page will be used. Default
118 value: none. 147 value: none.
119 148
120max-commit-count 149max-commit-count::
121 Specifies the number of entries to list per page in "log" view. Default 150 Specifies the number of entries to list per page in "log" view. Default
122 value: "50". 151 value: "50".
123 152
124max-message-length 153max-message-length::
125 Specifies the maximum number of commit message characters to display in 154 Specifies the maximum number of commit message characters to display in
126 "log" view. Default value: "80". 155 "log" view. Default value: "80".
127 156
128max-repo-count 157max-repo-count::
129 Specifies the number of entries to list per page on therepository 158 Specifies the number of entries to list per page on therepository
130 index page. Default value: "50". 159 index page. Default value: "50".
131 160
132max-repodesc-length 161max-repodesc-length::
133 Specifies the maximum number of repo description characters to display 162 Specifies the maximum number of repo description characters to display
134 on the repository index page. Default value: "80". 163 on the repository index page. Default value: "80".
135 164
136max-stats 165max-stats::
137 Set the default maximum statistics period. Valid values are "week", 166 Set the default maximum statistics period. Valid values are "week",
138 "month", "quarter" and "year". If unspecified, statistics are 167 "month", "quarter" and "year". If unspecified, statistics are
139 disabled. Default value: none. See also: "repo.max-stats". 168 disabled. Default value: none. See also: "repo.max-stats".
140 169
141module-link 170mimetype.<ext>::
171 Set the mimetype for the specified filename extension. This is used
172 by the `plain` command when returning blob content.
173
174module-link::
142 Text which will be used as the formatstring for a hyperlink when a 175 Text which will be used as the formatstring for a hyperlink when a
143 submodule is printed in a directory listing. The arguments for the 176 submodule is printed in a directory listing. The arguments for the
144 formatstring are the path and SHA1 of the submodule commit. Default 177 formatstring are the path and SHA1 of the submodule commit. Default
145 value: "./?repo=%s&page=commit&id=%s" 178 value: "./?repo=%s&page=commit&id=%s"
146 179
147nocache 180nocache::
148 If set to the value "1" caching will be disabled. This settings is 181 If set to the value "1" caching will be disabled. This settings is
149 deprecated, and will not be honored starting with cgit-1.0. Default 182 deprecated, and will not be honored starting with cgit-1.0. Default
150 value: "0". 183 value: "0".
151 184
152renamelimit 185noplainemail::
186 If set to "1" showing full author email adresses will be disabled.
187 Default value: "0".
188
189noheader::
190 Flag which, when set to "1", will make cgit omit the standard header
191 on all pages. Default value: none. See also: "embedded".
192
193renamelimit::
153 Maximum number of files to consider when detecting renames. The value 194 Maximum number of files to consider when detecting renames. The value
154 "-1" uses the compiletime value in git (for further info, look at 195 "-1" uses the compiletime value in git (for further info, look at
155 `man git-diff`). Default value: "-1". 196 `man git-diff`). Default value: "-1".
156 197
157repo.group 198repo.group::
158 A value for the current repository group, which all repositories 199 A value for the current repository group, which all repositories
159 specified after this setting will inherit. Default value: none. 200 specified after this setting will inherit. Default value: none.
160 201
161robots 202robots::
162 Text used as content for the "robots" meta-tag. Default value: 203 Text used as content for the "robots" meta-tag. Default value:
163 "index, nofollow". 204 "index, nofollow".
164 205
165root-desc 206root-desc::
166 Text printed below the heading on the repository index page. Default 207 Text printed below the heading on the repository index page. Default
167 value: "a fast webinterface for the git dscm". 208 value: "a fast webinterface for the git dscm".
168 209
169root-readme: 210root-readme::
170 The content of the file specified with this option will be included 211 The content of the file specified with this option will be included
171 verbatim below the "about" link on the repository index page. Default 212 verbatim below the "about" link on the repository index page. Default
172 value: none. 213 value: none.
173 214
174root-title 215root-title::
175 Text printed as heading on the repository index page. Default value: 216 Text printed as heading on the repository index page. Default value:
176 "Git Repository Browser". 217 "Git Repository Browser".
177 218
178snapshots 219snapshots::
179 Text which specifies the default (and allowed) set of snapshot formats 220 Text which specifies the default (and allowed) set of snapshot formats
180 supported by cgit. The value is a space-separated list of zero or more 221 supported by cgit. The value is a space-separated list of zero or more
181 of the following values: 222 of the following values:
182 "tar" uncompressed tar-file 223 "tar" uncompressed tar-file
183 "tar.gz"gzip-compressed tar-file 224 "tar.gz"gzip-compressed tar-file
184 "tar.bz2"bzip-compressed tar-file 225 "tar.bz2"bzip-compressed tar-file
185 "zip" zip-file 226 "zip" zip-file
186 Default value: none. 227 Default value: none.
187 228
188summary-branches 229source-filter::
230 Specifies a command which will be invoked to format plaintext blobs
231 in the tree view. The command will get the blob content on its STDIN
232 and the name of the blob as its only command line argument. The STDOUT
233 from the command will be included verbatim as the blob contents, i.e.
234 this can be used to implement e.g. syntax highlighting. Default value:
235 none.
236
237summary-branches::
189 Specifies the number of branches to display in the repository "summary" 238 Specifies the number of branches to display in the repository "summary"
190 view. Default value: "10". 239 view. Default value: "10".
191 240
192summary-log 241summary-log::
193 Specifies the number of log entries to display in the repository 242 Specifies the number of log entries to display in the repository
194 "summary" view. Default value: "10". 243 "summary" view. Default value: "10".
195 244
196summary-tags 245summary-tags::
197 Specifies the number of tags to display in the repository "summary" 246 Specifies the number of tags to display in the repository "summary"
198 view. Default value: "10". 247 view. Default value: "10".
199 248
200virtual-root 249virtual-root::
201 Url which, if specified, will be used as root for all cgit links. It 250 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 251 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 252 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
204 value: none. 253 value: none.
205 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 254 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. 255 same kind of virtual urls, so this option will probably be deprecated.
207 256
208REPOSITORY SETTINGS 257REPOSITORY SETTINGS
209------------------- 258-------------------
210repo.clone-url 259repo.about-filter::
260 Override the default about-filter. Default value: <about-filter>.
261
262repo.clone-url::
211 A list of space-separated urls which can be used to clone this repo. 263 A list of space-separated urls which can be used to clone this repo.
212 Default value: none. 264 Default value: none.
213 265
214repo.defbranch 266repo.commit-filter::
267 Override the default commit-filter. Default value: <commit-filter>.
268
269repo.defbranch::
215 The name of the default branch for this repository. If no such branch 270 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 271 exists in the repository, the first branch name (when sorted) is used
217 as default instead. Default value: "master". 272 as default instead. Default value: "master".
218 273
219repo.desc 274repo.desc::
220 The value to show as repository description. Default value: none. 275 The value to show as repository description. Default value: none.
221 276
222repo.enable-log-filecount 277repo.enable-log-filecount::
223 A flag which can be used to disable the global setting 278 A flag which can be used to disable the global setting
224 `enable-log-filecount'. Default value: none. 279 `enable-log-filecount'. Default value: none.
225 280
226repo.enable-log-linecount 281repo.enable-log-linecount::
227 A flag which can be used to disable the global setting 282 A flag which can be used to disable the global setting
228 `enable-log-linecount'. Default value: none. 283 `enable-log-linecount'. Default value: none.
229 284
230repo.max-stats 285repo.max-stats::
231 Override the default maximum statistics period. Valid values are equal 286 Override the default maximum statistics period. Valid values are equal
232 to the values specified for the global "max-stats" setting. Default 287 to the values specified for the global "max-stats" setting. Default
233 value: none. 288 value: none.
234 289
235repo.name 290repo.name::
236 The value to show as repository name. Default value: <repo.url>. 291 The value to show as repository name. Default value: <repo.url>.
237 292
238repo.owner 293repo.owner::
239 A value used to identify the owner of the repository. Default value: 294 A value used to identify the owner of the repository. Default value:
240 none. 295 none.
241 296
242repo.path 297repo.path::
243 An absolute path to the repository directory. For non-bare repositories 298 An absolute path to the repository directory. For non-bare repositories
244 this is the .git-directory. Default value: none. 299 this is the .git-directory. Default value: none.
245 300
246repo.readme 301repo.readme::
247 A path (relative to <repo.path>) which specifies a file to include 302 A path (relative to <repo.path>) which specifies a file to include
248 verbatim as the "About" page for this repo. Default value: none. 303 verbatim as the "About" page for this repo. Default value: none.
249 304
250repo.snapshots 305repo.snapshots::
251 A mask of allowed snapshot-formats for this repo, restricted by the 306 A mask of allowed snapshot-formats for this repo, restricted by the
252 "snapshots" global setting. Default value: <snapshots>. 307 "snapshots" global setting. Default value: <snapshots>.
253 308
254repo.url 309repo.source-filter::
310 Override the default source-filter. Default value: <source-filter>.
311
312repo.url::
255 The relative url used to access the repository. This must be the first 313 The relative url used to access the repository. This must be the first
256 setting specified for each repo. Default value: none. 314 setting specified for each repo. Default value: none.
257 315
258 316
259EXAMPLE CGITRC FILE 317EXAMPLE CGITRC FILE
260------------------- 318-------------------
261 319
320....
262# Enable caching of up to 1000 output entriess 321# Enable caching of up to 1000 output entriess
263cache-size=1000 322cache-size=1000
264 323
265 324
266# Specify some default clone prefixes 325# Specify some default clone prefixes
267clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 326clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
268 327
269# Specify the css url 328# Specify the css url
270css=/css/cgit.css 329css=/css/cgit.css
271 330
272 331
273# Show extra links for each repository on the index page 332# Show extra links for each repository on the index page
@@ -302,24 +361,37 @@ root-title=foobar.com git repositories
302root-desc=tracking the foobar development 361root-desc=tracking the foobar development
303 362
304 363
305# Include some more info about foobar.com on the index page 364# Include some more info about foobar.com on the index page
306root-readme=/var/www/htdocs/about.html 365root-readme=/var/www/htdocs/about.html
307 366
308 367
309# Allow download of tar.gz, tar.bz2 and zip-files 368# Allow download of tar.gz, tar.bz2 and zip-files
310snapshots=tar.gz tar.bz2 zip 369snapshots=tar.gz tar.bz2 zip
311 370
312 371
313## 372##
373## List of common mimetypes
374##
375
376mimetype.git=image/git
377mimetype.html=text/html
378mimetype.jpg=image/jpeg
379mimetype.jpeg=image/jpeg
380mimetype.pdf=application/pdf
381mimetype.png=image/png
382mimetype.svg=image/svg+xml
383
384
385##
314## List of repositories. 386## List of repositories.
315## PS: Any repositories listed when repo.group is unset will not be 387## PS: Any repositories listed when repo.group is unset will not be
316## displayed under a group heading 388## displayed under a group heading
317## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 389## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
318## and included like this: 390## and included like this:
319## include=/etc/cgitrepos 391## include=/etc/cgitrepos
320## 392##
321 393
322 394
323repo.url=foo 395repo.url=foo
324repo.path=/pub/git/foo.git 396repo.path=/pub/git/foo.git
325repo.desc=the master foo repository 397repo.desc=the master foo repository
@@ -359,24 +431,25 @@ repo.desc=the dscm
359repo.url=linux 431repo.url=linux
360repo.path=/pub/git/linux.git 432repo.path=/pub/git/linux.git
361repo.desc=the kernel 433repo.desc=the kernel
362 434
363# Disable adhoc downloads of this repo 435# Disable adhoc downloads of this repo
364repo.snapshots=0 436repo.snapshots=0
365 437
366# Disable line-counts for this repo 438# Disable line-counts for this repo
367repo.enable-log-linecount=0 439repo.enable-log-linecount=0
368 440
369# Restrict the max statistics period for this repo 441# Restrict the max statistics period for this repo
370repo.max-stats=month 442repo.max-stats=month
443....
371 444
372 445
373BUGS 446BUGS
374---- 447----
375Comments currently cannot appear on the same line as a setting; the comment 448Comments currently cannot appear on the same line as a setting; the comment
376will be included as part of the value. E.g. this line: 449will be included as part of the value. E.g. this line:
377 450
378 robots=index # allow indexing 451 robots=index # allow indexing
379 452
380will generate the following html element: 453will generate the following html element:
381 454
382 <meta name='robots' content='index # allow indexing'/> 455 <meta name='robots' content='index # allow indexing'/>
diff --git a/cmd.c b/cmd.c
index cf97da7..766f903 100644
--- a/cmd.c
+++ b/cmd.c
@@ -30,25 +30,25 @@ static 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);
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 e276f018f2c1f0fc962fbe44a36708d1cdebada
diff --git a/shared.c b/shared.c
index cce0af4..911a55a 100644
--- a/shared.c
+++ b/shared.c
@@ -53,24 +53,27 @@ struct cgit_repo *cgit_add_repo(const char *url)
53 ret->path = NULL; 53 ret->path = NULL;
54 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
55 ret->owner = NULL; 55 ret->owner = NULL;
56 ret->group = ctx.cfg.repo_group; 56 ret->group = ctx.cfg.repo_group;
57 ret->defbranch = "master"; 57 ret->defbranch = "master";
58 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->max_stats = ctx.cfg.max_stats; 61 ret->max_stats = ctx.cfg.max_stats;
62 ret->module_link = ctx.cfg.module_link; 62 ret->module_link = ctx.cfg.module_link;
63 ret->readme = NULL; 63 ret->readme = NULL;
64 ret->mtime = -1; 64 ret->mtime = -1;
65 ret->about_filter = ctx.cfg.about_filter;
66 ret->commit_filter = ctx.cfg.commit_filter;
67 ret->source_filter = ctx.cfg.source_filter;
65 return ret; 68 return ret;
66} 69}
67 70
68struct cgit_repo *cgit_get_repoinfo(const char *url) 71struct cgit_repo *cgit_get_repoinfo(const char *url)
69{ 72{
70 int i; 73 int i;
71 struct cgit_repo *repo; 74 struct cgit_repo *repo;
72 75
73 for (i=0; i<cgit_repolist.count; i++) { 76 for (i=0; i<cgit_repolist.count; i++) {
74 repo = &cgit_repolist.repos[i]; 77 repo = &cgit_repolist.repos[i];
75 if (!strcmp(repo->url, url)) 78 if (!strcmp(repo->url, url))
76 return repo; 79 return repo;
@@ -346,12 +349,47 @@ int cgit_parse_snapshots_mask(const char *str)
346 for (f = cgit_snapshot_formats; f->suffix; f++) { 349 for (f = cgit_snapshot_formats; f->suffix; f++) {
347 sl = strlen(f->suffix); 350 sl = strlen(f->suffix);
348 if((tl == sl && !strncmp(f->suffix, str, tl)) || 351 if((tl == sl && !strncmp(f->suffix, str, tl)) ||
349 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) { 352 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) {
350 rv |= f->bit; 353 rv |= f->bit;
351 break; 354 break;
352 } 355 }
353 } 356 }
354 str += tl; 357 str += tl;
355 } 358 }
356 return rv; 359 return rv;
357} 360}
361
362int cgit_open_filter(struct cgit_filter *filter)
363{
364
365 filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
366 "Unable to duplicate STDOUT");
367 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess");
368 filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
369 if (filter->pid == 0) {
370 close(filter->pipe_fh[1]);
371 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO),
372 "Unable to use pipe as STDIN");
373 execvp(filter->cmd, filter->argv);
374 die("Unable to exec subprocess %s: %s (%d)", filter->cmd,
375 strerror(errno), errno);
376 }
377 close(filter->pipe_fh[0]);
378 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO),
379 "Unable to use pipe as STDOUT");
380 close(filter->pipe_fh[1]);
381 return 0;
382}
383
384int cgit_close_filter(struct cgit_filter *filter)
385{
386 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
387 "Unable to restore STDOUT");
388 close(filter->old_stdout);
389 if (filter->pid < 0)
390 return 0;
391 waitpid(filter->pid, &filter->exitstatus, 0);
392 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus))
393 return 0;
394 die("Subprocess %s exited abnormally", filter->cmd);
395}
diff --git a/ui-atom.c b/ui-atom.c
index a6ea3ee..808b2d0 100644
--- a/ui-atom.c
+++ b/ui-atom.c
@@ -23,45 +23,46 @@ void add_entry(struct commit *commit, char *host)
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");
@@ -104,25 +105,26 @@ void cgit_print_atom(char *tip, char *path, int max_count)
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");
diff --git a/ui-blob.c b/ui-blob.c
index 3cda03d..2ccd31d 100644
--- a/ui-blob.c
+++ b/ui-blob.c
@@ -18,25 +18,25 @@ static int walk_tree(const unsigned char *sha1, const char *base,int baselen,
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));
@@ -58,16 +58,22 @@ void cgit_print_blob(const char *hex, char *path, const char *head)
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..d6b73ee 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -31,33 +31,37 @@ void cgit_print_commit(char *hex)
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();
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,
@@ -80,27 +84,35 @@ void cgit_print_commit(char *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..0b37785 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -44,24 +44,28 @@ void show_commit_decorations(struct commit *commit)
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;
diff --git a/ui-patch.c b/ui-patch.c
index 5d665d3..2a8f7a5 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -99,25 +99,29 @@ void cgit_print_patch(char *hex)
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);
diff --git a/ui-plain.c b/ui-plain.c
index 5addd9e..27c6dae 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -8,41 +8,55 @@
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
diff --git a/ui-refs.c b/ui-refs.c
index 25da00a..d3b4f6e 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -37,26 +37,37 @@ static int cmp_ref_name(const void *a, const void *b)
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);
@@ -136,24 +147,30 @@ static int print_tag(struct refinfo *ref)
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
diff --git a/ui-repolist.c b/ui-repolist.c
index 2c13d50..25f076f 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -264,15 +264,20 @@ void cgit_print_repolist()
264 html("</tr>\n"); 264 html("</tr>\n");
265 } 265 }
266 html("</table>"); 266 html("</table>");
267 if (!hits) 267 if (!hits)
268 cgit_print_error("No repositories found"); 268 cgit_print_error("No repositories found");
269 else if (hits > ctx.cfg.max_repo_count) 269 else if (hits > ctx.cfg.max_repo_count)
270 print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search); 270 print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search);
271 cgit_print_docend(); 271 cgit_print_docend();
272} 272}
273 273
274void cgit_print_site_readme() 274void cgit_print_site_readme()
275{ 275{
276 if (ctx.cfg.root_readme) 276 if (!ctx.cfg.root_readme)
277 html_include(ctx.cfg.root_readme); 277 return;
278 if (ctx.cfg.about_filter)
279 cgit_open_filter(ctx.cfg.about_filter);
280 html_include(ctx.cfg.root_readme);
281 if (ctx.cfg.about_filter)
282 cgit_close_filter(ctx.cfg.about_filter);
278} 283}
diff --git a/ui-shared.c b/ui-shared.c
index de77bbf..cf06511 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -25,42 +25,41 @@ static char *http_date(time_t 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{
@@ -447,83 +446,108 @@ void cgit_print_age(time_t t, time_t max_relative, char *format)
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,
@@ -593,31 +617,26 @@ void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page)
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 html("<td class='logo' rowspan='2'><a href='"); 633 html("<td class='logo' rowspan='2'><a href='");
615 if (ctx->cfg.logo_link) 634 if (ctx->cfg.logo_link)
616 html_attr(ctx->cfg.logo_link); 635 html_attr(ctx->cfg.logo_link);
617 else 636 else
618 html_attr(cgit_rooturl()); 637 html_attr(cgit_rooturl());
619 html("'><img src='"); 638 html("'><img src='");
620 html_attr(ctx->cfg.logo); 639 html_attr(ctx->cfg.logo);
621 html("' alt='cgit logo'/></a></td>\n"); 640 html("' alt='cgit logo'/></a></td>\n");
622 641
623 html("<td class='main'>"); 642 html("<td class='main'>");
@@ -640,24 +659,36 @@ void cgit_print_pageheader(struct cgit_context *ctx)
640 html("<tr><td class='sub'>"); 659 html("<tr><td class='sub'>");
641 if (ctx->repo) { 660 if (ctx->repo) {
642 html_txt(ctx->repo->desc); 661 html_txt(ctx->repo->desc);
643 html("</td><td class='sub right'>"); 662 html("</td><td class='sub right'>");
644 html_txt(ctx->repo->owner); 663 html_txt(ctx->repo->owner);
645 } else { 664 } else {
646 if (ctx->cfg.root_desc) 665 if (ctx->cfg.root_desc)
647 html_txt(ctx->cfg.root_desc); 666 html_txt(ctx->cfg.root_desc);
648 else if (ctx->cfg.index_info) 667 else if (ctx->cfg.index_info)
649 html_include(ctx->cfg.index_info); 668 html_include(ctx->cfg.index_info);
650 } 669 }
651 html("</td></tr></table>\n"); 670 html("</td></tr></table>\n");
671}
672
673void cgit_print_pageheader(struct cgit_context *ctx)
674{
675 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
676
677 if (!cmd && ctx->repo)
678 fallback_cmd = "summary";
679
680 html("<div id='cgit'>");
681 if (!ctx->cfg.noheader)
682 print_header(ctx);
652 683
653 html("<table class='tabs'><tr><td>\n"); 684 html("<table class='tabs'><tr><td>\n");
654 if (ctx->repo) { 685 if (ctx->repo) {
655 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 686 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
656 ctx->qry.head); 687 ctx->qry.head);
657 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 688 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
658 ctx->qry.sha1, NULL); 689 ctx->qry.sha1, NULL);
659 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 690 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
660 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); 691 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
661 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 692 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
662 ctx->qry.sha1, NULL); 693 ctx->qry.sha1, NULL);
663 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 694 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
diff --git a/ui-shared.h b/ui-shared.h
index 5a3821f..bff4826 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -1,15 +1,16 @@
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);
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 5372f5d..4136b3e 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -3,55 +3,34 @@
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}
diff --git a/ui-summary.c b/ui-summary.c
index ede4a62..a2c018e 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -57,20 +57,36 @@ void cgit_print_summary()
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
@@ -58,25 +58,25 @@ void cgit_print_tag(char *revname)
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");
diff --git a/ui-tree.c b/ui-tree.c
index 553dbaa..c608754 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -6,31 +6,41 @@
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 if (ctx.repo->source_filter) {
26 html("<tr><td class='lines'><pre><code>");
27 ctx.repo->source_filter->argv[1] = xstrdup(name);
28 cgit_open_filter(ctx.repo->source_filter);
29 write(STDOUT_FILENO, buf, size);
30 cgit_close_filter(ctx.repo->source_filter);
31 html("</code></pre></td></tr></table>\n");
32 return;
33 }
34
25 html("<tr><td class='linenumbers'><pre>"); 35 html("<tr><td class='linenumbers'><pre>");
26 idx = 0; 36 idx = 0;
27 lineno = 0; 37 lineno = 0;
28 38
29 if (size) { 39 if (size) {
30 htmlf(numberfmt, ++lineno); 40 htmlf(numberfmt, ++lineno);
31 while(idx < size - 1) { // skip absolute last newline 41 while(idx < size - 1) { // skip absolute last newline
32 if (buf[idx] == '\n') 42 if (buf[idx] == '\n')
33 htmlf(numberfmt, ++lineno); 43 htmlf(numberfmt, ++lineno);
34 idx++; 44 idx++;
35 } 45 }
36 } 46 }
@@ -56,25 +66,25 @@ static void print_binary_buffer(char *buf, unsigned long size)
56 idx == 16 ? 4 : 1, "", 66 idx == 16 ? 4 : 1, "",
57 buf[idx] & 0xff); 67 buf[idx] & 0xff);
58 html(" </td><td class='hex'>"); 68 html(" </td><td class='hex'>");
59 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 69 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
60 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; 70 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
61 ascii[idx] = '\0'; 71 ascii[idx] = '\0';
62 html_txt(ascii); 72 html_txt(ascii);
63 html("</td></tr>\n"); 73 html("</td></tr>\n");
64 } 74 }
65 html("</table>\n"); 75 html("</table>\n");
66} 76}
67 77
68static void print_object(const unsigned char *sha1, char *path) 78static void print_object(const unsigned char *sha1, char *path, const char *basename)
69{ 79{
70 enum object_type type; 80 enum object_type type;
71 char *buf; 81 char *buf;
72 unsigned long size; 82 unsigned long size;
73 83
74 type = sha1_object_info(sha1, &size); 84 type = sha1_object_info(sha1, &size);
75 if (type == OBJ_BAD) { 85 if (type == OBJ_BAD) {
76 cgit_print_error(fmt("Bad object name: %s", 86 cgit_print_error(fmt("Bad object name: %s",
77 sha1_to_hex(sha1))); 87 sha1_to_hex(sha1)));
78 return; 88 return;
79 } 89 }
80 90
@@ -84,34 +94,35 @@ static void print_object(const unsigned char *sha1, char *path)
84 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
85 return; 95 return;
86 } 96 }
87 97
88 html(" ("); 98 html(" (");
89 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 99 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
90 curr_rev, path); 100 curr_rev, path);
91 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 101 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1));
92 102
93 if (buffer_is_binary(buf, size)) 103 if (buffer_is_binary(buf, size))
94 print_binary_buffer(buf, size); 104 print_binary_buffer(buf, size);
95 else 105 else
96 print_text_buffer(buf, size); 106 print_text_buffer(basename, buf, size);
97} 107}
98 108
99 109
100static int ls_item(const unsigned char *sha1, const char *base, int baselen, 110static int ls_item(const unsigned char *sha1, const char *base, int baselen,
101 const char *pathname, unsigned int mode, int stage, 111 const char *pathname, unsigned int mode, int stage,
102 void *cbdata) 112 void *cbdata)
103{ 113{
104 char *name; 114 char *name;
105 char *fullpath; 115 char *fullpath;
116 char *class;
106 enum object_type type; 117 enum object_type type;
107 unsigned long size = 0; 118 unsigned long size = 0;
108 119
109 name = xstrdup(pathname); 120 name = xstrdup(pathname);
110 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 121 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
111 ctx.qry.path ? "/" : "", name); 122 ctx.qry.path ? "/" : "", name);
112 123
113 if (!S_ISGITLINK(mode)) { 124 if (!S_ISGITLINK(mode)) {
114 type = sha1_object_info(sha1, &size); 125 type = sha1_object_info(sha1, &size);
115 if (type == OBJ_BAD) { 126 if (type == OBJ_BAD) {
116 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 127 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
117 name, 128 name,
@@ -126,25 +137,30 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
126 if (S_ISGITLINK(mode)) { 137 if (S_ISGITLINK(mode)) {
127 htmlf("<a class='ls-mod' href='"); 138 htmlf("<a class='ls-mod' href='");
128 html_attr(fmt(ctx.repo->module_link, 139 html_attr(fmt(ctx.repo->module_link,
129 name, 140 name,
130 sha1_to_hex(sha1))); 141 sha1_to_hex(sha1)));
131 html("'>"); 142 html("'>");
132 html_txt(name); 143 html_txt(name);
133 html("</a>"); 144 html("</a>");
134 } else if (S_ISDIR(mode)) { 145 } else if (S_ISDIR(mode)) {
135 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 146 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
136 curr_rev, fullpath); 147 curr_rev, fullpath);
137 } else { 148 } else {
138 cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head, 149 class = strrchr(name, '.');
150 if (class != NULL) {
151 class = fmt("ls-blob %s", class + 1);
152 } else
153 class = "ls-blob";
154 cgit_tree_link(name, NULL, class, ctx.qry.head,
139 curr_rev, fullpath); 155 curr_rev, fullpath);
140 } 156 }
141 htmlf("</td><td class='ls-size'>%li</td>", size); 157 htmlf("</td><td class='ls-size'>%li</td>", size);
142 158
143 html("<td>"); 159 html("<td>");
144 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 160 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
145 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 161 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
146 if (ctx.repo->max_stats) 162 if (ctx.repo->max_stats)
147 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 163 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
148 fullpath); 164 fullpath);
149 html("</td></tr>\n"); 165 html("</td></tr>\n");
150 free(name); 166 free(name);
@@ -204,25 +220,25 @@ static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
204 html("/"); 220 html("/");
205 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head, 221 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
206 curr_rev, buffer); 222 curr_rev, buffer);
207 223
208 if (strcmp(match_path, buffer)) 224 if (strcmp(match_path, buffer))
209 return READ_TREE_RECURSIVE; 225 return READ_TREE_RECURSIVE;
210 226
211 if (S_ISDIR(mode)) { 227 if (S_ISDIR(mode)) {
212 state = 1; 228 state = 1;
213 ls_head(); 229 ls_head();
214 return READ_TREE_RECURSIVE; 230 return READ_TREE_RECURSIVE;
215 } else { 231 } else {
216 print_object(sha1, buffer); 232 print_object(sha1, buffer, pathname);
217 return 0; 233 return 0;
218 } 234 }
219 } 235 }
220 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 236 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
221 return 0; 237 return 0;
222} 238}
223 239
224 240
225/* 241/*
226 * Show a tree or a blob 242 * Show a tree or a blob
227 * rev: the commit pointing at the root tree object 243 * rev: the commit pointing at the root tree object
228 * path: path to tree or blob 244 * path: path to tree or blob