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