summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c15
-rw-r--r--cgit.h1
-rw-r--r--cgitrc.5.txt21
3 files changed, 26 insertions, 11 deletions
diff --git a/cgit.c b/cgit.c
index 167b5dd..f1ea03c 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,644 +1,647 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cache.h" 10#include "cache.h"
11#include "cmd.h" 11#include "cmd.h"
12#include "configfile.h" 12#include "configfile.h"
13#include "html.h" 13#include "html.h"
14#include "ui-shared.h" 14#include "ui-shared.h"
15#include "ui-stats.h" 15#include "ui-stats.h"
16#include "scan-tree.h" 16#include "scan-tree.h"
17 17
18const char *cgit_version = CGIT_VERSION; 18const char *cgit_version = CGIT_VERSION;
19 19
20void add_mimetype(const char *name, const char *value) 20void add_mimetype(const char *name, const char *value)
21{ 21{
22 struct string_list_item *item; 22 struct string_list_item *item;
23 23
24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); 24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
25 item->util = xstrdup(value); 25 item->util = xstrdup(value);
26} 26}
27 27
28struct cgit_filter *new_filter(const char *cmd, int extra_args) 28struct cgit_filter *new_filter(const char *cmd, int extra_args)
29{ 29{
30 struct cgit_filter *f; 30 struct cgit_filter *f;
31 31
32 if (!cmd || !cmd[0]) 32 if (!cmd || !cmd[0])
33 return NULL; 33 return NULL;
34 34
35 f = xmalloc(sizeof(struct cgit_filter)); 35 f = xmalloc(sizeof(struct cgit_filter));
36 f->cmd = xstrdup(cmd); 36 f->cmd = xstrdup(cmd);
37 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 37 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
38 f->argv[0] = f->cmd; 38 f->argv[0] = f->cmd;
39 f->argv[1] = NULL; 39 f->argv[1] = NULL;
40 return f; 40 return f;
41} 41}
42 42
43static void process_cached_repolist(const char *path); 43static void process_cached_repolist(const char *path);
44 44
45void repo_config(struct cgit_repo *repo, const char *name, const char *value) 45void repo_config(struct cgit_repo *repo, const char *name, const char *value)
46{ 46{
47 if (!strcmp(name, "name")) 47 if (!strcmp(name, "name"))
48 repo->name = xstrdup(value); 48 repo->name = xstrdup(value);
49 else if (!strcmp(name, "clone-url")) 49 else if (!strcmp(name, "clone-url"))
50 repo->clone_url = xstrdup(value); 50 repo->clone_url = xstrdup(value);
51 else if (!strcmp(name, "desc")) 51 else if (!strcmp(name, "desc"))
52 repo->desc = xstrdup(value); 52 repo->desc = xstrdup(value);
53 else if (!strcmp(name, "owner")) 53 else if (!strcmp(name, "owner"))
54 repo->owner = xstrdup(value); 54 repo->owner = xstrdup(value);
55 else if (!strcmp(name, "defbranch")) 55 else if (!strcmp(name, "defbranch"))
56 repo->defbranch = xstrdup(value); 56 repo->defbranch = xstrdup(value);
57 else if (!strcmp(name, "snapshots")) 57 else if (!strcmp(name, "snapshots"))
58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
59 else if (!strcmp(name, "enable-log-filecount")) 59 else if (!strcmp(name, "enable-log-filecount"))
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount")) 61 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
63 else if (!strcmp(name, "max-stats")) 63 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL); 64 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link")) 65 else if (!strcmp(name, "module-link"))
66 repo->module_link= xstrdup(value); 66 repo->module_link= xstrdup(value);
67 else if (!strcmp(name, "section")) 67 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value); 68 repo->section = xstrdup(value);
69 else if (!strcmp(name, "about-filter"))
70 repo->about_filter = new_filter(value, 0);
71 else if (!strcmp(name, "commit-filter"))
72 repo->commit_filter = new_filter(value, 0);
73 else if (!strcmp(name, "source-filter"))
74 repo->source_filter = new_filter(value, 1);
75 else if (!strcmp(name, "readme") && value != NULL) { 69 else if (!strcmp(name, "readme") && value != NULL) {
76 if (*value == '/') 70 if (*value == '/')
77 ctx.repo->readme = xstrdup(value); 71 ctx.repo->readme = xstrdup(value);
78 else 72 else
79 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 73 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
74 } else if (ctx.cfg.enable_filter_overrides) {
75 if (!strcmp(name, "about-filter"))
76 repo->about_filter = new_filter(value, 0);
77 else if (!strcmp(name, "commit-filter"))
78 repo->commit_filter = new_filter(value, 0);
79 else if (!strcmp(name, "source-filter"))
80 repo->source_filter = new_filter(value, 1);
80 } 81 }
81} 82}
82 83
83void config_cb(const char *name, const char *value) 84void config_cb(const char *name, const char *value)
84{ 85{
85 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 86 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
86 ctx.cfg.section = xstrdup(value); 87 ctx.cfg.section = xstrdup(value);
87 else if (!strcmp(name, "repo.url")) 88 else if (!strcmp(name, "repo.url"))
88 ctx.repo = cgit_add_repo(value); 89 ctx.repo = cgit_add_repo(value);
89 else if (ctx.repo && !strcmp(name, "repo.path")) 90 else if (ctx.repo && !strcmp(name, "repo.path"))
90 ctx.repo->path = trim_end(value, '/'); 91 ctx.repo->path = trim_end(value, '/');
91 else if (ctx.repo && !prefixcmp(name, "repo.")) 92 else if (ctx.repo && !prefixcmp(name, "repo."))
92 repo_config(ctx.repo, name + 5, value); 93 repo_config(ctx.repo, name + 5, value);
93 else if (!strcmp(name, "root-title")) 94 else if (!strcmp(name, "root-title"))
94 ctx.cfg.root_title = xstrdup(value); 95 ctx.cfg.root_title = xstrdup(value);
95 else if (!strcmp(name, "root-desc")) 96 else if (!strcmp(name, "root-desc"))
96 ctx.cfg.root_desc = xstrdup(value); 97 ctx.cfg.root_desc = xstrdup(value);
97 else if (!strcmp(name, "root-readme")) 98 else if (!strcmp(name, "root-readme"))
98 ctx.cfg.root_readme = xstrdup(value); 99 ctx.cfg.root_readme = xstrdup(value);
99 else if (!strcmp(name, "css")) 100 else if (!strcmp(name, "css"))
100 ctx.cfg.css = xstrdup(value); 101 ctx.cfg.css = xstrdup(value);
101 else if (!strcmp(name, "favicon")) 102 else if (!strcmp(name, "favicon"))
102 ctx.cfg.favicon = xstrdup(value); 103 ctx.cfg.favicon = xstrdup(value);
103 else if (!strcmp(name, "footer")) 104 else if (!strcmp(name, "footer"))
104 ctx.cfg.footer = xstrdup(value); 105 ctx.cfg.footer = xstrdup(value);
105 else if (!strcmp(name, "head-include")) 106 else if (!strcmp(name, "head-include"))
106 ctx.cfg.head_include = xstrdup(value); 107 ctx.cfg.head_include = xstrdup(value);
107 else if (!strcmp(name, "header")) 108 else if (!strcmp(name, "header"))
108 ctx.cfg.header = xstrdup(value); 109 ctx.cfg.header = xstrdup(value);
109 else if (!strcmp(name, "logo")) 110 else if (!strcmp(name, "logo"))
110 ctx.cfg.logo = xstrdup(value); 111 ctx.cfg.logo = xstrdup(value);
111 else if (!strcmp(name, "index-header")) 112 else if (!strcmp(name, "index-header"))
112 ctx.cfg.index_header = xstrdup(value); 113 ctx.cfg.index_header = xstrdup(value);
113 else if (!strcmp(name, "index-info")) 114 else if (!strcmp(name, "index-info"))
114 ctx.cfg.index_info = xstrdup(value); 115 ctx.cfg.index_info = xstrdup(value);
115 else if (!strcmp(name, "logo-link")) 116 else if (!strcmp(name, "logo-link"))
116 ctx.cfg.logo_link = xstrdup(value); 117 ctx.cfg.logo_link = xstrdup(value);
117 else if (!strcmp(name, "module-link")) 118 else if (!strcmp(name, "module-link"))
118 ctx.cfg.module_link = xstrdup(value); 119 ctx.cfg.module_link = xstrdup(value);
119 else if (!strcmp(name, "virtual-root")) { 120 else if (!strcmp(name, "virtual-root")) {
120 ctx.cfg.virtual_root = trim_end(value, '/'); 121 ctx.cfg.virtual_root = trim_end(value, '/');
121 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 122 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
122 ctx.cfg.virtual_root = ""; 123 ctx.cfg.virtual_root = "";
123 } else if (!strcmp(name, "nocache")) 124 } else if (!strcmp(name, "nocache"))
124 ctx.cfg.nocache = atoi(value); 125 ctx.cfg.nocache = atoi(value);
125 else if (!strcmp(name, "noplainemail")) 126 else if (!strcmp(name, "noplainemail"))
126 ctx.cfg.noplainemail = atoi(value); 127 ctx.cfg.noplainemail = atoi(value);
127 else if (!strcmp(name, "noheader")) 128 else if (!strcmp(name, "noheader"))
128 ctx.cfg.noheader = atoi(value); 129 ctx.cfg.noheader = atoi(value);
129 else if (!strcmp(name, "snapshots")) 130 else if (!strcmp(name, "snapshots"))
130 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 131 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
132 else if (!strcmp(name, "enable-filter-overrides"))
133 ctx.cfg.enable_filter_overrides = atoi(value);
131 else if (!strcmp(name, "enable-index-links")) 134 else if (!strcmp(name, "enable-index-links"))
132 ctx.cfg.enable_index_links = atoi(value); 135 ctx.cfg.enable_index_links = atoi(value);
133 else if (!strcmp(name, "enable-log-filecount")) 136 else if (!strcmp(name, "enable-log-filecount"))
134 ctx.cfg.enable_log_filecount = atoi(value); 137 ctx.cfg.enable_log_filecount = atoi(value);
135 else if (!strcmp(name, "enable-log-linecount")) 138 else if (!strcmp(name, "enable-log-linecount"))
136 ctx.cfg.enable_log_linecount = atoi(value); 139 ctx.cfg.enable_log_linecount = atoi(value);
137 else if (!strcmp(name, "max-stats")) 140 else if (!strcmp(name, "max-stats"))
138 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 141 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
139 else if (!strcmp(name, "cache-size")) 142 else if (!strcmp(name, "cache-size"))
140 ctx.cfg.cache_size = atoi(value); 143 ctx.cfg.cache_size = atoi(value);
141 else if (!strcmp(name, "cache-root")) 144 else if (!strcmp(name, "cache-root"))
142 ctx.cfg.cache_root = xstrdup(value); 145 ctx.cfg.cache_root = xstrdup(value);
143 else if (!strcmp(name, "cache-root-ttl")) 146 else if (!strcmp(name, "cache-root-ttl"))
144 ctx.cfg.cache_root_ttl = atoi(value); 147 ctx.cfg.cache_root_ttl = atoi(value);
145 else if (!strcmp(name, "cache-repo-ttl")) 148 else if (!strcmp(name, "cache-repo-ttl"))
146 ctx.cfg.cache_repo_ttl = atoi(value); 149 ctx.cfg.cache_repo_ttl = atoi(value);
147 else if (!strcmp(name, "cache-scanrc-ttl")) 150 else if (!strcmp(name, "cache-scanrc-ttl"))
148 ctx.cfg.cache_scanrc_ttl = atoi(value); 151 ctx.cfg.cache_scanrc_ttl = atoi(value);
149 else if (!strcmp(name, "cache-static-ttl")) 152 else if (!strcmp(name, "cache-static-ttl"))
150 ctx.cfg.cache_static_ttl = atoi(value); 153 ctx.cfg.cache_static_ttl = atoi(value);
151 else if (!strcmp(name, "cache-dynamic-ttl")) 154 else if (!strcmp(name, "cache-dynamic-ttl"))
152 ctx.cfg.cache_dynamic_ttl = atoi(value); 155 ctx.cfg.cache_dynamic_ttl = atoi(value);
153 else if (!strcmp(name, "about-filter")) 156 else if (!strcmp(name, "about-filter"))
154 ctx.cfg.about_filter = new_filter(value, 0); 157 ctx.cfg.about_filter = new_filter(value, 0);
155 else if (!strcmp(name, "commit-filter")) 158 else if (!strcmp(name, "commit-filter"))
156 ctx.cfg.commit_filter = new_filter(value, 0); 159 ctx.cfg.commit_filter = new_filter(value, 0);
157 else if (!strcmp(name, "embedded")) 160 else if (!strcmp(name, "embedded"))
158 ctx.cfg.embedded = atoi(value); 161 ctx.cfg.embedded = atoi(value);
159 else if (!strcmp(name, "max-message-length")) 162 else if (!strcmp(name, "max-message-length"))
160 ctx.cfg.max_msg_len = atoi(value); 163 ctx.cfg.max_msg_len = atoi(value);
161 else if (!strcmp(name, "max-repodesc-length")) 164 else if (!strcmp(name, "max-repodesc-length"))
162 ctx.cfg.max_repodesc_len = atoi(value); 165 ctx.cfg.max_repodesc_len = atoi(value);
163 else if (!strcmp(name, "max-repo-count")) 166 else if (!strcmp(name, "max-repo-count"))
164 ctx.cfg.max_repo_count = atoi(value); 167 ctx.cfg.max_repo_count = atoi(value);
165 else if (!strcmp(name, "max-commit-count")) 168 else if (!strcmp(name, "max-commit-count"))
166 ctx.cfg.max_commit_count = atoi(value); 169 ctx.cfg.max_commit_count = atoi(value);
167 else if (!strcmp(name, "scan-path")) 170 else if (!strcmp(name, "scan-path"))
168 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 171 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
169 process_cached_repolist(value); 172 process_cached_repolist(value);
170 else 173 else
171 scan_tree(value, repo_config); 174 scan_tree(value, repo_config);
172 else if (!strcmp(name, "source-filter")) 175 else if (!strcmp(name, "source-filter"))
173 ctx.cfg.source_filter = new_filter(value, 1); 176 ctx.cfg.source_filter = new_filter(value, 1);
174 else if (!strcmp(name, "summary-log")) 177 else if (!strcmp(name, "summary-log"))
175 ctx.cfg.summary_log = atoi(value); 178 ctx.cfg.summary_log = atoi(value);
176 else if (!strcmp(name, "summary-branches")) 179 else if (!strcmp(name, "summary-branches"))
177 ctx.cfg.summary_branches = atoi(value); 180 ctx.cfg.summary_branches = atoi(value);
178 else if (!strcmp(name, "summary-tags")) 181 else if (!strcmp(name, "summary-tags"))
179 ctx.cfg.summary_tags = atoi(value); 182 ctx.cfg.summary_tags = atoi(value);
180 else if (!strcmp(name, "agefile")) 183 else if (!strcmp(name, "agefile"))
181 ctx.cfg.agefile = xstrdup(value); 184 ctx.cfg.agefile = xstrdup(value);
182 else if (!strcmp(name, "renamelimit")) 185 else if (!strcmp(name, "renamelimit"))
183 ctx.cfg.renamelimit = atoi(value); 186 ctx.cfg.renamelimit = atoi(value);
184 else if (!strcmp(name, "robots")) 187 else if (!strcmp(name, "robots"))
185 ctx.cfg.robots = xstrdup(value); 188 ctx.cfg.robots = xstrdup(value);
186 else if (!strcmp(name, "clone-prefix")) 189 else if (!strcmp(name, "clone-prefix"))
187 ctx.cfg.clone_prefix = xstrdup(value); 190 ctx.cfg.clone_prefix = xstrdup(value);
188 else if (!strcmp(name, "local-time")) 191 else if (!strcmp(name, "local-time"))
189 ctx.cfg.local_time = atoi(value); 192 ctx.cfg.local_time = atoi(value);
190 else if (!prefixcmp(name, "mimetype.")) 193 else if (!prefixcmp(name, "mimetype."))
191 add_mimetype(name + 9, value); 194 add_mimetype(name + 9, value);
192 else if (!strcmp(name, "include")) 195 else if (!strcmp(name, "include"))
193 parse_configfile(value, config_cb); 196 parse_configfile(value, config_cb);
194} 197}
195 198
196static void querystring_cb(const char *name, const char *value) 199static void querystring_cb(const char *name, const char *value)
197{ 200{
198 if (!value) 201 if (!value)
199 value = ""; 202 value = "";
200 203
201 if (!strcmp(name,"r")) { 204 if (!strcmp(name,"r")) {
202 ctx.qry.repo = xstrdup(value); 205 ctx.qry.repo = xstrdup(value);
203 ctx.repo = cgit_get_repoinfo(value); 206 ctx.repo = cgit_get_repoinfo(value);
204 } else if (!strcmp(name, "p")) { 207 } else if (!strcmp(name, "p")) {
205 ctx.qry.page = xstrdup(value); 208 ctx.qry.page = xstrdup(value);
206 } else if (!strcmp(name, "url")) { 209 } else if (!strcmp(name, "url")) {
207 ctx.qry.url = xstrdup(value); 210 ctx.qry.url = xstrdup(value);
208 cgit_parse_url(value); 211 cgit_parse_url(value);
209 } else if (!strcmp(name, "qt")) { 212 } else if (!strcmp(name, "qt")) {
210 ctx.qry.grep = xstrdup(value); 213 ctx.qry.grep = xstrdup(value);
211 } else if (!strcmp(name, "q")) { 214 } else if (!strcmp(name, "q")) {
212 ctx.qry.search = xstrdup(value); 215 ctx.qry.search = xstrdup(value);
213 } else if (!strcmp(name, "h")) { 216 } else if (!strcmp(name, "h")) {
214 ctx.qry.head = xstrdup(value); 217 ctx.qry.head = xstrdup(value);
215 ctx.qry.has_symref = 1; 218 ctx.qry.has_symref = 1;
216 } else if (!strcmp(name, "id")) { 219 } else if (!strcmp(name, "id")) {
217 ctx.qry.sha1 = xstrdup(value); 220 ctx.qry.sha1 = xstrdup(value);
218 ctx.qry.has_sha1 = 1; 221 ctx.qry.has_sha1 = 1;
219 } else if (!strcmp(name, "id2")) { 222 } else if (!strcmp(name, "id2")) {
220 ctx.qry.sha2 = xstrdup(value); 223 ctx.qry.sha2 = xstrdup(value);
221 ctx.qry.has_sha1 = 1; 224 ctx.qry.has_sha1 = 1;
222 } else if (!strcmp(name, "ofs")) { 225 } else if (!strcmp(name, "ofs")) {
223 ctx.qry.ofs = atoi(value); 226 ctx.qry.ofs = atoi(value);
224 } else if (!strcmp(name, "path")) { 227 } else if (!strcmp(name, "path")) {
225 ctx.qry.path = trim_end(value, '/'); 228 ctx.qry.path = trim_end(value, '/');
226 } else if (!strcmp(name, "name")) { 229 } else if (!strcmp(name, "name")) {
227 ctx.qry.name = xstrdup(value); 230 ctx.qry.name = xstrdup(value);
228 } else if (!strcmp(name, "mimetype")) { 231 } else if (!strcmp(name, "mimetype")) {
229 ctx.qry.mimetype = xstrdup(value); 232 ctx.qry.mimetype = xstrdup(value);
230 } else if (!strcmp(name, "s")){ 233 } else if (!strcmp(name, "s")){
231 ctx.qry.sort = xstrdup(value); 234 ctx.qry.sort = xstrdup(value);
232 } else if (!strcmp(name, "showmsg")) { 235 } else if (!strcmp(name, "showmsg")) {
233 ctx.qry.showmsg = atoi(value); 236 ctx.qry.showmsg = atoi(value);
234 } else if (!strcmp(name, "period")) { 237 } else if (!strcmp(name, "period")) {
235 ctx.qry.period = xstrdup(value); 238 ctx.qry.period = xstrdup(value);
236 } 239 }
237} 240}
238 241
239char *xstrdupn(const char *str) 242char *xstrdupn(const char *str)
240{ 243{
241 return (str ? xstrdup(str) : NULL); 244 return (str ? xstrdup(str) : NULL);
242} 245}
243 246
244static void prepare_context(struct cgit_context *ctx) 247static void prepare_context(struct cgit_context *ctx)
245{ 248{
246 memset(ctx, 0, sizeof(ctx)); 249 memset(ctx, 0, sizeof(ctx));
247 ctx->cfg.agefile = "info/web/last-modified"; 250 ctx->cfg.agefile = "info/web/last-modified";
248 ctx->cfg.nocache = 0; 251 ctx->cfg.nocache = 0;
249 ctx->cfg.cache_size = 0; 252 ctx->cfg.cache_size = 0;
250 ctx->cfg.cache_dynamic_ttl = 5; 253 ctx->cfg.cache_dynamic_ttl = 5;
251 ctx->cfg.cache_max_create_time = 5; 254 ctx->cfg.cache_max_create_time = 5;
252 ctx->cfg.cache_repo_ttl = 5; 255 ctx->cfg.cache_repo_ttl = 5;
253 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 256 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
254 ctx->cfg.cache_root_ttl = 5; 257 ctx->cfg.cache_root_ttl = 5;
255 ctx->cfg.cache_scanrc_ttl = 15; 258 ctx->cfg.cache_scanrc_ttl = 15;
256 ctx->cfg.cache_static_ttl = -1; 259 ctx->cfg.cache_static_ttl = -1;
257 ctx->cfg.css = "/cgit.css"; 260 ctx->cfg.css = "/cgit.css";
258 ctx->cfg.logo = "/cgit.png"; 261 ctx->cfg.logo = "/cgit.png";
259 ctx->cfg.local_time = 0; 262 ctx->cfg.local_time = 0;
260 ctx->cfg.max_repo_count = 50; 263 ctx->cfg.max_repo_count = 50;
261 ctx->cfg.max_commit_count = 50; 264 ctx->cfg.max_commit_count = 50;
262 ctx->cfg.max_lock_attempts = 5; 265 ctx->cfg.max_lock_attempts = 5;
263 ctx->cfg.max_msg_len = 80; 266 ctx->cfg.max_msg_len = 80;
264 ctx->cfg.max_repodesc_len = 80; 267 ctx->cfg.max_repodesc_len = 80;
265 ctx->cfg.max_stats = 0; 268 ctx->cfg.max_stats = 0;
266 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 269 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
267 ctx->cfg.renamelimit = -1; 270 ctx->cfg.renamelimit = -1;
268 ctx->cfg.robots = "index, nofollow"; 271 ctx->cfg.robots = "index, nofollow";
269 ctx->cfg.root_title = "Git repository browser"; 272 ctx->cfg.root_title = "Git repository browser";
270 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 273 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
271 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 274 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
272 ctx->cfg.section = ""; 275 ctx->cfg.section = "";
273 ctx->cfg.summary_branches = 10; 276 ctx->cfg.summary_branches = 10;
274 ctx->cfg.summary_log = 10; 277 ctx->cfg.summary_log = 10;
275 ctx->cfg.summary_tags = 10; 278 ctx->cfg.summary_tags = 10;
276 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 279 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
277 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 280 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
278 ctx->env.https = xstrdupn(getenv("HTTPS")); 281 ctx->env.https = xstrdupn(getenv("HTTPS"));
279 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 282 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
280 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 283 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
281 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 284 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
282 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 285 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
283 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 286 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
284 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 287 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
285 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 288 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
286 ctx->page.mimetype = "text/html"; 289 ctx->page.mimetype = "text/html";
287 ctx->page.charset = PAGE_ENCODING; 290 ctx->page.charset = PAGE_ENCODING;
288 ctx->page.filename = NULL; 291 ctx->page.filename = NULL;
289 ctx->page.size = 0; 292 ctx->page.size = 0;
290 ctx->page.modified = time(NULL); 293 ctx->page.modified = time(NULL);
291 ctx->page.expires = ctx->page.modified; 294 ctx->page.expires = ctx->page.modified;
292 ctx->page.etag = NULL; 295 ctx->page.etag = NULL;
293 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 296 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
294 if (ctx->env.script_name) 297 if (ctx->env.script_name)
295 ctx->cfg.script_name = ctx->env.script_name; 298 ctx->cfg.script_name = ctx->env.script_name;
296 if (ctx->env.query_string) 299 if (ctx->env.query_string)
297 ctx->qry.raw = ctx->env.query_string; 300 ctx->qry.raw = ctx->env.query_string;
298 if (!ctx->env.cgit_config) 301 if (!ctx->env.cgit_config)
299 ctx->env.cgit_config = CGIT_CONFIG; 302 ctx->env.cgit_config = CGIT_CONFIG;
300} 303}
301 304
302struct refmatch { 305struct refmatch {
303 char *req_ref; 306 char *req_ref;
304 char *first_ref; 307 char *first_ref;
305 int match; 308 int match;
306}; 309};
307 310
308int find_current_ref(const char *refname, const unsigned char *sha1, 311int find_current_ref(const char *refname, const unsigned char *sha1,
309 int flags, void *cb_data) 312 int flags, void *cb_data)
310{ 313{
311 struct refmatch *info; 314 struct refmatch *info;
312 315
313 info = (struct refmatch *)cb_data; 316 info = (struct refmatch *)cb_data;
314 if (!strcmp(refname, info->req_ref)) 317 if (!strcmp(refname, info->req_ref))
315 info->match = 1; 318 info->match = 1;
316 if (!info->first_ref) 319 if (!info->first_ref)
317 info->first_ref = xstrdup(refname); 320 info->first_ref = xstrdup(refname);
318 return info->match; 321 return info->match;
319} 322}
320 323
321char *find_default_branch(struct cgit_repo *repo) 324char *find_default_branch(struct cgit_repo *repo)
322{ 325{
323 struct refmatch info; 326 struct refmatch info;
324 char *ref; 327 char *ref;
325 328
326 info.req_ref = repo->defbranch; 329 info.req_ref = repo->defbranch;
327 info.first_ref = NULL; 330 info.first_ref = NULL;
328 info.match = 0; 331 info.match = 0;
329 for_each_branch_ref(find_current_ref, &info); 332 for_each_branch_ref(find_current_ref, &info);
330 if (info.match) 333 if (info.match)
331 ref = info.req_ref; 334 ref = info.req_ref;
332 else 335 else
333 ref = info.first_ref; 336 ref = info.first_ref;
334 if (ref) 337 if (ref)
335 ref = xstrdup(ref); 338 ref = xstrdup(ref);
336 return ref; 339 return ref;
337} 340}
338 341
339static int prepare_repo_cmd(struct cgit_context *ctx) 342static int prepare_repo_cmd(struct cgit_context *ctx)
340{ 343{
341 char *tmp; 344 char *tmp;
342 unsigned char sha1[20]; 345 unsigned char sha1[20];
343 int nongit = 0; 346 int nongit = 0;
344 347
345 setenv("GIT_DIR", ctx->repo->path, 1); 348 setenv("GIT_DIR", ctx->repo->path, 1);
346 setup_git_directory_gently(&nongit); 349 setup_git_directory_gently(&nongit);
347 if (nongit) { 350 if (nongit) {
348 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 351 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
349 "config error"); 352 "config error");
350 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 353 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
351 ctx->repo = NULL; 354 ctx->repo = NULL;
352 cgit_print_http_headers(ctx); 355 cgit_print_http_headers(ctx);
353 cgit_print_docstart(ctx); 356 cgit_print_docstart(ctx);
354 cgit_print_pageheader(ctx); 357 cgit_print_pageheader(ctx);
355 cgit_print_error(tmp); 358 cgit_print_error(tmp);
356 cgit_print_docend(); 359 cgit_print_docend();
357 return 1; 360 return 1;
358 } 361 }
359 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 362 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
360 363
361 if (!ctx->qry.head) { 364 if (!ctx->qry.head) {
362 ctx->qry.nohead = 1; 365 ctx->qry.nohead = 1;
363 ctx->qry.head = find_default_branch(ctx->repo); 366 ctx->qry.head = find_default_branch(ctx->repo);
364 ctx->repo->defbranch = ctx->qry.head; 367 ctx->repo->defbranch = ctx->qry.head;
365 } 368 }
366 369
367 if (!ctx->qry.head) { 370 if (!ctx->qry.head) {
368 cgit_print_http_headers(ctx); 371 cgit_print_http_headers(ctx);
369 cgit_print_docstart(ctx); 372 cgit_print_docstart(ctx);
370 cgit_print_pageheader(ctx); 373 cgit_print_pageheader(ctx);
371 cgit_print_error("Repository seems to be empty"); 374 cgit_print_error("Repository seems to be empty");
372 cgit_print_docend(); 375 cgit_print_docend();
373 return 1; 376 return 1;
374 } 377 }
375 378
376 if (get_sha1(ctx->qry.head, sha1)) { 379 if (get_sha1(ctx->qry.head, sha1)) {
377 tmp = xstrdup(ctx->qry.head); 380 tmp = xstrdup(ctx->qry.head);
378 ctx->qry.head = ctx->repo->defbranch; 381 ctx->qry.head = ctx->repo->defbranch;
379 ctx->page.status = 404; 382 ctx->page.status = 404;
380 ctx->page.statusmsg = "not found"; 383 ctx->page.statusmsg = "not found";
381 cgit_print_http_headers(ctx); 384 cgit_print_http_headers(ctx);
382 cgit_print_docstart(ctx); 385 cgit_print_docstart(ctx);
383 cgit_print_pageheader(ctx); 386 cgit_print_pageheader(ctx);
384 cgit_print_error(fmt("Invalid branch: %s", tmp)); 387 cgit_print_error(fmt("Invalid branch: %s", tmp));
385 cgit_print_docend(); 388 cgit_print_docend();
386 return 1; 389 return 1;
387 } 390 }
388 return 0; 391 return 0;
389} 392}
390 393
391static void process_request(void *cbdata) 394static void process_request(void *cbdata)
392{ 395{
393 struct cgit_context *ctx = cbdata; 396 struct cgit_context *ctx = cbdata;
394 struct cgit_cmd *cmd; 397 struct cgit_cmd *cmd;
395 398
396 cmd = cgit_get_cmd(ctx); 399 cmd = cgit_get_cmd(ctx);
397 if (!cmd) { 400 if (!cmd) {
398 ctx->page.title = "cgit error"; 401 ctx->page.title = "cgit error";
399 cgit_print_http_headers(ctx); 402 cgit_print_http_headers(ctx);
400 cgit_print_docstart(ctx); 403 cgit_print_docstart(ctx);
401 cgit_print_pageheader(ctx); 404 cgit_print_pageheader(ctx);
402 cgit_print_error("Invalid request"); 405 cgit_print_error("Invalid request");
403 cgit_print_docend(); 406 cgit_print_docend();
404 return; 407 return;
405 } 408 }
406 409
407 if (cmd->want_repo && !ctx->repo) { 410 if (cmd->want_repo && !ctx->repo) {
408 cgit_print_http_headers(ctx); 411 cgit_print_http_headers(ctx);
409 cgit_print_docstart(ctx); 412 cgit_print_docstart(ctx);
410 cgit_print_pageheader(ctx); 413 cgit_print_pageheader(ctx);
411 cgit_print_error(fmt("No repository selected")); 414 cgit_print_error(fmt("No repository selected"));
412 cgit_print_docend(); 415 cgit_print_docend();
413 return; 416 return;
414 } 417 }
415 418
416 if (ctx->repo && prepare_repo_cmd(ctx)) 419 if (ctx->repo && prepare_repo_cmd(ctx))
417 return; 420 return;
418 421
419 if (cmd->want_layout) { 422 if (cmd->want_layout) {
420 cgit_print_http_headers(ctx); 423 cgit_print_http_headers(ctx);
421 cgit_print_docstart(ctx); 424 cgit_print_docstart(ctx);
422 cgit_print_pageheader(ctx); 425 cgit_print_pageheader(ctx);
423 } 426 }
424 427
425 cmd->fn(ctx); 428 cmd->fn(ctx);
426 429
427 if (cmd->want_layout) 430 if (cmd->want_layout)
428 cgit_print_docend(); 431 cgit_print_docend();
429} 432}
430 433
431int cmp_repos(const void *a, const void *b) 434int cmp_repos(const void *a, const void *b)
432{ 435{
433 const struct cgit_repo *ra = a, *rb = b; 436 const struct cgit_repo *ra = a, *rb = b;
434 return strcmp(ra->url, rb->url); 437 return strcmp(ra->url, rb->url);
435} 438}
436 439
437void print_repo(FILE *f, struct cgit_repo *repo) 440void print_repo(FILE *f, struct cgit_repo *repo)
438{ 441{
439 fprintf(f, "repo.url=%s\n", repo->url); 442 fprintf(f, "repo.url=%s\n", repo->url);
440 fprintf(f, "repo.name=%s\n", repo->name); 443 fprintf(f, "repo.name=%s\n", repo->name);
441 fprintf(f, "repo.path=%s\n", repo->path); 444 fprintf(f, "repo.path=%s\n", repo->path);
442 if (repo->owner) 445 if (repo->owner)
443 fprintf(f, "repo.owner=%s\n", repo->owner); 446 fprintf(f, "repo.owner=%s\n", repo->owner);
444 if (repo->desc) 447 if (repo->desc)
445 fprintf(f, "repo.desc=%s\n", repo->desc); 448 fprintf(f, "repo.desc=%s\n", repo->desc);
446 if (repo->readme) 449 if (repo->readme)
447 fprintf(f, "repo.readme=%s\n", repo->readme); 450 fprintf(f, "repo.readme=%s\n", repo->readme);
448 fprintf(f, "\n"); 451 fprintf(f, "\n");
449} 452}
450 453
451void print_repolist(FILE *f, struct cgit_repolist *list, int start) 454void print_repolist(FILE *f, struct cgit_repolist *list, int start)
452{ 455{
453 int i; 456 int i;
454 457
455 for(i = start; i < list->count; i++) 458 for(i = start; i < list->count; i++)
456 print_repo(f, &list->repos[i]); 459 print_repo(f, &list->repos[i]);
457} 460}
458 461
459/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 462/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
460 * and return 0 on success. 463 * and return 0 on success.
461 */ 464 */
462static int generate_cached_repolist(const char *path, const char *cached_rc) 465static int generate_cached_repolist(const char *path, const char *cached_rc)
463{ 466{
464 char *locked_rc; 467 char *locked_rc;
465 int idx; 468 int idx;
466 FILE *f; 469 FILE *f;
467 470
468 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 471 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
469 f = fopen(locked_rc, "wx"); 472 f = fopen(locked_rc, "wx");
470 if (!f) { 473 if (!f) {
471 /* Inform about the error unless the lockfile already existed, 474 /* Inform about the error unless the lockfile already existed,
472 * since that only means we've got concurrent requests. 475 * since that only means we've got concurrent requests.
473 */ 476 */
474 if (errno != EEXIST) 477 if (errno != EEXIST)
475 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 478 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
476 locked_rc, strerror(errno), errno); 479 locked_rc, strerror(errno), errno);
477 return errno; 480 return errno;
478 } 481 }
479 idx = cgit_repolist.count; 482 idx = cgit_repolist.count;
480 scan_tree(path, repo_config); 483 scan_tree(path, repo_config);
481 print_repolist(f, &cgit_repolist, idx); 484 print_repolist(f, &cgit_repolist, idx);
482 if (rename(locked_rc, cached_rc)) 485 if (rename(locked_rc, cached_rc))
483 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 486 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
484 locked_rc, cached_rc, strerror(errno), errno); 487 locked_rc, cached_rc, strerror(errno), errno);
485 fclose(f); 488 fclose(f);
486 return 0; 489 return 0;
487} 490}
488 491
489static void process_cached_repolist(const char *path) 492static void process_cached_repolist(const char *path)
490{ 493{
491 struct stat st; 494 struct stat st;
492 char *cached_rc; 495 char *cached_rc;
493 time_t age; 496 time_t age;
494 497
495 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, 498 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root,
496 hash_str(path))); 499 hash_str(path)));
497 500
498 if (stat(cached_rc, &st)) { 501 if (stat(cached_rc, &st)) {
499 /* Nothing is cached, we need to scan without forking. And 502 /* Nothing is cached, we need to scan without forking. And
500 * if we fail to generate a cached repolist, we need to 503 * if we fail to generate a cached repolist, we need to
501 * invoke scan_tree manually. 504 * invoke scan_tree manually.
502 */ 505 */
503 if (generate_cached_repolist(path, cached_rc)) 506 if (generate_cached_repolist(path, cached_rc))
504 scan_tree(path, repo_config); 507 scan_tree(path, repo_config);
505 return; 508 return;
506 } 509 }
507 510
508 parse_configfile(cached_rc, config_cb); 511 parse_configfile(cached_rc, config_cb);
509 512
510 /* If the cached configfile hasn't expired, lets exit now */ 513 /* If the cached configfile hasn't expired, lets exit now */
511 age = time(NULL) - st.st_mtime; 514 age = time(NULL) - st.st_mtime;
512 if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) 515 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
513 return; 516 return;
514 517
515 /* The cached repolist has been parsed, but it was old. So lets 518 /* The cached repolist has been parsed, but it was old. So lets
516 * rescan the specified path and generate a new cached repolist 519 * rescan the specified path and generate a new cached repolist
517 * in a child-process to avoid latency for the current request. 520 * in a child-process to avoid latency for the current request.
518 */ 521 */
519 if (fork()) 522 if (fork())
520 return; 523 return;
521 524
522 exit(generate_cached_repolist(path, cached_rc)); 525 exit(generate_cached_repolist(path, cached_rc));
523} 526}
524 527
525static void cgit_parse_args(int argc, const char **argv) 528static void cgit_parse_args(int argc, const char **argv)
526{ 529{
527 int i; 530 int i;
528 int scan = 0; 531 int scan = 0;
529 532
530 for (i = 1; i < argc; i++) { 533 for (i = 1; i < argc; i++) {
531 if (!strncmp(argv[i], "--cache=", 8)) { 534 if (!strncmp(argv[i], "--cache=", 8)) {
532 ctx.cfg.cache_root = xstrdup(argv[i]+8); 535 ctx.cfg.cache_root = xstrdup(argv[i]+8);
533 } 536 }
534 if (!strcmp(argv[i], "--nocache")) { 537 if (!strcmp(argv[i], "--nocache")) {
535 ctx.cfg.nocache = 1; 538 ctx.cfg.nocache = 1;
536 } 539 }
537 if (!strcmp(argv[i], "--nohttp")) { 540 if (!strcmp(argv[i], "--nohttp")) {
538 ctx.env.no_http = "1"; 541 ctx.env.no_http = "1";
539 } 542 }
540 if (!strncmp(argv[i], "--query=", 8)) { 543 if (!strncmp(argv[i], "--query=", 8)) {
541 ctx.qry.raw = xstrdup(argv[i]+8); 544 ctx.qry.raw = xstrdup(argv[i]+8);
542 } 545 }
543 if (!strncmp(argv[i], "--repo=", 7)) { 546 if (!strncmp(argv[i], "--repo=", 7)) {
544 ctx.qry.repo = xstrdup(argv[i]+7); 547 ctx.qry.repo = xstrdup(argv[i]+7);
545 } 548 }
546 if (!strncmp(argv[i], "--page=", 7)) { 549 if (!strncmp(argv[i], "--page=", 7)) {
547 ctx.qry.page = xstrdup(argv[i]+7); 550 ctx.qry.page = xstrdup(argv[i]+7);
548 } 551 }
549 if (!strncmp(argv[i], "--head=", 7)) { 552 if (!strncmp(argv[i], "--head=", 7)) {
550 ctx.qry.head = xstrdup(argv[i]+7); 553 ctx.qry.head = xstrdup(argv[i]+7);
551 ctx.qry.has_symref = 1; 554 ctx.qry.has_symref = 1;
552 } 555 }
553 if (!strncmp(argv[i], "--sha1=", 7)) { 556 if (!strncmp(argv[i], "--sha1=", 7)) {
554 ctx.qry.sha1 = xstrdup(argv[i]+7); 557 ctx.qry.sha1 = xstrdup(argv[i]+7);
555 ctx.qry.has_sha1 = 1; 558 ctx.qry.has_sha1 = 1;
556 } 559 }
557 if (!strncmp(argv[i], "--ofs=", 6)) { 560 if (!strncmp(argv[i], "--ofs=", 6)) {
558 ctx.qry.ofs = atoi(argv[i]+6); 561 ctx.qry.ofs = atoi(argv[i]+6);
559 } 562 }
560 if (!strncmp(argv[i], "--scan-tree=", 12) || 563 if (!strncmp(argv[i], "--scan-tree=", 12) ||
561 !strncmp(argv[i], "--scan-path=", 12)) { 564 !strncmp(argv[i], "--scan-path=", 12)) {
562 scan++; 565 scan++;
563 scan_tree(argv[i] + 12, repo_config); 566 scan_tree(argv[i] + 12, repo_config);
564 } 567 }
565 } 568 }
566 if (scan) { 569 if (scan) {
567 qsort(cgit_repolist.repos, cgit_repolist.count, 570 qsort(cgit_repolist.repos, cgit_repolist.count,
568 sizeof(struct cgit_repo), cmp_repos); 571 sizeof(struct cgit_repo), cmp_repos);
569 print_repolist(stdout, &cgit_repolist, 0); 572 print_repolist(stdout, &cgit_repolist, 0);
570 exit(0); 573 exit(0);
571 } 574 }
572} 575}
573 576
574static int calc_ttl() 577static int calc_ttl()
575{ 578{
576 if (!ctx.repo) 579 if (!ctx.repo)
577 return ctx.cfg.cache_root_ttl; 580 return ctx.cfg.cache_root_ttl;
578 581
579 if (!ctx.qry.page) 582 if (!ctx.qry.page)
580 return ctx.cfg.cache_repo_ttl; 583 return ctx.cfg.cache_repo_ttl;
581 584
582 if (ctx.qry.has_symref) 585 if (ctx.qry.has_symref)
583 return ctx.cfg.cache_dynamic_ttl; 586 return ctx.cfg.cache_dynamic_ttl;
584 587
585 if (ctx.qry.has_sha1) 588 if (ctx.qry.has_sha1)
586 return ctx.cfg.cache_static_ttl; 589 return ctx.cfg.cache_static_ttl;
587 590
588 return ctx.cfg.cache_repo_ttl; 591 return ctx.cfg.cache_repo_ttl;
589} 592}
590 593
591int main(int argc, const char **argv) 594int main(int argc, const char **argv)
592{ 595{
593 const char *path; 596 const char *path;
594 char *qry; 597 char *qry;
595 int err, ttl; 598 int err, ttl;
596 599
597 prepare_context(&ctx); 600 prepare_context(&ctx);
598 cgit_repolist.length = 0; 601 cgit_repolist.length = 0;
599 cgit_repolist.count = 0; 602 cgit_repolist.count = 0;
600 cgit_repolist.repos = NULL; 603 cgit_repolist.repos = NULL;
601 604
602 cgit_parse_args(argc, argv); 605 cgit_parse_args(argc, argv);
603 parse_configfile(ctx.env.cgit_config, config_cb); 606 parse_configfile(ctx.env.cgit_config, config_cb);
604 ctx.repo = NULL; 607 ctx.repo = NULL;
605 http_parse_querystring(ctx.qry.raw, querystring_cb); 608 http_parse_querystring(ctx.qry.raw, querystring_cb);
606 609
607 /* If virtual-root isn't specified in cgitrc, lets pretend 610 /* If virtual-root isn't specified in cgitrc, lets pretend
608 * that virtual-root equals SCRIPT_NAME. 611 * that virtual-root equals SCRIPT_NAME.
609 */ 612 */
610 if (!ctx.cfg.virtual_root) 613 if (!ctx.cfg.virtual_root)
611 ctx.cfg.virtual_root = ctx.cfg.script_name; 614 ctx.cfg.virtual_root = ctx.cfg.script_name;
612 615
613 /* If no url parameter is specified on the querystring, lets 616 /* If no url parameter is specified on the querystring, lets
614 * use PATH_INFO as url. This allows cgit to work with virtual 617 * use PATH_INFO as url. This allows cgit to work with virtual
615 * urls without the need for rewriterules in the webserver (as 618 * urls without the need for rewriterules in the webserver (as
616 * long as PATH_INFO is included in the cache lookup key). 619 * long as PATH_INFO is included in the cache lookup key).
617 */ 620 */
618 path = ctx.env.path_info; 621 path = ctx.env.path_info;
619 if (!ctx.qry.url && path) { 622 if (!ctx.qry.url && path) {
620 if (path[0] == '/') 623 if (path[0] == '/')
621 path++; 624 path++;
622 ctx.qry.url = xstrdup(path); 625 ctx.qry.url = xstrdup(path);
623 if (ctx.qry.raw) { 626 if (ctx.qry.raw) {
624 qry = ctx.qry.raw; 627 qry = ctx.qry.raw;
625 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 628 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
626 free(qry); 629 free(qry);
627 } else 630 } else
628 ctx.qry.raw = xstrdup(ctx.qry.url); 631 ctx.qry.raw = xstrdup(ctx.qry.url);
629 cgit_parse_url(ctx.qry.url); 632 cgit_parse_url(ctx.qry.url);
630 } 633 }
631 634
632 ttl = calc_ttl(); 635 ttl = calc_ttl();
633 ctx.page.expires += ttl*60; 636 ctx.page.expires += ttl*60;
634 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) 637 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
635 ctx.cfg.nocache = 1; 638 ctx.cfg.nocache = 1;
636 if (ctx.cfg.nocache) 639 if (ctx.cfg.nocache)
637 ctx.cfg.cache_size = 0; 640 ctx.cfg.cache_size = 0;
638 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 641 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
639 ctx.qry.raw, ttl, process_request, &ctx); 642 ctx.qry.raw, ttl, process_request, &ctx);
640 if (err) 643 if (err)
641 cgit_print_error(fmt("Error processing page: %s (%d)", 644 cgit_print_error(fmt("Error processing page: %s (%d)",
642 strerror(err), err)); 645 strerror(err), err));
643 return err; 646 return err;
644} 647}
diff --git a/cgit.h b/cgit.h
index 3359be9..ef109aa 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,292 +1,293 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22 22
23 23
24/* 24/*
25 * Dateformats used on misc. pages 25 * Dateformats used on misc. pages
26 */ 26 */
27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
28#define FMT_SHORTDATE "%Y-%m-%d" 28#define FMT_SHORTDATE "%Y-%m-%d"
29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
30 30
31 31
32/* 32/*
33 * Limits used for relative dates 33 * Limits used for relative dates
34 */ 34 */
35#define TM_MIN 60 35#define TM_MIN 60
36#define TM_HOUR (TM_MIN * 60) 36#define TM_HOUR (TM_MIN * 60)
37#define TM_DAY (TM_HOUR * 24) 37#define TM_DAY (TM_HOUR * 24)
38#define TM_WEEK (TM_DAY * 7) 38#define TM_WEEK (TM_DAY * 7)
39#define TM_YEAR (TM_DAY * 365) 39#define TM_YEAR (TM_DAY * 365)
40#define TM_MONTH (TM_YEAR / 12.0) 40#define TM_MONTH (TM_YEAR / 12.0)
41 41
42 42
43/* 43/*
44 * Default encoding 44 * Default encoding
45 */ 45 */
46#define PAGE_ENCODING "UTF-8" 46#define PAGE_ENCODING "UTF-8"
47 47
48typedef void (*configfn)(const char *name, const char *value); 48typedef void (*configfn)(const char *name, const char *value);
49typedef void (*filepair_fn)(struct diff_filepair *pair); 49typedef void (*filepair_fn)(struct diff_filepair *pair);
50typedef void (*linediff_fn)(char *line, int len); 50typedef void (*linediff_fn)(char *line, int len);
51 51
52struct cgit_filter { 52struct cgit_filter {
53 char *cmd; 53 char *cmd;
54 char **argv; 54 char **argv;
55 int old_stdout; 55 int old_stdout;
56 int pipe_fh[2]; 56 int pipe_fh[2];
57 int pid; 57 int pid;
58 int exitstatus; 58 int exitstatus;
59}; 59};
60 60
61struct cgit_repo { 61struct cgit_repo {
62 char *url; 62 char *url;
63 char *name; 63 char *name;
64 char *path; 64 char *path;
65 char *desc; 65 char *desc;
66 char *owner; 66 char *owner;
67 char *defbranch; 67 char *defbranch;
68 char *module_link; 68 char *module_link;
69 char *readme; 69 char *readme;
70 char *section; 70 char *section;
71 char *clone_url; 71 char *clone_url;
72 int snapshots; 72 int snapshots;
73 int enable_log_filecount; 73 int enable_log_filecount;
74 int enable_log_linecount; 74 int enable_log_linecount;
75 int max_stats; 75 int max_stats;
76 time_t mtime; 76 time_t mtime;
77 struct cgit_filter *about_filter; 77 struct cgit_filter *about_filter;
78 struct cgit_filter *commit_filter; 78 struct cgit_filter *commit_filter;
79 struct cgit_filter *source_filter; 79 struct cgit_filter *source_filter;
80}; 80};
81 81
82typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 82typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
83 const char *value); 83 const char *value);
84 84
85struct cgit_repolist { 85struct cgit_repolist {
86 int length; 86 int length;
87 int count; 87 int count;
88 struct cgit_repo *repos; 88 struct cgit_repo *repos;
89}; 89};
90 90
91struct commitinfo { 91struct commitinfo {
92 struct commit *commit; 92 struct commit *commit;
93 char *author; 93 char *author;
94 char *author_email; 94 char *author_email;
95 unsigned long author_date; 95 unsigned long author_date;
96 char *committer; 96 char *committer;
97 char *committer_email; 97 char *committer_email;
98 unsigned long committer_date; 98 unsigned long committer_date;
99 char *subject; 99 char *subject;
100 char *msg; 100 char *msg;
101 char *msg_encoding; 101 char *msg_encoding;
102}; 102};
103 103
104struct taginfo { 104struct taginfo {
105 char *tagger; 105 char *tagger;
106 char *tagger_email; 106 char *tagger_email;
107 unsigned long tagger_date; 107 unsigned long tagger_date;
108 char *msg; 108 char *msg;
109}; 109};
110 110
111struct refinfo { 111struct refinfo {
112 const char *refname; 112 const char *refname;
113 struct object *object; 113 struct object *object;
114 union { 114 union {
115 struct taginfo *tag; 115 struct taginfo *tag;
116 struct commitinfo *commit; 116 struct commitinfo *commit;
117 }; 117 };
118}; 118};
119 119
120struct reflist { 120struct reflist {
121 struct refinfo **refs; 121 struct refinfo **refs;
122 int alloc; 122 int alloc;
123 int count; 123 int count;
124}; 124};
125 125
126struct cgit_query { 126struct cgit_query {
127 int has_symref; 127 int has_symref;
128 int has_sha1; 128 int has_sha1;
129 char *raw; 129 char *raw;
130 char *repo; 130 char *repo;
131 char *page; 131 char *page;
132 char *search; 132 char *search;
133 char *grep; 133 char *grep;
134 char *head; 134 char *head;
135 char *sha1; 135 char *sha1;
136 char *sha2; 136 char *sha2;
137 char *path; 137 char *path;
138 char *name; 138 char *name;
139 char *mimetype; 139 char *mimetype;
140 char *url; 140 char *url;
141 char *period; 141 char *period;
142 int ofs; 142 int ofs;
143 int nohead; 143 int nohead;
144 char *sort; 144 char *sort;
145 int showmsg; 145 int showmsg;
146}; 146};
147 147
148struct cgit_config { 148struct cgit_config {
149 char *agefile; 149 char *agefile;
150 char *cache_root; 150 char *cache_root;
151 char *clone_prefix; 151 char *clone_prefix;
152 char *css; 152 char *css;
153 char *favicon; 153 char *favicon;
154 char *footer; 154 char *footer;
155 char *head_include; 155 char *head_include;
156 char *header; 156 char *header;
157 char *index_header; 157 char *index_header;
158 char *index_info; 158 char *index_info;
159 char *logo; 159 char *logo;
160 char *logo_link; 160 char *logo_link;
161 char *module_link; 161 char *module_link;
162 char *robots; 162 char *robots;
163 char *root_title; 163 char *root_title;
164 char *root_desc; 164 char *root_desc;
165 char *root_readme; 165 char *root_readme;
166 char *script_name; 166 char *script_name;
167 char *section; 167 char *section;
168 char *virtual_root; 168 char *virtual_root;
169 int cache_size; 169 int cache_size;
170 int cache_dynamic_ttl; 170 int cache_dynamic_ttl;
171 int cache_max_create_time; 171 int cache_max_create_time;
172 int cache_repo_ttl; 172 int cache_repo_ttl;
173 int cache_root_ttl; 173 int cache_root_ttl;
174 int cache_scanrc_ttl; 174 int cache_scanrc_ttl;
175 int cache_static_ttl; 175 int cache_static_ttl;
176 int embedded; 176 int embedded;
177 int enable_filter_overrides;
177 int enable_index_links; 178 int enable_index_links;
178 int enable_log_filecount; 179 int enable_log_filecount;
179 int enable_log_linecount; 180 int enable_log_linecount;
180 int local_time; 181 int local_time;
181 int max_repo_count; 182 int max_repo_count;
182 int max_commit_count; 183 int max_commit_count;
183 int max_lock_attempts; 184 int max_lock_attempts;
184 int max_msg_len; 185 int max_msg_len;
185 int max_repodesc_len; 186 int max_repodesc_len;
186 int max_stats; 187 int max_stats;
187 int nocache; 188 int nocache;
188 int noplainemail; 189 int noplainemail;
189 int noheader; 190 int noheader;
190 int renamelimit; 191 int renamelimit;
191 int snapshots; 192 int snapshots;
192 int summary_branches; 193 int summary_branches;
193 int summary_log; 194 int summary_log;
194 int summary_tags; 195 int summary_tags;
195 struct string_list mimetypes; 196 struct string_list mimetypes;
196 struct cgit_filter *about_filter; 197 struct cgit_filter *about_filter;
197 struct cgit_filter *commit_filter; 198 struct cgit_filter *commit_filter;
198 struct cgit_filter *source_filter; 199 struct cgit_filter *source_filter;
199}; 200};
200 201
201struct cgit_page { 202struct cgit_page {
202 time_t modified; 203 time_t modified;
203 time_t expires; 204 time_t expires;
204 size_t size; 205 size_t size;
205 char *mimetype; 206 char *mimetype;
206 char *charset; 207 char *charset;
207 char *filename; 208 char *filename;
208 char *etag; 209 char *etag;
209 char *title; 210 char *title;
210 int status; 211 int status;
211 char *statusmsg; 212 char *statusmsg;
212}; 213};
213 214
214struct cgit_environment { 215struct cgit_environment {
215 char *cgit_config; 216 char *cgit_config;
216 char *http_host; 217 char *http_host;
217 char *https; 218 char *https;
218 char *no_http; 219 char *no_http;
219 char *path_info; 220 char *path_info;
220 char *query_string; 221 char *query_string;
221 char *request_method; 222 char *request_method;
222 char *script_name; 223 char *script_name;
223 char *server_name; 224 char *server_name;
224 char *server_port; 225 char *server_port;
225}; 226};
226 227
227struct cgit_context { 228struct cgit_context {
228 struct cgit_environment env; 229 struct cgit_environment env;
229 struct cgit_query qry; 230 struct cgit_query qry;
230 struct cgit_config cfg; 231 struct cgit_config cfg;
231 struct cgit_repo *repo; 232 struct cgit_repo *repo;
232 struct cgit_page page; 233 struct cgit_page page;
233}; 234};
234 235
235struct cgit_snapshot_format { 236struct cgit_snapshot_format {
236 const char *suffix; 237 const char *suffix;
237 const char *mimetype; 238 const char *mimetype;
238 write_archive_fn_t write_func; 239 write_archive_fn_t write_func;
239 int bit; 240 int bit;
240}; 241};
241 242
242extern const char *cgit_version; 243extern const char *cgit_version;
243 244
244extern struct cgit_repolist cgit_repolist; 245extern struct cgit_repolist cgit_repolist;
245extern struct cgit_context ctx; 246extern struct cgit_context ctx;
246extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 247extern const struct cgit_snapshot_format cgit_snapshot_formats[];
247 248
248extern struct cgit_repo *cgit_add_repo(const char *url); 249extern struct cgit_repo *cgit_add_repo(const char *url);
249extern struct cgit_repo *cgit_get_repoinfo(const char *url); 250extern struct cgit_repo *cgit_get_repoinfo(const char *url);
250extern void cgit_repo_config_cb(const char *name, const char *value); 251extern void cgit_repo_config_cb(const char *name, const char *value);
251 252
252extern int chk_zero(int result, char *msg); 253extern int chk_zero(int result, char *msg);
253extern int chk_positive(int result, char *msg); 254extern int chk_positive(int result, char *msg);
254extern int chk_non_negative(int result, char *msg); 255extern int chk_non_negative(int result, char *msg);
255 256
256extern char *trim_end(const char *str, char c); 257extern char *trim_end(const char *str, char c);
257extern char *strlpart(char *txt, int maxlen); 258extern char *strlpart(char *txt, int maxlen);
258extern char *strrpart(char *txt, int maxlen); 259extern char *strrpart(char *txt, int maxlen);
259 260
260extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 261extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
261extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 262extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
262 int flags, void *cb_data); 263 int flags, void *cb_data);
263 264
264extern void *cgit_free_commitinfo(struct commitinfo *info); 265extern void *cgit_free_commitinfo(struct commitinfo *info);
265 266
266extern int cgit_diff_files(const unsigned char *old_sha1, 267extern int cgit_diff_files(const unsigned char *old_sha1,
267 const unsigned char *new_sha1, 268 const unsigned char *new_sha1,
268 unsigned long *old_size, unsigned long *new_size, 269 unsigned long *old_size, unsigned long *new_size,
269 int *binary, linediff_fn fn); 270 int *binary, linediff_fn fn);
270 271
271extern void cgit_diff_tree(const unsigned char *old_sha1, 272extern void cgit_diff_tree(const unsigned char *old_sha1,
272 const unsigned char *new_sha1, 273 const unsigned char *new_sha1,
273 filepair_fn fn, const char *prefix); 274 filepair_fn fn, const char *prefix);
274 275
275extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 276extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
276 277
277extern char *fmt(const char *format,...); 278extern char *fmt(const char *format,...);
278 279
279extern struct commitinfo *cgit_parse_commit(struct commit *commit); 280extern struct commitinfo *cgit_parse_commit(struct commit *commit);
280extern struct taginfo *cgit_parse_tag(struct tag *tag); 281extern struct taginfo *cgit_parse_tag(struct tag *tag);
281extern void cgit_parse_url(const char *url); 282extern void cgit_parse_url(const char *url);
282 283
283extern const char *cgit_repobasename(const char *reponame); 284extern const char *cgit_repobasename(const char *reponame);
284 285
285extern int cgit_parse_snapshots_mask(const char *str); 286extern int cgit_parse_snapshots_mask(const char *str);
286 287
287extern int cgit_open_filter(struct cgit_filter *filter); 288extern int cgit_open_filter(struct cgit_filter *filter);
288extern int cgit_close_filter(struct cgit_filter *filter); 289extern int cgit_close_filter(struct cgit_filter *filter);
289 290
290extern int readfile(const char *path, char **buf, size_t *size); 291extern int readfile(const char *path, char **buf, size_t *size);
291 292
292#endif /* CGIT_H */ 293#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 46df291..617b7c3 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,483 +1,494 @@
1CGITRC(5) 1CGITRC(5)
2======== 2========
3 3
4 4
5NAME 5NAME
6---- 6----
7cgitrc - runtime configuration for cgit 7cgitrc - runtime configuration for cgit
8 8
9 9
10SYNOPSIS 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 17LOCATION
18-------- 18--------
19The default location of cgitrc, defined at compile time, is /etc/cgitrc. At 19The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
20runtime, cgit will consult the environment variable CGIT_CONFIG and, if 20runtime, cgit will consult the environment variable CGIT_CONFIG and, if
21defined, use its value instead. 21defined, use its value instead.
22 22
23 23
24GLOBAL SETTINGS 24GLOBAL SETTINGS
25--------------- 25---------------
26about-filter:: 26about-filter::
27 Specifies a command which will be invoked to format the content of 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 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 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: 30 command will be included verbatim on the about page. Default value:
31 none. 31 none.
32 32
33agefile:: 33agefile::
34 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
35 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.
36 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"
37 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 37 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
38 hh:mm:ss". Default value: "info/web/last-modified". 38 hh:mm:ss". Default value: "info/web/last-modified".
39 39
40cache-root:: 40cache-root::
41 Path used to store the cgit cache entries. Default value: 41 Path used to store the cgit cache entries. Default value:
42 "/var/cache/cgit". 42 "/var/cache/cgit".
43 43
44cache-dynamic-ttl:: 44cache-dynamic-ttl::
45 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
46 version of repository pages accessed without a fixed SHA1. Default 46 version of repository pages accessed without a fixed SHA1. Default
47 value: "5". 47 value: "5".
48 48
49cache-repo-ttl:: 49cache-repo-ttl::
50 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
51 version of the repository summary page. Default value: "5". 51 version of the repository summary page. Default value: "5".
52 52
53cache-root-ttl:: 53cache-root-ttl::
54 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
55 version of the repository index page. Default value: "5". 55 version of the repository index page. Default value: "5".
56 56
57cache-scanrc-ttl:: 57cache-scanrc-ttl::
58 Number which specifies the time-to-live, in minutes, for the result 58 Number which specifies the time-to-live, in minutes, for the result
59 of scanning a path for git repositories. Default value: "15". 59 of scanning a path for git repositories. Default value: "15".
60 60
61cache-size:: 61cache-size::
62 The maximum number of entries in the cgit cache. Default value: "0" 62 The maximum number of entries in the cgit cache. Default value: "0"
63 (i.e. caching is disabled). 63 (i.e. caching is disabled).
64 64
65cache-static-ttl:: 65cache-static-ttl::
66 Number which specifies the time-to-live, in minutes, for the cached 66 Number which specifies the time-to-live, in minutes, for the cached
67 version of repository pages accessed with a fixed SHA1. Default value: 67 version of repository pages accessed with a fixed SHA1. Default value:
68 "5". 68 "5".
69 69
70clone-prefix:: 70clone-prefix::
71 Space-separated list of common prefixes which, when combined with a 71 Space-separated list of common prefixes which, when combined with a
72 repository url, generates valid clone urls for the repository. This 72 repository url, generates valid clone urls for the repository. This
73 setting is only used if `repo.clone-url` is unspecified. Default value: 73 setting is only used if `repo.clone-url` is unspecified. Default value:
74 none. 74 none.
75 75
76commit-filter:: 76commit-filter::
77 Specifies a command which will be invoked to format commit messages. 77 Specifies a command which will be invoked to format commit messages.
78 The command will get the message on its STDIN, and the STDOUT from the 78 The command will get the message on its STDIN, and the STDOUT from the
79 command will be included verbatim as the commit message, i.e. this can 79 command will be included verbatim as the commit message, i.e. this can
80 be used to implement bugtracker integration. Default value: none. 80 be used to implement bugtracker integration. Default value: none.
81 81
82css:: 82css::
83 Url which specifies the css document to include in all cgit pages. 83 Url which specifies the css document to include in all cgit pages.
84 Default value: "/cgit.css". 84 Default value: "/cgit.css".
85 85
86embedded:: 86embedded::
87 Flag which, when set to "1", will make cgit generate a html fragment 87 Flag which, when set to "1", will make cgit generate a html fragment
88 suitable for embedding in other html pages. Default value: none. See 88 suitable for embedding in other html pages. Default value: none. See
89 also: "noheader". 89 also: "noheader".
90 90
91enable-filter-overrides::
92 Flag which, when set to "1", allows all filter settings to be
93 overridden in repository-specific cgitrc files. Default value: none.
94
91enable-index-links:: 95enable-index-links::
92 Flag which, when set to "1", will make cgit generate extra links for 96 Flag which, when set to "1", will make cgit generate extra links for
93 each repo in the repository index (specifically, to the "summary", 97 each repo in the repository index (specifically, to the "summary",
94 "commit" and "tree" pages). Default value: "0". 98 "commit" and "tree" pages). Default value: "0".
95 99
96enable-log-filecount:: 100enable-log-filecount::
97 Flag which, when set to "1", will make cgit print the number of 101 Flag which, when set to "1", will make cgit print the number of
98 modified files for each commit on the repository log page. Default 102 modified files for each commit on the repository log page. Default
99 value: "0". 103 value: "0".
100 104
101enable-log-linecount:: 105enable-log-linecount::
102 Flag which, when set to "1", will make cgit print the number of added 106 Flag which, when set to "1", will make cgit print the number of added
103 and removed lines for each commit on the repository log page. Default 107 and removed lines for each commit on the repository log page. Default
104 value: "0". 108 value: "0".
105 109
106favicon:: 110favicon::
107 Url used as link to a shortcut icon for cgit. If specified, it is 111 Url used as link to a shortcut icon for cgit. If specified, it is
108 suggested to use the value "/favicon.ico" since certain browsers will 112 suggested to use the value "/favicon.ico" since certain browsers will
109 ignore other values. Default value: none. 113 ignore other values. Default value: none.
110 114
111footer:: 115footer::
112 The content of the file specified with this option will be included 116 The content of the file specified with this option will be included
113 verbatim at the bottom of all pages (i.e. it replaces the standard 117 verbatim at the bottom of all pages (i.e. it replaces the standard
114 "generated by..." message. Default value: none. 118 "generated by..." message. Default value: none.
115 119
116head-include:: 120head-include::
117 The content of the file specified with this option will be included 121 The content of the file specified with this option will be included
118 verbatim in the html HEAD section on all pages. Default value: none. 122 verbatim in the html HEAD section on all pages. Default value: none.
119 123
120header:: 124header::
121 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
122 verbatim at the top of all pages. Default value: none. 126 verbatim at the top of all pages. Default value: none.
123 127
124include:: 128include::
125 Name of a configfile to include before the rest of the current config- 129 Name of a configfile to include before the rest of the current config-
126 file is parsed. Default value: none. 130 file is parsed. Default value: none.
127 131
128index-header:: 132index-header::
129 The content of the file specified with this option will be included 133 The content of the file specified with this option will be included
130 verbatim above the repository index. This setting is deprecated, and 134 verbatim above the repository index. This setting is deprecated, and
131 will not be supported by cgit-1.0 (use root-readme instead). Default 135 will not be supported by cgit-1.0 (use root-readme instead). Default
132 value: none. 136 value: none.
133 137
134index-info:: 138index-info::
135 The content of the file specified with this option will be included 139 The content of the file specified with this option will be included
136 verbatim below the heading on the repository index page. This setting 140 verbatim below the heading on the repository index page. This setting
137 is deprecated, and will not be supported by cgit-1.0 (use root-desc 141 is deprecated, and will not be supported by cgit-1.0 (use root-desc
138 instead). Default value: none. 142 instead). Default value: none.
139 143
140local-time:: 144local-time::
141 Flag which, if set to "1", makes cgit print commit and tag times in the 145 Flag which, if set to "1", makes cgit print commit and tag times in the
142 servers timezone. Default value: "0". 146 servers timezone. Default value: "0".
143 147
144logo:: 148logo::
145 Url which specifies the source of an image which will be used as a logo 149 Url which specifies the source of an image which will be used as a logo
146 on all cgit pages. Default value: "/cgit.png". 150 on all cgit pages. Default value: "/cgit.png".
147 151
148logo-link:: 152logo-link::
149 Url loaded when clicking on the cgit logo image. If unspecified the 153 Url loaded when clicking on the cgit logo image. If unspecified the
150 calculated url of the repository index page will be used. Default 154 calculated url of the repository index page will be used. Default
151 value: none. 155 value: none.
152 156
153max-commit-count:: 157max-commit-count::
154 Specifies the number of entries to list per page in "log" view. Default 158 Specifies the number of entries to list per page in "log" view. Default
155 value: "50". 159 value: "50".
156 160
157max-message-length:: 161max-message-length::
158 Specifies the maximum number of commit message characters to display in 162 Specifies the maximum number of commit message characters to display in
159 "log" view. Default value: "80". 163 "log" view. Default value: "80".
160 164
161max-repo-count:: 165max-repo-count::
162 Specifies the number of entries to list per page on therepository 166 Specifies the number of entries to list per page on therepository
163 index page. Default value: "50". 167 index page. Default value: "50".
164 168
165max-repodesc-length:: 169max-repodesc-length::
166 Specifies the maximum number of repo description characters to display 170 Specifies the maximum number of repo description characters to display
167 on the repository index page. Default value: "80". 171 on the repository index page. Default value: "80".
168 172
169max-stats:: 173max-stats::
170 Set the default maximum statistics period. Valid values are "week", 174 Set the default maximum statistics period. Valid values are "week",
171 "month", "quarter" and "year". If unspecified, statistics are 175 "month", "quarter" and "year". If unspecified, statistics are
172 disabled. Default value: none. See also: "repo.max-stats". 176 disabled. Default value: none. See also: "repo.max-stats".
173 177
174mimetype.<ext>:: 178mimetype.<ext>::
175 Set the mimetype for the specified filename extension. This is used 179 Set the mimetype for the specified filename extension. This is used
176 by the `plain` command when returning blob content. 180 by the `plain` command when returning blob content.
177 181
178module-link:: 182module-link::
179 Text which will be used as the formatstring for a hyperlink when a 183 Text which will be used as the formatstring for a hyperlink when a
180 submodule is printed in a directory listing. The arguments for the 184 submodule is printed in a directory listing. The arguments for the
181 formatstring are the path and SHA1 of the submodule commit. Default 185 formatstring are the path and SHA1 of the submodule commit. Default
182 value: "./?repo=%s&page=commit&id=%s" 186 value: "./?repo=%s&page=commit&id=%s"
183 187
184nocache:: 188nocache::
185 If set to the value "1" caching will be disabled. This settings is 189 If set to the value "1" caching will be disabled. This settings is
186 deprecated, and will not be honored starting with cgit-1.0. Default 190 deprecated, and will not be honored starting with cgit-1.0. Default
187 value: "0". 191 value: "0".
188 192
189noplainemail:: 193noplainemail::
190 If set to "1" showing full author email adresses will be disabled. 194 If set to "1" showing full author email adresses will be disabled.
191 Default value: "0". 195 Default value: "0".
192 196
193noheader:: 197noheader::
194 Flag which, when set to "1", will make cgit omit the standard header 198 Flag which, when set to "1", will make cgit omit the standard header
195 on all pages. Default value: none. See also: "embedded". 199 on all pages. Default value: none. See also: "embedded".
196 200
197renamelimit:: 201renamelimit::
198 Maximum number of files to consider when detecting renames. The value 202 Maximum number of files to consider when detecting renames. The value
199 "-1" uses the compiletime value in git (for further info, look at 203 "-1" uses the compiletime value in git (for further info, look at
200 `man git-diff`). Default value: "-1". 204 `man git-diff`). Default value: "-1".
201 205
202repo.group:: 206repo.group::
203 Legacy alias for "section". This option is deprecated and will not be 207 Legacy alias for "section". This option is deprecated and will not be
204 supported in cgit-1.0. 208 supported in cgit-1.0.
205 209
206robots:: 210robots::
207 Text used as content for the "robots" meta-tag. Default value: 211 Text used as content for the "robots" meta-tag. Default value:
208 "index, nofollow". 212 "index, nofollow".
209 213
210root-desc:: 214root-desc::
211 Text printed below the heading on the repository index page. Default 215 Text printed below the heading on the repository index page. Default
212 value: "a fast webinterface for the git dscm". 216 value: "a fast webinterface for the git dscm".
213 217
214root-readme:: 218root-readme::
215 The content of the file specified with this option will be included 219 The content of the file specified with this option will be included
216 verbatim below the "about" link on the repository index page. Default 220 verbatim below the "about" link on the repository index page. Default
217 value: none. 221 value: none.
218 222
219root-title:: 223root-title::
220 Text printed as heading on the repository index page. Default value: 224 Text printed as heading on the repository index page. Default value:
221 "Git Repository Browser". 225 "Git Repository Browser".
222 226
223scan-path:: 227scan-path::
224 A path which will be scanned for repositories. If caching is enabled, 228 A path which will be scanned for repositories. If caching is enabled,
225 the result will be cached as a cgitrc include-file in the cache 229 the result will be cached as a cgitrc include-file in the cache
226 directory. Default value: none. See also: cache-scanrc-ttl. 230 directory. Default value: none. See also: cache-scanrc-ttl.
227 231
228section:: 232section::
229 The name of the current repository section - all repositories defined 233 The name of the current repository section - all repositories defined
230 after this option will inherit the current section name. Default value: 234 after this option will inherit the current section name. Default value:
231 none. 235 none.
232 236
233snapshots:: 237snapshots::
234 Text which specifies the default set of snapshot formats generated by 238 Text which specifies the default set of snapshot formats generated by
235 cgit. The value is a space-separated list of zero or more of the 239 cgit. The value is a space-separated list of zero or more of the
236 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 240 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
237 241
238source-filter:: 242source-filter::
239 Specifies a command which will be invoked to format plaintext blobs 243 Specifies a command which will be invoked to format plaintext blobs
240 in the tree view. The command will get the blob content on its STDIN 244 in the tree view. The command will get the blob content on its STDIN
241 and the name of the blob as its only command line argument. The STDOUT 245 and the name of the blob as its only command line argument. The STDOUT
242 from the command will be included verbatim as the blob contents, i.e. 246 from the command will be included verbatim as the blob contents, i.e.
243 this can be used to implement e.g. syntax highlighting. Default value: 247 this can be used to implement e.g. syntax highlighting. Default value:
244 none. 248 none.
245 249
246summary-branches:: 250summary-branches::
247 Specifies the number of branches to display in the repository "summary" 251 Specifies the number of branches to display in the repository "summary"
248 view. Default value: "10". 252 view. Default value: "10".
249 253
250summary-log:: 254summary-log::
251 Specifies the number of log entries to display in the repository 255 Specifies the number of log entries to display in the repository
252 "summary" view. Default value: "10". 256 "summary" view. Default value: "10".
253 257
254summary-tags:: 258summary-tags::
255 Specifies the number of tags to display in the repository "summary" 259 Specifies the number of tags to display in the repository "summary"
256 view. Default value: "10". 260 view. Default value: "10".
257 261
258virtual-root:: 262virtual-root::
259 Url which, if specified, will be used as root for all cgit links. It 263 Url which, if specified, will be used as root for all cgit links. It
260 will also cause cgit to generate 'virtual urls', i.e. urls like 264 will also cause cgit to generate 'virtual urls', i.e. urls like
261 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 265 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
262 value: none. 266 value: none.
263 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 267 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
264 same kind of virtual urls, so this option will probably be deprecated. 268 same kind of virtual urls, so this option will probably be deprecated.
265 269
266REPOSITORY SETTINGS 270REPOSITORY SETTINGS
267------------------- 271-------------------
268repo.about-filter:: 272repo.about-filter::
269 Override the default about-filter. Default value: <about-filter>. 273 Override the default about-filter. Default value: none. See also:
274 "enable-filter-overrides".
270 275
271repo.clone-url:: 276repo.clone-url::
272 A list of space-separated urls which can be used to clone this repo. 277 A list of space-separated urls which can be used to clone this repo.
273 Default value: none. 278 Default value: none.
274 279
275repo.commit-filter:: 280repo.commit-filter::
276 Override the default commit-filter. Default value: <commit-filter>. 281 Override the default commit-filter. Default value: none. See also:
282 "enable-filter-overrides".
277 283
278repo.defbranch:: 284repo.defbranch::
279 The name of the default branch for this repository. If no such branch 285 The name of the default branch for this repository. If no such branch
280 exists in the repository, the first branch name (when sorted) is used 286 exists in the repository, the first branch name (when sorted) is used
281 as default instead. Default value: "master". 287 as default instead. Default value: "master".
282 288
283repo.desc:: 289repo.desc::
284 The value to show as repository description. Default value: none. 290 The value to show as repository description. Default value: none.
285 291
286repo.enable-log-filecount:: 292repo.enable-log-filecount::
287 A flag which can be used to disable the global setting 293 A flag which can be used to disable the global setting
288 `enable-log-filecount'. Default value: none. 294 `enable-log-filecount'. Default value: none.
289 295
290repo.enable-log-linecount:: 296repo.enable-log-linecount::
291 A flag which can be used to disable the global setting 297 A flag which can be used to disable the global setting
292 `enable-log-linecount'. Default value: none. 298 `enable-log-linecount'. Default value: none.
293 299
294repo.max-stats:: 300repo.max-stats::
295 Override the default maximum statistics period. Valid values are equal 301 Override the default maximum statistics period. Valid values are equal
296 to the values specified for the global "max-stats" setting. Default 302 to the values specified for the global "max-stats" setting. Default
297 value: none. 303 value: none.
298 304
299repo.name:: 305repo.name::
300 The value to show as repository name. Default value: <repo.url>. 306 The value to show as repository name. Default value: <repo.url>.
301 307
302repo.owner:: 308repo.owner::
303 A value used to identify the owner of the repository. Default value: 309 A value used to identify the owner of the repository. Default value:
304 none. 310 none.
305 311
306repo.path:: 312repo.path::
307 An absolute path to the repository directory. For non-bare repositories 313 An absolute path to the repository directory. For non-bare repositories
308 this is the .git-directory. Default value: none. 314 this is the .git-directory. Default value: none.
309 315
310repo.readme:: 316repo.readme::
311 A path (relative to <repo.path>) which specifies a file to include 317 A path (relative to <repo.path>) which specifies a file to include
312 verbatim as the "About" page for this repo. Default value: none. 318 verbatim as the "About" page for this repo. Default value: none.
313 319
314repo.snapshots:: 320repo.snapshots::
315 A mask of allowed snapshot-formats for this repo, restricted by the 321 A mask of allowed snapshot-formats for this repo, restricted by the
316 "snapshots" global setting. Default value: <snapshots>. 322 "snapshots" global setting. Default value: <snapshots>.
317 323
318repo.section:: 324repo.section::
319 Override the current section name for this repository. Default value: 325 Override the current section name for this repository. Default value:
320 none. 326 none.
321 327
322repo.source-filter:: 328repo.source-filter::
323 Override the default source-filter. Default value: <source-filter>. 329 Override the default source-filter. Default value: none. See also:
330 "enable-filter-overrides".
324 331
325repo.url:: 332repo.url::
326 The relative url used to access the repository. This must be the first 333 The relative url used to access the repository. This must be the first
327 setting specified for each repo. Default value: none. 334 setting specified for each repo. Default value: none.
328 335
329 336
330REPOSITORY-SPECIFIC CGITRC FILE 337REPOSITORY-SPECIFIC CGITRC FILE
331------------------------------- 338-------------------------------
332When the option "scan-path" is used to auto-discover git repositories, cgit 339When the option "scan-path" is used to auto-discover git repositories, cgit
333will try to parse the file "cgitrc" within any found repository. Such a 340will try to parse the file "cgitrc" within any found repository. Such a
334repo-specific config file may contain any of the repo-specific options 341repo-specific config file may contain any of the repo-specific options
335described above, except "repo.url" and "repo.path". Also, in a repo-specific 342described above, except "repo.url" and "repo.path". Additionally, the "filter"
336config file, the "repo." prefix is dropped from the config option names. 343options are only acknowledged in repo-specific config files when
344"enable-filter-overrides" is set to "1".
345
346Note: the "repo." prefix is dropped from the option names in repo-specific
347config files, e.g. "repo.desc" becomes "desc".
337 348
338 349
339EXAMPLE CGITRC FILE 350EXAMPLE CGITRC FILE
340------------------- 351-------------------
341 352
342.... 353....
343# Enable caching of up to 1000 output entriess 354# Enable caching of up to 1000 output entriess
344cache-size=1000 355cache-size=1000
345 356
346 357
347# Specify some default clone prefixes 358# Specify some default clone prefixes
348clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 359clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
349 360
350# Specify the css url 361# Specify the css url
351css=/css/cgit.css 362css=/css/cgit.css
352 363
353 364
354# Show extra links for each repository on the index page 365# Show extra links for each repository on the index page
355enable-index-links=1 366enable-index-links=1
356 367
357 368
358# Show number of affected files per commit on the log pages 369# Show number of affected files per commit on the log pages
359enable-log-filecount=1 370enable-log-filecount=1
360 371
361 372
362# Show number of added/removed lines per commit on the log pages 373# Show number of added/removed lines per commit on the log pages
363enable-log-linecount=1 374enable-log-linecount=1
364 375
365 376
366# Add a cgit favicon 377# Add a cgit favicon
367favicon=/favicon.ico 378favicon=/favicon.ico
368 379
369 380
370# Use a custom logo 381# Use a custom logo
371logo=/img/mylogo.png 382logo=/img/mylogo.png
372 383
373 384
374# Enable statistics per week, month and quarter 385# Enable statistics per week, month and quarter
375max-stats=quarter 386max-stats=quarter
376 387
377 388
378# Set the title and heading of the repository index page 389# Set the title and heading of the repository index page
379root-title=foobar.com git repositories 390root-title=foobar.com git repositories
380 391
381 392
382# Set a subheading for the repository index page 393# Set a subheading for the repository index page
383root-desc=tracking the foobar development 394root-desc=tracking the foobar development
384 395
385 396
386# Include some more info about foobar.com on the index page 397# Include some more info about foobar.com on the index page
387root-readme=/var/www/htdocs/about.html 398root-readme=/var/www/htdocs/about.html
388 399
389 400
390# Allow download of tar.gz, tar.bz2 and zip-files 401# Allow download of tar.gz, tar.bz2 and zip-files
391snapshots=tar.gz tar.bz2 zip 402snapshots=tar.gz tar.bz2 zip
392 403
393 404
394## 405##
395## List of common mimetypes 406## List of common mimetypes
396## 407##
397 408
398mimetype.git=image/git 409mimetype.git=image/git
399mimetype.html=text/html 410mimetype.html=text/html
400mimetype.jpg=image/jpeg 411mimetype.jpg=image/jpeg
401mimetype.jpeg=image/jpeg 412mimetype.jpeg=image/jpeg
402mimetype.pdf=application/pdf 413mimetype.pdf=application/pdf
403mimetype.png=image/png 414mimetype.png=image/png
404mimetype.svg=image/svg+xml 415mimetype.svg=image/svg+xml
405 416
406 417
407## 418##
408## List of repositories. 419## List of repositories.
409## PS: Any repositories listed when repo.group is unset will not be 420## PS: Any repositories listed when repo.group is unset will not be
410## displayed under a group heading 421## displayed under a group heading
411## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 422## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
412## and included like this: 423## and included like this:
413## include=/etc/cgitrepos 424## include=/etc/cgitrepos
414## 425##
415 426
416 427
417repo.url=foo 428repo.url=foo
418repo.path=/pub/git/foo.git 429repo.path=/pub/git/foo.git
419repo.desc=the master foo repository 430repo.desc=the master foo repository
420repo.owner=fooman@foobar.com 431repo.owner=fooman@foobar.com
421repo.readme=info/web/about.html 432repo.readme=info/web/about.html
422 433
423 434
424repo.url=bar 435repo.url=bar
425repo.path=/pub/git/bar.git 436repo.path=/pub/git/bar.git
426repo.desc=the bars for your foo 437repo.desc=the bars for your foo
427repo.owner=barman@foobar.com 438repo.owner=barman@foobar.com
428repo.readme=info/web/about.html 439repo.readme=info/web/about.html
429 440
430 441
431# The next repositories will be displayed under the 'extras' heading 442# The next repositories will be displayed under the 'extras' heading
432repo.group=extras 443repo.group=extras
433 444
434 445
435repo.url=baz 446repo.url=baz
436repo.path=/pub/git/baz.git 447repo.path=/pub/git/baz.git
437repo.desc=a set of extensions for bar users 448repo.desc=a set of extensions for bar users
438 449
439repo.url=wiz 450repo.url=wiz
440repo.path=/pub/git/wiz.git 451repo.path=/pub/git/wiz.git
441repo.desc=the wizard of foo 452repo.desc=the wizard of foo
442 453
443 454
444# Add some mirrored repositories 455# Add some mirrored repositories
445repo.group=mirrors 456repo.group=mirrors
446 457
447 458
448repo.url=git 459repo.url=git
449repo.path=/pub/git/git.git 460repo.path=/pub/git/git.git
450repo.desc=the dscm 461repo.desc=the dscm
451 462
452 463
453repo.url=linux 464repo.url=linux
454repo.path=/pub/git/linux.git 465repo.path=/pub/git/linux.git
455repo.desc=the kernel 466repo.desc=the kernel
456 467
457# Disable adhoc downloads of this repo 468# Disable adhoc downloads of this repo
458repo.snapshots=0 469repo.snapshots=0
459 470
460# Disable line-counts for this repo 471# Disable line-counts for this repo
461repo.enable-log-linecount=0 472repo.enable-log-linecount=0
462 473
463# Restrict the max statistics period for this repo 474# Restrict the max statistics period for this repo
464repo.max-stats=month 475repo.max-stats=month
465.... 476....
466 477
467 478
468BUGS 479BUGS
469---- 480----
470Comments currently cannot appear on the same line as a setting; the comment 481Comments currently cannot appear on the same line as a setting; the comment
471will be included as part of the value. E.g. this line: 482will be included as part of the value. E.g. this line:
472 483
473 robots=index # allow indexing 484 robots=index # allow indexing
474 485
475will generate the following html element: 486will generate the following html element:
476 487
477 <meta name='robots' content='index # allow indexing'/> 488 <meta name='robots' content='index # allow indexing'/>
478 489
479 490
480 491
481AUTHOR 492AUTHOR
482------ 493------
483Lars Hjemli <hjemli@gmail.com> 494Lars Hjemli <hjemli@gmail.com>