summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c2
-rw-r--r--cgit.h1
-rw-r--r--cgitrc.5.txt4
-rw-r--r--ui-shared.c2
4 files changed, 9 insertions, 0 deletions
diff --git a/cgit.c b/cgit.c
index 608cab6..64d95f9 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,484 +1,486 @@
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 config_cb(const char *name, const char *value) 20void config_cb(const char *name, const char *value)
21{ 21{
22 if (!strcmp(name, "root-title")) 22 if (!strcmp(name, "root-title"))
23 ctx.cfg.root_title = xstrdup(value); 23 ctx.cfg.root_title = xstrdup(value);
24 else if (!strcmp(name, "root-desc")) 24 else if (!strcmp(name, "root-desc"))
25 ctx.cfg.root_desc = xstrdup(value); 25 ctx.cfg.root_desc = xstrdup(value);
26 else if (!strcmp(name, "root-readme")) 26 else if (!strcmp(name, "root-readme"))
27 ctx.cfg.root_readme = xstrdup(value); 27 ctx.cfg.root_readme = xstrdup(value);
28 else if (!strcmp(name, "css")) 28 else if (!strcmp(name, "css"))
29 ctx.cfg.css = xstrdup(value); 29 ctx.cfg.css = xstrdup(value);
30 else if (!strcmp(name, "favicon")) 30 else if (!strcmp(name, "favicon"))
31 ctx.cfg.favicon = xstrdup(value); 31 ctx.cfg.favicon = xstrdup(value);
32 else if (!strcmp(name, "footer")) 32 else if (!strcmp(name, "footer"))
33 ctx.cfg.footer = xstrdup(value); 33 ctx.cfg.footer = xstrdup(value);
34 else if (!strcmp(name, "header"))
35 ctx.cfg.header = xstrdup(value);
34 else if (!strcmp(name, "logo")) 36 else if (!strcmp(name, "logo"))
35 ctx.cfg.logo = xstrdup(value); 37 ctx.cfg.logo = xstrdup(value);
36 else if (!strcmp(name, "index-header")) 38 else if (!strcmp(name, "index-header"))
37 ctx.cfg.index_header = xstrdup(value); 39 ctx.cfg.index_header = xstrdup(value);
38 else if (!strcmp(name, "index-info")) 40 else if (!strcmp(name, "index-info"))
39 ctx.cfg.index_info = xstrdup(value); 41 ctx.cfg.index_info = xstrdup(value);
40 else if (!strcmp(name, "logo-link")) 42 else if (!strcmp(name, "logo-link"))
41 ctx.cfg.logo_link = xstrdup(value); 43 ctx.cfg.logo_link = xstrdup(value);
42 else if (!strcmp(name, "module-link")) 44 else if (!strcmp(name, "module-link"))
43 ctx.cfg.module_link = xstrdup(value); 45 ctx.cfg.module_link = xstrdup(value);
44 else if (!strcmp(name, "virtual-root")) { 46 else if (!strcmp(name, "virtual-root")) {
45 ctx.cfg.virtual_root = trim_end(value, '/'); 47 ctx.cfg.virtual_root = trim_end(value, '/');
46 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 48 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
47 ctx.cfg.virtual_root = ""; 49 ctx.cfg.virtual_root = "";
48 } else if (!strcmp(name, "nocache")) 50 } else if (!strcmp(name, "nocache"))
49 ctx.cfg.nocache = atoi(value); 51 ctx.cfg.nocache = atoi(value);
50 else if (!strcmp(name, "snapshots")) 52 else if (!strcmp(name, "snapshots"))
51 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 53 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
52 else if (!strcmp(name, "enable-index-links")) 54 else if (!strcmp(name, "enable-index-links"))
53 ctx.cfg.enable_index_links = atoi(value); 55 ctx.cfg.enable_index_links = atoi(value);
54 else if (!strcmp(name, "enable-log-filecount")) 56 else if (!strcmp(name, "enable-log-filecount"))
55 ctx.cfg.enable_log_filecount = atoi(value); 57 ctx.cfg.enable_log_filecount = atoi(value);
56 else if (!strcmp(name, "enable-log-linecount")) 58 else if (!strcmp(name, "enable-log-linecount"))
57 ctx.cfg.enable_log_linecount = atoi(value); 59 ctx.cfg.enable_log_linecount = atoi(value);
58 else if (!strcmp(name, "max-stats")) 60 else if (!strcmp(name, "max-stats"))
59 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 61 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
60 else if (!strcmp(name, "cache-size")) 62 else if (!strcmp(name, "cache-size"))
61 ctx.cfg.cache_size = atoi(value); 63 ctx.cfg.cache_size = atoi(value);
62 else if (!strcmp(name, "cache-root")) 64 else if (!strcmp(name, "cache-root"))
63 ctx.cfg.cache_root = xstrdup(value); 65 ctx.cfg.cache_root = xstrdup(value);
64 else if (!strcmp(name, "cache-root-ttl")) 66 else if (!strcmp(name, "cache-root-ttl"))
65 ctx.cfg.cache_root_ttl = atoi(value); 67 ctx.cfg.cache_root_ttl = atoi(value);
66 else if (!strcmp(name, "cache-repo-ttl")) 68 else if (!strcmp(name, "cache-repo-ttl"))
67 ctx.cfg.cache_repo_ttl = atoi(value); 69 ctx.cfg.cache_repo_ttl = atoi(value);
68 else if (!strcmp(name, "cache-static-ttl")) 70 else if (!strcmp(name, "cache-static-ttl"))
69 ctx.cfg.cache_static_ttl = atoi(value); 71 ctx.cfg.cache_static_ttl = atoi(value);
70 else if (!strcmp(name, "cache-dynamic-ttl")) 72 else if (!strcmp(name, "cache-dynamic-ttl"))
71 ctx.cfg.cache_dynamic_ttl = atoi(value); 73 ctx.cfg.cache_dynamic_ttl = atoi(value);
72 else if (!strcmp(name, "max-message-length")) 74 else if (!strcmp(name, "max-message-length"))
73 ctx.cfg.max_msg_len = atoi(value); 75 ctx.cfg.max_msg_len = atoi(value);
74 else if (!strcmp(name, "max-repodesc-length")) 76 else if (!strcmp(name, "max-repodesc-length"))
75 ctx.cfg.max_repodesc_len = atoi(value); 77 ctx.cfg.max_repodesc_len = atoi(value);
76 else if (!strcmp(name, "max-repo-count")) 78 else if (!strcmp(name, "max-repo-count"))
77 ctx.cfg.max_repo_count = atoi(value); 79 ctx.cfg.max_repo_count = atoi(value);
78 else if (!strcmp(name, "max-commit-count")) 80 else if (!strcmp(name, "max-commit-count"))
79 ctx.cfg.max_commit_count = atoi(value); 81 ctx.cfg.max_commit_count = atoi(value);
80 else if (!strcmp(name, "summary-log")) 82 else if (!strcmp(name, "summary-log"))
81 ctx.cfg.summary_log = atoi(value); 83 ctx.cfg.summary_log = atoi(value);
82 else if (!strcmp(name, "summary-branches")) 84 else if (!strcmp(name, "summary-branches"))
83 ctx.cfg.summary_branches = atoi(value); 85 ctx.cfg.summary_branches = atoi(value);
84 else if (!strcmp(name, "summary-tags")) 86 else if (!strcmp(name, "summary-tags"))
85 ctx.cfg.summary_tags = atoi(value); 87 ctx.cfg.summary_tags = atoi(value);
86 else if (!strcmp(name, "agefile")) 88 else if (!strcmp(name, "agefile"))
87 ctx.cfg.agefile = xstrdup(value); 89 ctx.cfg.agefile = xstrdup(value);
88 else if (!strcmp(name, "renamelimit")) 90 else if (!strcmp(name, "renamelimit"))
89 ctx.cfg.renamelimit = atoi(value); 91 ctx.cfg.renamelimit = atoi(value);
90 else if (!strcmp(name, "robots")) 92 else if (!strcmp(name, "robots"))
91 ctx.cfg.robots = xstrdup(value); 93 ctx.cfg.robots = xstrdup(value);
92 else if (!strcmp(name, "clone-prefix")) 94 else if (!strcmp(name, "clone-prefix"))
93 ctx.cfg.clone_prefix = xstrdup(value); 95 ctx.cfg.clone_prefix = xstrdup(value);
94 else if (!strcmp(name, "local-time")) 96 else if (!strcmp(name, "local-time"))
95 ctx.cfg.local_time = atoi(value); 97 ctx.cfg.local_time = atoi(value);
96 else if (!strcmp(name, "repo.group")) 98 else if (!strcmp(name, "repo.group"))
97 ctx.cfg.repo_group = xstrdup(value); 99 ctx.cfg.repo_group = xstrdup(value);
98 else if (!strcmp(name, "repo.url")) 100 else if (!strcmp(name, "repo.url"))
99 ctx.repo = cgit_add_repo(value); 101 ctx.repo = cgit_add_repo(value);
100 else if (!strcmp(name, "repo.name")) 102 else if (!strcmp(name, "repo.name"))
101 ctx.repo->name = xstrdup(value); 103 ctx.repo->name = xstrdup(value);
102 else if (ctx.repo && !strcmp(name, "repo.path")) 104 else if (ctx.repo && !strcmp(name, "repo.path"))
103 ctx.repo->path = trim_end(value, '/'); 105 ctx.repo->path = trim_end(value, '/');
104 else if (ctx.repo && !strcmp(name, "repo.clone-url")) 106 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
105 ctx.repo->clone_url = xstrdup(value); 107 ctx.repo->clone_url = xstrdup(value);
106 else if (ctx.repo && !strcmp(name, "repo.desc")) 108 else if (ctx.repo && !strcmp(name, "repo.desc"))
107 ctx.repo->desc = xstrdup(value); 109 ctx.repo->desc = xstrdup(value);
108 else if (ctx.repo && !strcmp(name, "repo.owner")) 110 else if (ctx.repo && !strcmp(name, "repo.owner"))
109 ctx.repo->owner = xstrdup(value); 111 ctx.repo->owner = xstrdup(value);
110 else if (ctx.repo && !strcmp(name, "repo.defbranch")) 112 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
111 ctx.repo->defbranch = xstrdup(value); 113 ctx.repo->defbranch = xstrdup(value);
112 else if (ctx.repo && !strcmp(name, "repo.snapshots")) 114 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
113 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ 115 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
114 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) 116 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
115 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 117 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
116 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) 118 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
117 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 119 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
118 else if (ctx.repo && !strcmp(name, "repo.max-stats")) 120 else if (ctx.repo && !strcmp(name, "repo.max-stats"))
119 ctx.repo->max_stats = cgit_find_stats_period(value, NULL); 121 ctx.repo->max_stats = cgit_find_stats_period(value, NULL);
120 else if (ctx.repo && !strcmp(name, "repo.module-link")) 122 else if (ctx.repo && !strcmp(name, "repo.module-link"))
121 ctx.repo->module_link= xstrdup(value); 123 ctx.repo->module_link= xstrdup(value);
122 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 124 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
123 if (*value == '/') 125 if (*value == '/')
124 ctx.repo->readme = xstrdup(value); 126 ctx.repo->readme = xstrdup(value);
125 else 127 else
126 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 128 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
127 } else if (!strcmp(name, "include")) 129 } else if (!strcmp(name, "include"))
128 parse_configfile(value, config_cb); 130 parse_configfile(value, config_cb);
129} 131}
130 132
131static void querystring_cb(const char *name, const char *value) 133static void querystring_cb(const char *name, const char *value)
132{ 134{
133 if (!strcmp(name,"r")) { 135 if (!strcmp(name,"r")) {
134 ctx.qry.repo = xstrdup(value); 136 ctx.qry.repo = xstrdup(value);
135 ctx.repo = cgit_get_repoinfo(value); 137 ctx.repo = cgit_get_repoinfo(value);
136 } else if (!strcmp(name, "p")) { 138 } else if (!strcmp(name, "p")) {
137 ctx.qry.page = xstrdup(value); 139 ctx.qry.page = xstrdup(value);
138 } else if (!strcmp(name, "url")) { 140 } else if (!strcmp(name, "url")) {
139 ctx.qry.url = xstrdup(value); 141 ctx.qry.url = xstrdup(value);
140 cgit_parse_url(value); 142 cgit_parse_url(value);
141 } else if (!strcmp(name, "qt")) { 143 } else if (!strcmp(name, "qt")) {
142 ctx.qry.grep = xstrdup(value); 144 ctx.qry.grep = xstrdup(value);
143 } else if (!strcmp(name, "q")) { 145 } else if (!strcmp(name, "q")) {
144 ctx.qry.search = xstrdup(value); 146 ctx.qry.search = xstrdup(value);
145 } else if (!strcmp(name, "h")) { 147 } else if (!strcmp(name, "h")) {
146 ctx.qry.head = xstrdup(value); 148 ctx.qry.head = xstrdup(value);
147 ctx.qry.has_symref = 1; 149 ctx.qry.has_symref = 1;
148 } else if (!strcmp(name, "id")) { 150 } else if (!strcmp(name, "id")) {
149 ctx.qry.sha1 = xstrdup(value); 151 ctx.qry.sha1 = xstrdup(value);
150 ctx.qry.has_sha1 = 1; 152 ctx.qry.has_sha1 = 1;
151 } else if (!strcmp(name, "id2")) { 153 } else if (!strcmp(name, "id2")) {
152 ctx.qry.sha2 = xstrdup(value); 154 ctx.qry.sha2 = xstrdup(value);
153 ctx.qry.has_sha1 = 1; 155 ctx.qry.has_sha1 = 1;
154 } else if (!strcmp(name, "ofs")) { 156 } else if (!strcmp(name, "ofs")) {
155 ctx.qry.ofs = atoi(value); 157 ctx.qry.ofs = atoi(value);
156 } else if (!strcmp(name, "path")) { 158 } else if (!strcmp(name, "path")) {
157 ctx.qry.path = trim_end(value, '/'); 159 ctx.qry.path = trim_end(value, '/');
158 } else if (!strcmp(name, "name")) { 160 } else if (!strcmp(name, "name")) {
159 ctx.qry.name = xstrdup(value); 161 ctx.qry.name = xstrdup(value);
160 } else if (!strcmp(name, "mimetype")) { 162 } else if (!strcmp(name, "mimetype")) {
161 ctx.qry.mimetype = xstrdup(value); 163 ctx.qry.mimetype = xstrdup(value);
162 } else if (!strcmp(name, "s")){ 164 } else if (!strcmp(name, "s")){
163 ctx.qry.sort = xstrdup(value); 165 ctx.qry.sort = xstrdup(value);
164 } else if (!strcmp(name, "showmsg")) { 166 } else if (!strcmp(name, "showmsg")) {
165 ctx.qry.showmsg = atoi(value); 167 ctx.qry.showmsg = atoi(value);
166 } else if (!strcmp(name, "period")) { 168 } else if (!strcmp(name, "period")) {
167 ctx.qry.period = xstrdup(value); 169 ctx.qry.period = xstrdup(value);
168 } 170 }
169} 171}
170 172
171static void prepare_context(struct cgit_context *ctx) 173static void prepare_context(struct cgit_context *ctx)
172{ 174{
173 memset(ctx, 0, sizeof(ctx)); 175 memset(ctx, 0, sizeof(ctx));
174 ctx->cfg.agefile = "info/web/last-modified"; 176 ctx->cfg.agefile = "info/web/last-modified";
175 ctx->cfg.nocache = 0; 177 ctx->cfg.nocache = 0;
176 ctx->cfg.cache_size = 0; 178 ctx->cfg.cache_size = 0;
177 ctx->cfg.cache_dynamic_ttl = 5; 179 ctx->cfg.cache_dynamic_ttl = 5;
178 ctx->cfg.cache_max_create_time = 5; 180 ctx->cfg.cache_max_create_time = 5;
179 ctx->cfg.cache_repo_ttl = 5; 181 ctx->cfg.cache_repo_ttl = 5;
180 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 182 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
181 ctx->cfg.cache_root_ttl = 5; 183 ctx->cfg.cache_root_ttl = 5;
182 ctx->cfg.cache_static_ttl = -1; 184 ctx->cfg.cache_static_ttl = -1;
183 ctx->cfg.css = "/cgit.css"; 185 ctx->cfg.css = "/cgit.css";
184 ctx->cfg.logo = "/git-logo.png"; 186 ctx->cfg.logo = "/git-logo.png";
185 ctx->cfg.local_time = 0; 187 ctx->cfg.local_time = 0;
186 ctx->cfg.max_repo_count = 50; 188 ctx->cfg.max_repo_count = 50;
187 ctx->cfg.max_commit_count = 50; 189 ctx->cfg.max_commit_count = 50;
188 ctx->cfg.max_lock_attempts = 5; 190 ctx->cfg.max_lock_attempts = 5;
189 ctx->cfg.max_msg_len = 80; 191 ctx->cfg.max_msg_len = 80;
190 ctx->cfg.max_repodesc_len = 80; 192 ctx->cfg.max_repodesc_len = 80;
191 ctx->cfg.max_stats = 0; 193 ctx->cfg.max_stats = 0;
192 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 194 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
193 ctx->cfg.renamelimit = -1; 195 ctx->cfg.renamelimit = -1;
194 ctx->cfg.robots = "index, nofollow"; 196 ctx->cfg.robots = "index, nofollow";
195 ctx->cfg.root_title = "Git repository browser"; 197 ctx->cfg.root_title = "Git repository browser";
196 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 198 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
197 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 199 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
198 ctx->cfg.summary_branches = 10; 200 ctx->cfg.summary_branches = 10;
199 ctx->cfg.summary_log = 10; 201 ctx->cfg.summary_log = 10;
200 ctx->cfg.summary_tags = 10; 202 ctx->cfg.summary_tags = 10;
201 ctx->page.mimetype = "text/html"; 203 ctx->page.mimetype = "text/html";
202 ctx->page.charset = PAGE_ENCODING; 204 ctx->page.charset = PAGE_ENCODING;
203 ctx->page.filename = NULL; 205 ctx->page.filename = NULL;
204 ctx->page.size = 0; 206 ctx->page.size = 0;
205 ctx->page.modified = time(NULL); 207 ctx->page.modified = time(NULL);
206 ctx->page.expires = ctx->page.modified; 208 ctx->page.expires = ctx->page.modified;
207} 209}
208 210
209struct refmatch { 211struct refmatch {
210 char *req_ref; 212 char *req_ref;
211 char *first_ref; 213 char *first_ref;
212 int match; 214 int match;
213}; 215};
214 216
215int find_current_ref(const char *refname, const unsigned char *sha1, 217int find_current_ref(const char *refname, const unsigned char *sha1,
216 int flags, void *cb_data) 218 int flags, void *cb_data)
217{ 219{
218 struct refmatch *info; 220 struct refmatch *info;
219 221
220 info = (struct refmatch *)cb_data; 222 info = (struct refmatch *)cb_data;
221 if (!strcmp(refname, info->req_ref)) 223 if (!strcmp(refname, info->req_ref))
222 info->match = 1; 224 info->match = 1;
223 if (!info->first_ref) 225 if (!info->first_ref)
224 info->first_ref = xstrdup(refname); 226 info->first_ref = xstrdup(refname);
225 return info->match; 227 return info->match;
226} 228}
227 229
228char *find_default_branch(struct cgit_repo *repo) 230char *find_default_branch(struct cgit_repo *repo)
229{ 231{
230 struct refmatch info; 232 struct refmatch info;
231 char *ref; 233 char *ref;
232 234
233 info.req_ref = repo->defbranch; 235 info.req_ref = repo->defbranch;
234 info.first_ref = NULL; 236 info.first_ref = NULL;
235 info.match = 0; 237 info.match = 0;
236 for_each_branch_ref(find_current_ref, &info); 238 for_each_branch_ref(find_current_ref, &info);
237 if (info.match) 239 if (info.match)
238 ref = info.req_ref; 240 ref = info.req_ref;
239 else 241 else
240 ref = info.first_ref; 242 ref = info.first_ref;
241 if (ref) 243 if (ref)
242 ref = xstrdup(ref); 244 ref = xstrdup(ref);
243 return ref; 245 return ref;
244} 246}
245 247
246static int prepare_repo_cmd(struct cgit_context *ctx) 248static int prepare_repo_cmd(struct cgit_context *ctx)
247{ 249{
248 char *tmp; 250 char *tmp;
249 unsigned char sha1[20]; 251 unsigned char sha1[20];
250 int nongit = 0; 252 int nongit = 0;
251 253
252 setenv("GIT_DIR", ctx->repo->path, 1); 254 setenv("GIT_DIR", ctx->repo->path, 1);
253 setup_git_directory_gently(&nongit); 255 setup_git_directory_gently(&nongit);
254 if (nongit) { 256 if (nongit) {
255 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 257 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
256 "config error"); 258 "config error");
257 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 259 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
258 ctx->repo = NULL; 260 ctx->repo = NULL;
259 cgit_print_http_headers(ctx); 261 cgit_print_http_headers(ctx);
260 cgit_print_docstart(ctx); 262 cgit_print_docstart(ctx);
261 cgit_print_pageheader(ctx); 263 cgit_print_pageheader(ctx);
262 cgit_print_error(tmp); 264 cgit_print_error(tmp);
263 cgit_print_docend(); 265 cgit_print_docend();
264 return 1; 266 return 1;
265 } 267 }
266 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 268 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
267 269
268 if (!ctx->qry.head) { 270 if (!ctx->qry.head) {
269 ctx->qry.nohead = 1; 271 ctx->qry.nohead = 1;
270 ctx->qry.head = find_default_branch(ctx->repo); 272 ctx->qry.head = find_default_branch(ctx->repo);
271 ctx->repo->defbranch = ctx->qry.head; 273 ctx->repo->defbranch = ctx->qry.head;
272 } 274 }
273 275
274 if (!ctx->qry.head) { 276 if (!ctx->qry.head) {
275 cgit_print_http_headers(ctx); 277 cgit_print_http_headers(ctx);
276 cgit_print_docstart(ctx); 278 cgit_print_docstart(ctx);
277 cgit_print_pageheader(ctx); 279 cgit_print_pageheader(ctx);
278 cgit_print_error("Repository seems to be empty"); 280 cgit_print_error("Repository seems to be empty");
279 cgit_print_docend(); 281 cgit_print_docend();
280 return 1; 282 return 1;
281 } 283 }
282 284
283 if (get_sha1(ctx->qry.head, sha1)) { 285 if (get_sha1(ctx->qry.head, sha1)) {
284 tmp = xstrdup(ctx->qry.head); 286 tmp = xstrdup(ctx->qry.head);
285 ctx->qry.head = ctx->repo->defbranch; 287 ctx->qry.head = ctx->repo->defbranch;
286 cgit_print_http_headers(ctx); 288 cgit_print_http_headers(ctx);
287 cgit_print_docstart(ctx); 289 cgit_print_docstart(ctx);
288 cgit_print_pageheader(ctx); 290 cgit_print_pageheader(ctx);
289 cgit_print_error(fmt("Invalid branch: %s", tmp)); 291 cgit_print_error(fmt("Invalid branch: %s", tmp));
290 cgit_print_docend(); 292 cgit_print_docend();
291 return 1; 293 return 1;
292 } 294 }
293 return 0; 295 return 0;
294} 296}
295 297
296static void process_request(void *cbdata) 298static void process_request(void *cbdata)
297{ 299{
298 struct cgit_context *ctx = cbdata; 300 struct cgit_context *ctx = cbdata;
299 struct cgit_cmd *cmd; 301 struct cgit_cmd *cmd;
300 302
301 cmd = cgit_get_cmd(ctx); 303 cmd = cgit_get_cmd(ctx);
302 if (!cmd) { 304 if (!cmd) {
303 ctx->page.title = "cgit error"; 305 ctx->page.title = "cgit error";
304 cgit_print_http_headers(ctx); 306 cgit_print_http_headers(ctx);
305 cgit_print_docstart(ctx); 307 cgit_print_docstart(ctx);
306 cgit_print_pageheader(ctx); 308 cgit_print_pageheader(ctx);
307 cgit_print_error("Invalid request"); 309 cgit_print_error("Invalid request");
308 cgit_print_docend(); 310 cgit_print_docend();
309 return; 311 return;
310 } 312 }
311 313
312 if (cmd->want_repo && !ctx->repo) { 314 if (cmd->want_repo && !ctx->repo) {
313 cgit_print_http_headers(ctx); 315 cgit_print_http_headers(ctx);
314 cgit_print_docstart(ctx); 316 cgit_print_docstart(ctx);
315 cgit_print_pageheader(ctx); 317 cgit_print_pageheader(ctx);
316 cgit_print_error(fmt("No repository selected")); 318 cgit_print_error(fmt("No repository selected"));
317 cgit_print_docend(); 319 cgit_print_docend();
318 return; 320 return;
319 } 321 }
320 322
321 if (ctx->repo && prepare_repo_cmd(ctx)) 323 if (ctx->repo && prepare_repo_cmd(ctx))
322 return; 324 return;
323 325
324 if (cmd->want_layout) { 326 if (cmd->want_layout) {
325 cgit_print_http_headers(ctx); 327 cgit_print_http_headers(ctx);
326 cgit_print_docstart(ctx); 328 cgit_print_docstart(ctx);
327 cgit_print_pageheader(ctx); 329 cgit_print_pageheader(ctx);
328 } 330 }
329 331
330 cmd->fn(ctx); 332 cmd->fn(ctx);
331 333
332 if (cmd->want_layout) 334 if (cmd->want_layout)
333 cgit_print_docend(); 335 cgit_print_docend();
334} 336}
335 337
336int cmp_repos(const void *a, const void *b) 338int cmp_repos(const void *a, const void *b)
337{ 339{
338 const struct cgit_repo *ra = a, *rb = b; 340 const struct cgit_repo *ra = a, *rb = b;
339 return strcmp(ra->url, rb->url); 341 return strcmp(ra->url, rb->url);
340} 342}
341 343
342void print_repo(struct cgit_repo *repo) 344void print_repo(struct cgit_repo *repo)
343{ 345{
344 printf("repo.url=%s\n", repo->url); 346 printf("repo.url=%s\n", repo->url);
345 printf("repo.name=%s\n", repo->name); 347 printf("repo.name=%s\n", repo->name);
346 printf("repo.path=%s\n", repo->path); 348 printf("repo.path=%s\n", repo->path);
347 if (repo->owner) 349 if (repo->owner)
348 printf("repo.owner=%s\n", repo->owner); 350 printf("repo.owner=%s\n", repo->owner);
349 if (repo->desc) 351 if (repo->desc)
350 printf("repo.desc=%s\n", repo->desc); 352 printf("repo.desc=%s\n", repo->desc);
351 if (repo->readme) 353 if (repo->readme)
352 printf("repo.readme=%s\n", repo->readme); 354 printf("repo.readme=%s\n", repo->readme);
353 printf("\n"); 355 printf("\n");
354} 356}
355 357
356void print_repolist(struct cgit_repolist *list) 358void print_repolist(struct cgit_repolist *list)
357{ 359{
358 int i; 360 int i;
359 361
360 for(i = 0; i < list->count; i++) 362 for(i = 0; i < list->count; i++)
361 print_repo(&list->repos[i]); 363 print_repo(&list->repos[i]);
362} 364}
363 365
364 366
365static void cgit_parse_args(int argc, const char **argv) 367static void cgit_parse_args(int argc, const char **argv)
366{ 368{
367 int i; 369 int i;
368 int scan = 0; 370 int scan = 0;
369 371
370 for (i = 1; i < argc; i++) { 372 for (i = 1; i < argc; i++) {
371 if (!strncmp(argv[i], "--cache=", 8)) { 373 if (!strncmp(argv[i], "--cache=", 8)) {
372 ctx.cfg.cache_root = xstrdup(argv[i]+8); 374 ctx.cfg.cache_root = xstrdup(argv[i]+8);
373 } 375 }
374 if (!strcmp(argv[i], "--nocache")) { 376 if (!strcmp(argv[i], "--nocache")) {
375 ctx.cfg.nocache = 1; 377 ctx.cfg.nocache = 1;
376 } 378 }
377 if (!strncmp(argv[i], "--query=", 8)) { 379 if (!strncmp(argv[i], "--query=", 8)) {
378 ctx.qry.raw = xstrdup(argv[i]+8); 380 ctx.qry.raw = xstrdup(argv[i]+8);
379 } 381 }
380 if (!strncmp(argv[i], "--repo=", 7)) { 382 if (!strncmp(argv[i], "--repo=", 7)) {
381 ctx.qry.repo = xstrdup(argv[i]+7); 383 ctx.qry.repo = xstrdup(argv[i]+7);
382 } 384 }
383 if (!strncmp(argv[i], "--page=", 7)) { 385 if (!strncmp(argv[i], "--page=", 7)) {
384 ctx.qry.page = xstrdup(argv[i]+7); 386 ctx.qry.page = xstrdup(argv[i]+7);
385 } 387 }
386 if (!strncmp(argv[i], "--head=", 7)) { 388 if (!strncmp(argv[i], "--head=", 7)) {
387 ctx.qry.head = xstrdup(argv[i]+7); 389 ctx.qry.head = xstrdup(argv[i]+7);
388 ctx.qry.has_symref = 1; 390 ctx.qry.has_symref = 1;
389 } 391 }
390 if (!strncmp(argv[i], "--sha1=", 7)) { 392 if (!strncmp(argv[i], "--sha1=", 7)) {
391 ctx.qry.sha1 = xstrdup(argv[i]+7); 393 ctx.qry.sha1 = xstrdup(argv[i]+7);
392 ctx.qry.has_sha1 = 1; 394 ctx.qry.has_sha1 = 1;
393 } 395 }
394 if (!strncmp(argv[i], "--ofs=", 6)) { 396 if (!strncmp(argv[i], "--ofs=", 6)) {
395 ctx.qry.ofs = atoi(argv[i]+6); 397 ctx.qry.ofs = atoi(argv[i]+6);
396 } 398 }
397 if (!strncmp(argv[i], "--scan-tree=", 12)) { 399 if (!strncmp(argv[i], "--scan-tree=", 12)) {
398 scan++; 400 scan++;
399 scan_tree(argv[i] + 12); 401 scan_tree(argv[i] + 12);
400 } 402 }
401 } 403 }
402 if (scan) { 404 if (scan) {
403 qsort(cgit_repolist.repos, cgit_repolist.count, 405 qsort(cgit_repolist.repos, cgit_repolist.count,
404 sizeof(struct cgit_repo), cmp_repos); 406 sizeof(struct cgit_repo), cmp_repos);
405 print_repolist(&cgit_repolist); 407 print_repolist(&cgit_repolist);
406 exit(0); 408 exit(0);
407 } 409 }
408} 410}
409 411
410static int calc_ttl() 412static int calc_ttl()
411{ 413{
412 if (!ctx.repo) 414 if (!ctx.repo)
413 return ctx.cfg.cache_root_ttl; 415 return ctx.cfg.cache_root_ttl;
414 416
415 if (!ctx.qry.page) 417 if (!ctx.qry.page)
416 return ctx.cfg.cache_repo_ttl; 418 return ctx.cfg.cache_repo_ttl;
417 419
418 if (ctx.qry.has_symref) 420 if (ctx.qry.has_symref)
419 return ctx.cfg.cache_dynamic_ttl; 421 return ctx.cfg.cache_dynamic_ttl;
420 422
421 if (ctx.qry.has_sha1) 423 if (ctx.qry.has_sha1)
422 return ctx.cfg.cache_static_ttl; 424 return ctx.cfg.cache_static_ttl;
423 425
424 return ctx.cfg.cache_repo_ttl; 426 return ctx.cfg.cache_repo_ttl;
425} 427}
426 428
427int main(int argc, const char **argv) 429int main(int argc, const char **argv)
428{ 430{
429 const char *cgit_config_env = getenv("CGIT_CONFIG"); 431 const char *cgit_config_env = getenv("CGIT_CONFIG");
430 const char *path; 432 const char *path;
431 char *qry; 433 char *qry;
432 int err, ttl; 434 int err, ttl;
433 435
434 prepare_context(&ctx); 436 prepare_context(&ctx);
435 cgit_repolist.length = 0; 437 cgit_repolist.length = 0;
436 cgit_repolist.count = 0; 438 cgit_repolist.count = 0;
437 cgit_repolist.repos = NULL; 439 cgit_repolist.repos = NULL;
438 440
439 if (getenv("SCRIPT_NAME")) 441 if (getenv("SCRIPT_NAME"))
440 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME")); 442 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME"));
441 if (getenv("QUERY_STRING")) 443 if (getenv("QUERY_STRING"))
442 ctx.qry.raw = xstrdup(getenv("QUERY_STRING")); 444 ctx.qry.raw = xstrdup(getenv("QUERY_STRING"));
443 cgit_parse_args(argc, argv); 445 cgit_parse_args(argc, argv);
444 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG, 446 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG,
445 config_cb); 447 config_cb);
446 ctx.repo = NULL; 448 ctx.repo = NULL;
447 http_parse_querystring(ctx.qry.raw, querystring_cb); 449 http_parse_querystring(ctx.qry.raw, querystring_cb);
448 450
449 /* If virtual-root isn't specified in cgitrc, lets pretend 451 /* If virtual-root isn't specified in cgitrc, lets pretend
450 * that virtual-root equals SCRIPT_NAME. 452 * that virtual-root equals SCRIPT_NAME.
451 */ 453 */
452 if (!ctx.cfg.virtual_root) 454 if (!ctx.cfg.virtual_root)
453 ctx.cfg.virtual_root = ctx.cfg.script_name; 455 ctx.cfg.virtual_root = ctx.cfg.script_name;
454 456
455 /* If no url parameter is specified on the querystring, lets 457 /* If no url parameter is specified on the querystring, lets
456 * use PATH_INFO as url. This allows cgit to work with virtual 458 * use PATH_INFO as url. This allows cgit to work with virtual
457 * urls without the need for rewriterules in the webserver (as 459 * urls without the need for rewriterules in the webserver (as
458 * long as PATH_INFO is included in the cache lookup key). 460 * long as PATH_INFO is included in the cache lookup key).
459 */ 461 */
460 path = getenv("PATH_INFO"); 462 path = getenv("PATH_INFO");
461 if (!ctx.qry.url && path) { 463 if (!ctx.qry.url && path) {
462 if (path[0] == '/') 464 if (path[0] == '/')
463 path++; 465 path++;
464 ctx.qry.url = xstrdup(path); 466 ctx.qry.url = xstrdup(path);
465 if (ctx.qry.raw) { 467 if (ctx.qry.raw) {
466 qry = ctx.qry.raw; 468 qry = ctx.qry.raw;
467 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 469 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
468 free(qry); 470 free(qry);
469 } else 471 } else
470 ctx.qry.raw = ctx.qry.url; 472 ctx.qry.raw = ctx.qry.url;
471 cgit_parse_url(ctx.qry.url); 473 cgit_parse_url(ctx.qry.url);
472 } 474 }
473 475
474 ttl = calc_ttl(); 476 ttl = calc_ttl();
475 ctx.page.expires += ttl*60; 477 ctx.page.expires += ttl*60;
476 if (ctx.cfg.nocache) 478 if (ctx.cfg.nocache)
477 ctx.cfg.cache_size = 0; 479 ctx.cfg.cache_size = 0;
478 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 480 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
479 ctx.qry.raw, ttl, process_request, &ctx); 481 ctx.qry.raw, ttl, process_request, &ctx);
480 if (err) 482 if (err)
481 cgit_print_error(fmt("Error processing page: %s (%d)", 483 cgit_print_error(fmt("Error processing page: %s (%d)",
482 strerror(err), err)); 484 strerror(err), err));
483 return err; 485 return err;
484} 486}
diff --git a/cgit.h b/cgit.h
index 4fe94c6..4942b96 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,243 +1,244 @@
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 <xdiff/xdiff.h> 18#include <xdiff/xdiff.h>
19#include <utf8.h> 19#include <utf8.h>
20 20
21 21
22/* 22/*
23 * Dateformats used on misc. pages 23 * Dateformats used on misc. pages
24 */ 24 */
25#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 25#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
26#define FMT_SHORTDATE "%Y-%m-%d" 26#define FMT_SHORTDATE "%Y-%m-%d"
27#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 27#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
28 28
29 29
30/* 30/*
31 * Limits used for relative dates 31 * Limits used for relative dates
32 */ 32 */
33#define TM_MIN 60 33#define TM_MIN 60
34#define TM_HOUR (TM_MIN * 60) 34#define TM_HOUR (TM_MIN * 60)
35#define TM_DAY (TM_HOUR * 24) 35#define TM_DAY (TM_HOUR * 24)
36#define TM_WEEK (TM_DAY * 7) 36#define TM_WEEK (TM_DAY * 7)
37#define TM_YEAR (TM_DAY * 365) 37#define TM_YEAR (TM_DAY * 365)
38#define TM_MONTH (TM_YEAR / 12.0) 38#define TM_MONTH (TM_YEAR / 12.0)
39 39
40 40
41/* 41/*
42 * Default encoding 42 * Default encoding
43 */ 43 */
44#define PAGE_ENCODING "UTF-8" 44#define PAGE_ENCODING "UTF-8"
45 45
46typedef void (*configfn)(const char *name, const char *value); 46typedef void (*configfn)(const char *name, const char *value);
47typedef void (*filepair_fn)(struct diff_filepair *pair); 47typedef void (*filepair_fn)(struct diff_filepair *pair);
48typedef void (*linediff_fn)(char *line, int len); 48typedef void (*linediff_fn)(char *line, int len);
49 49
50struct cgit_repo { 50struct cgit_repo {
51 char *url; 51 char *url;
52 char *name; 52 char *name;
53 char *path; 53 char *path;
54 char *desc; 54 char *desc;
55 char *owner; 55 char *owner;
56 char *defbranch; 56 char *defbranch;
57 char *group; 57 char *group;
58 char *module_link; 58 char *module_link;
59 char *readme; 59 char *readme;
60 char *clone_url; 60 char *clone_url;
61 int snapshots; 61 int snapshots;
62 int enable_log_filecount; 62 int enable_log_filecount;
63 int enable_log_linecount; 63 int enable_log_linecount;
64 int max_stats; 64 int max_stats;
65 time_t mtime; 65 time_t mtime;
66}; 66};
67 67
68struct cgit_repolist { 68struct cgit_repolist {
69 int length; 69 int length;
70 int count; 70 int count;
71 struct cgit_repo *repos; 71 struct cgit_repo *repos;
72}; 72};
73 73
74struct commitinfo { 74struct commitinfo {
75 struct commit *commit; 75 struct commit *commit;
76 char *author; 76 char *author;
77 char *author_email; 77 char *author_email;
78 unsigned long author_date; 78 unsigned long author_date;
79 char *committer; 79 char *committer;
80 char *committer_email; 80 char *committer_email;
81 unsigned long committer_date; 81 unsigned long committer_date;
82 char *subject; 82 char *subject;
83 char *msg; 83 char *msg;
84 char *msg_encoding; 84 char *msg_encoding;
85}; 85};
86 86
87struct taginfo { 87struct taginfo {
88 char *tagger; 88 char *tagger;
89 char *tagger_email; 89 char *tagger_email;
90 unsigned long tagger_date; 90 unsigned long tagger_date;
91 char *msg; 91 char *msg;
92}; 92};
93 93
94struct refinfo { 94struct refinfo {
95 const char *refname; 95 const char *refname;
96 struct object *object; 96 struct object *object;
97 union { 97 union {
98 struct taginfo *tag; 98 struct taginfo *tag;
99 struct commitinfo *commit; 99 struct commitinfo *commit;
100 }; 100 };
101}; 101};
102 102
103struct reflist { 103struct reflist {
104 struct refinfo **refs; 104 struct refinfo **refs;
105 int alloc; 105 int alloc;
106 int count; 106 int count;
107}; 107};
108 108
109struct cgit_query { 109struct cgit_query {
110 int has_symref; 110 int has_symref;
111 int has_sha1; 111 int has_sha1;
112 char *raw; 112 char *raw;
113 char *repo; 113 char *repo;
114 char *page; 114 char *page;
115 char *search; 115 char *search;
116 char *grep; 116 char *grep;
117 char *head; 117 char *head;
118 char *sha1; 118 char *sha1;
119 char *sha2; 119 char *sha2;
120 char *path; 120 char *path;
121 char *name; 121 char *name;
122 char *mimetype; 122 char *mimetype;
123 char *url; 123 char *url;
124 char *period; 124 char *period;
125 int ofs; 125 int ofs;
126 int nohead; 126 int nohead;
127 char *sort; 127 char *sort;
128 int showmsg; 128 int showmsg;
129}; 129};
130 130
131struct cgit_config { 131struct cgit_config {
132 char *agefile; 132 char *agefile;
133 char *cache_root; 133 char *cache_root;
134 char *clone_prefix; 134 char *clone_prefix;
135 char *css; 135 char *css;
136 char *favicon; 136 char *favicon;
137 char *footer; 137 char *footer;
138 char *header;
138 char *index_header; 139 char *index_header;
139 char *index_info; 140 char *index_info;
140 char *logo; 141 char *logo;
141 char *logo_link; 142 char *logo_link;
142 char *module_link; 143 char *module_link;
143 char *repo_group; 144 char *repo_group;
144 char *robots; 145 char *robots;
145 char *root_title; 146 char *root_title;
146 char *root_desc; 147 char *root_desc;
147 char *root_readme; 148 char *root_readme;
148 char *script_name; 149 char *script_name;
149 char *virtual_root; 150 char *virtual_root;
150 int cache_size; 151 int cache_size;
151 int cache_dynamic_ttl; 152 int cache_dynamic_ttl;
152 int cache_max_create_time; 153 int cache_max_create_time;
153 int cache_repo_ttl; 154 int cache_repo_ttl;
154 int cache_root_ttl; 155 int cache_root_ttl;
155 int cache_static_ttl; 156 int cache_static_ttl;
156 int enable_index_links; 157 int enable_index_links;
157 int enable_log_filecount; 158 int enable_log_filecount;
158 int enable_log_linecount; 159 int enable_log_linecount;
159 int local_time; 160 int local_time;
160 int max_repo_count; 161 int max_repo_count;
161 int max_commit_count; 162 int max_commit_count;
162 int max_lock_attempts; 163 int max_lock_attempts;
163 int max_msg_len; 164 int max_msg_len;
164 int max_repodesc_len; 165 int max_repodesc_len;
165 int max_stats; 166 int max_stats;
166 int nocache; 167 int nocache;
167 int renamelimit; 168 int renamelimit;
168 int snapshots; 169 int snapshots;
169 int summary_branches; 170 int summary_branches;
170 int summary_log; 171 int summary_log;
171 int summary_tags; 172 int summary_tags;
172}; 173};
173 174
174struct cgit_page { 175struct cgit_page {
175 time_t modified; 176 time_t modified;
176 time_t expires; 177 time_t expires;
177 size_t size; 178 size_t size;
178 char *mimetype; 179 char *mimetype;
179 char *charset; 180 char *charset;
180 char *filename; 181 char *filename;
181 char *title; 182 char *title;
182}; 183};
183 184
184struct cgit_context { 185struct cgit_context {
185 struct cgit_query qry; 186 struct cgit_query qry;
186 struct cgit_config cfg; 187 struct cgit_config cfg;
187 struct cgit_repo *repo; 188 struct cgit_repo *repo;
188 struct cgit_page page; 189 struct cgit_page page;
189}; 190};
190 191
191struct cgit_snapshot_format { 192struct cgit_snapshot_format {
192 const char *suffix; 193 const char *suffix;
193 const char *mimetype; 194 const char *mimetype;
194 write_archive_fn_t write_func; 195 write_archive_fn_t write_func;
195 int bit; 196 int bit;
196}; 197};
197 198
198extern const char *cgit_version; 199extern const char *cgit_version;
199 200
200extern struct cgit_repolist cgit_repolist; 201extern struct cgit_repolist cgit_repolist;
201extern struct cgit_context ctx; 202extern struct cgit_context ctx;
202extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 203extern const struct cgit_snapshot_format cgit_snapshot_formats[];
203 204
204extern struct cgit_repo *cgit_add_repo(const char *url); 205extern struct cgit_repo *cgit_add_repo(const char *url);
205extern struct cgit_repo *cgit_get_repoinfo(const char *url); 206extern struct cgit_repo *cgit_get_repoinfo(const char *url);
206extern void cgit_repo_config_cb(const char *name, const char *value); 207extern void cgit_repo_config_cb(const char *name, const char *value);
207 208
208extern int chk_zero(int result, char *msg); 209extern int chk_zero(int result, char *msg);
209extern int chk_positive(int result, char *msg); 210extern int chk_positive(int result, char *msg);
210extern int chk_non_negative(int result, char *msg); 211extern int chk_non_negative(int result, char *msg);
211 212
212extern char *trim_end(const char *str, char c); 213extern char *trim_end(const char *str, char c);
213extern char *strlpart(char *txt, int maxlen); 214extern char *strlpart(char *txt, int maxlen);
214extern char *strrpart(char *txt, int maxlen); 215extern char *strrpart(char *txt, int maxlen);
215 216
216extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 217extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
217extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 218extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
218 int flags, void *cb_data); 219 int flags, void *cb_data);
219 220
220extern void *cgit_free_commitinfo(struct commitinfo *info); 221extern void *cgit_free_commitinfo(struct commitinfo *info);
221 222
222extern int cgit_diff_files(const unsigned char *old_sha1, 223extern int cgit_diff_files(const unsigned char *old_sha1,
223 const unsigned char *new_sha1, 224 const unsigned char *new_sha1,
224 linediff_fn fn); 225 linediff_fn fn);
225 226
226extern void cgit_diff_tree(const unsigned char *old_sha1, 227extern void cgit_diff_tree(const unsigned char *old_sha1,
227 const unsigned char *new_sha1, 228 const unsigned char *new_sha1,
228 filepair_fn fn, const char *prefix); 229 filepair_fn fn, const char *prefix);
229 230
230extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 231extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
231 232
232extern char *fmt(const char *format,...); 233extern char *fmt(const char *format,...);
233 234
234extern struct commitinfo *cgit_parse_commit(struct commit *commit); 235extern struct commitinfo *cgit_parse_commit(struct commit *commit);
235extern struct taginfo *cgit_parse_tag(struct tag *tag); 236extern struct taginfo *cgit_parse_tag(struct tag *tag);
236extern void cgit_parse_url(const char *url); 237extern void cgit_parse_url(const char *url);
237 238
238extern const char *cgit_repobasename(const char *reponame); 239extern const char *cgit_repobasename(const char *reponame);
239 240
240extern int cgit_parse_snapshots_mask(const char *str); 241extern int cgit_parse_snapshots_mask(const char *str);
241 242
242 243
243#endif /* CGIT_H */ 244#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 09f56a6..fd299ae 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,384 +1,388 @@
1CGITRC 1CGITRC
2====== 2======
3 3
4 4
5NAME 5NAME
6---- 6----
7 cgitrc - runtime configuration for cgit 7 cgitrc - runtime configuration for cgit
8 8
9 9
10DESCRIPTION 10DESCRIPTION
11----------- 11-----------
12Cgitrc contains all runtime settings for cgit, including the list of git 12Cgitrc contains all runtime settings for cgit, including the list of git
13repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank 13repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
14lines, and lines starting with '#', are ignored. 14lines, and lines starting with '#', are ignored.
15 15
16 16
17GLOBAL SETTINGS 17GLOBAL SETTINGS
18--------------- 18---------------
19agefile 19agefile
20 Specifies a path, relative to each repository path, which can be used 20 Specifies a path, relative to each repository path, which can be used
21 to specify the date and time of the youngest commit in the repository. 21 to specify the date and time of the youngest commit in the repository.
22 The first line in the file is used as input to the "parse_date" 22 The first line in the file is used as input to the "parse_date"
23 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 23 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
24 hh:mm:ss". Default value: "info/web/last-modified". 24 hh:mm:ss". Default value: "info/web/last-modified".
25 25
26cache-root 26cache-root
27 Path used to store the cgit cache entries. Default value: 27 Path used to store the cgit cache entries. Default value:
28 "/var/cache/cgit". 28 "/var/cache/cgit".
29 29
30cache-dynamic-ttl 30cache-dynamic-ttl
31 Number which specifies the time-to-live, in minutes, for the cached 31 Number which specifies the time-to-live, in minutes, for the cached
32 version of repository pages accessed without a fixed SHA1. Default 32 version of repository pages accessed without a fixed SHA1. Default
33 value: "5". 33 value: "5".
34 34
35cache-repo-ttl 35cache-repo-ttl
36 Number which specifies the time-to-live, in minutes, for the cached 36 Number which specifies the time-to-live, in minutes, for the cached
37 version of the repository summary page. Default value: "5". 37 version of the repository summary page. Default value: "5".
38 38
39cache-root-ttl 39cache-root-ttl
40 Number which specifies the time-to-live, in minutes, for the cached 40 Number which specifies the time-to-live, in minutes, for the cached
41 version of the repository index page. Default value: "5". 41 version of the repository index page. Default value: "5".
42 42
43cache-size 43cache-size
44 The maximum number of entries in the cgit cache. Default value: "0" 44 The maximum number of entries in the cgit cache. Default value: "0"
45 (i.e. caching is disabled). 45 (i.e. caching is disabled).
46 46
47cache-static-ttl 47cache-static-ttl
48 Number which specifies the time-to-live, in minutes, for the cached 48 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed with a fixed SHA1. Default value: 49 version of repository pages accessed with a fixed SHA1. Default value:
50 "5". 50 "5".
51 51
52clone-prefix 52clone-prefix
53 Space-separated list of common prefixes which, when combined with a 53 Space-separated list of common prefixes which, when combined with a
54 repository url, generates valid clone urls for the repository. This 54 repository url, generates valid clone urls for the repository. This
55 setting is only used if `repo.clone-url` is unspecified. Default value: 55 setting is only used if `repo.clone-url` is unspecified. Default value:
56 none. 56 none.
57 57
58css 58css
59 Url which specifies the css document to include in all cgit pages. 59 Url which specifies the css document to include in all cgit pages.
60 Default value: "/cgit.css". 60 Default value: "/cgit.css".
61 61
62enable-index-links 62enable-index-links
63 Flag which, when set to "1", will make cgit generate extra links for 63 Flag which, when set to "1", will make cgit generate extra links for
64 each repo in the repository index (specifically, to the "summary", 64 each repo in the repository index (specifically, to the "summary",
65 "commit" and "tree" pages). Default value: "0". 65 "commit" and "tree" pages). Default value: "0".
66 66
67enable-log-filecount 67enable-log-filecount
68 Flag which, when set to "1", will make cgit print the number of 68 Flag which, when set to "1", will make cgit print the number of
69 modified files for each commit on the repository log page. Default 69 modified files for each commit on the repository log page. Default
70 value: "0". 70 value: "0".
71 71
72enable-log-linecount 72enable-log-linecount
73 Flag which, when set to "1", will make cgit print the number of added 73 Flag which, when set to "1", will make cgit print the number of added
74 and removed lines for each commit on the repository log page. Default 74 and removed lines for each commit on the repository log page. Default
75 value: "0". 75 value: "0".
76 76
77favicon 77favicon
78 Url used as link to a shortcut icon for cgit. If specified, it is 78 Url used as link to a shortcut icon for cgit. If specified, it is
79 suggested to use the value "/favicon.ico" since certain browsers will 79 suggested to use the value "/favicon.ico" since certain browsers will
80 ignore other values. Default value: none. 80 ignore other values. Default value: none.
81 81
82footer 82footer
83 The content of the file specified with this option will be included 83 The content of the file specified with this option will be included
84 verbatim at the bottom of all pages (i.e. it replaces the standard 84 verbatim at the bottom of all pages (i.e. it replaces the standard
85 "generated by..." message. Default value: none. 85 "generated by..." message. Default value: none.
86 86
87header
88 The content of the file specified with this option will be included
89 verbatim at the top of all pages. Default value: none.
90
87include 91include
88 Name of a configfile to include before the rest of the current config- 92 Name of a configfile to include before the rest of the current config-
89 file is parsed. Default value: none. 93 file is parsed. Default value: none.
90 94
91index-header 95index-header
92 The content of the file specified with this option will be included 96 The content of the file specified with this option will be included
93 verbatim above the repository index. This setting is deprecated, and 97 verbatim above the repository index. This setting is deprecated, and
94 will not be supported by cgit-1.0 (use root-readme instead). Default 98 will not be supported by cgit-1.0 (use root-readme instead). Default
95 value: none. 99 value: none.
96 100
97index-info 101index-info
98 The content of the file specified with this option will be included 102 The content of the file specified with this option will be included
99 verbatim below the heading on the repository index page. This setting 103 verbatim below the heading on the repository index page. This setting
100 is deprecated, and will not be supported by cgit-1.0 (use root-desc 104 is deprecated, and will not be supported by cgit-1.0 (use root-desc
101 instead). Default value: none. 105 instead). Default value: none.
102 106
103local-time 107local-time
104 Flag which, if set to "1", makes cgit print commit and tag times in the 108 Flag which, if set to "1", makes cgit print commit and tag times in the
105 servers timezone. Default value: "0". 109 servers timezone. Default value: "0".
106 110
107logo 111logo
108 Url which specifies the source of an image which will be used as a logo 112 Url which specifies the source of an image which will be used as a logo
109 on all cgit pages. 113 on all cgit pages.
110 114
111logo-link 115logo-link
112 Url loaded when clicking on the cgit logo image. If unspecified the 116 Url loaded when clicking on the cgit logo image. If unspecified the
113 calculated url of the repository index page will be used. Default 117 calculated url of the repository index page will be used. Default
114 value: none. 118 value: none.
115 119
116max-commit-count 120max-commit-count
117 Specifies the number of entries to list per page in "log" view. Default 121 Specifies the number of entries to list per page in "log" view. Default
118 value: "50". 122 value: "50".
119 123
120max-message-length 124max-message-length
121 Specifies the maximum number of commit message characters to display in 125 Specifies the maximum number of commit message characters to display in
122 "log" view. Default value: "80". 126 "log" view. Default value: "80".
123 127
124max-repo-count 128max-repo-count
125 Specifies the number of entries to list per page on therepository 129 Specifies the number of entries to list per page on therepository
126 index page. Default value: "50". 130 index page. Default value: "50".
127 131
128max-repodesc-length 132max-repodesc-length
129 Specifies the maximum number of repo description characters to display 133 Specifies the maximum number of repo description characters to display
130 on the repository index page. Default value: "80". 134 on the repository index page. Default value: "80".
131 135
132max-stats 136max-stats
133 Set the default maximum statistics period. Valid values are "week", 137 Set the default maximum statistics period. Valid values are "week",
134 "month", "quarter" and "year". If unspecified, statistics are 138 "month", "quarter" and "year". If unspecified, statistics are
135 disabled. Default value: none. See also: "repo.max-stats". 139 disabled. Default value: none. See also: "repo.max-stats".
136 140
137module-link 141module-link
138 Text which will be used as the formatstring for a hyperlink when a 142 Text which will be used as the formatstring for a hyperlink when a
139 submodule is printed in a directory listing. The arguments for the 143 submodule is printed in a directory listing. The arguments for the
140 formatstring are the path and SHA1 of the submodule commit. Default 144 formatstring are the path and SHA1 of the submodule commit. Default
141 value: "./?repo=%s&page=commit&id=%s" 145 value: "./?repo=%s&page=commit&id=%s"
142 146
143nocache 147nocache
144 If set to the value "1" caching will be disabled. This settings is 148 If set to the value "1" caching will be disabled. This settings is
145 deprecated, and will not be honored starting with cgit-1.0. Default 149 deprecated, and will not be honored starting with cgit-1.0. Default
146 value: "0". 150 value: "0".
147 151
148renamelimit 152renamelimit
149 Maximum number of files to consider when detecting renames. The value 153 Maximum number of files to consider when detecting renames. The value
150 "-1" uses the compiletime value in git (for further info, look at 154 "-1" uses the compiletime value in git (for further info, look at
151 `man git-diff`). Default value: "-1". 155 `man git-diff`). Default value: "-1".
152 156
153repo.group 157repo.group
154 A value for the current repository group, which all repositories 158 A value for the current repository group, which all repositories
155 specified after this setting will inherit. Default value: none. 159 specified after this setting will inherit. Default value: none.
156 160
157robots 161robots
158 Text used as content for the "robots" meta-tag. Default value: 162 Text used as content for the "robots" meta-tag. Default value:
159 "index, nofollow". 163 "index, nofollow".
160 164
161root-desc 165root-desc
162 Text printed below the heading on the repository index page. Default 166 Text printed below the heading on the repository index page. Default
163 value: "a fast webinterface for the git dscm". 167 value: "a fast webinterface for the git dscm".
164 168
165root-readme: 169root-readme:
166 The content of the file specified with this option will be included 170 The content of the file specified with this option will be included
167 verbatim below the "about" link on the repository index page. Default 171 verbatim below the "about" link on the repository index page. Default
168 value: none. 172 value: none.
169 173
170root-title 174root-title
171 Text printed as heading on the repository index page. Default value: 175 Text printed as heading on the repository index page. Default value:
172 "Git Repository Browser". 176 "Git Repository Browser".
173 177
174snapshots 178snapshots
175 Text which specifies the default (and allowed) set of snapshot formats 179 Text which specifies the default (and allowed) set of snapshot formats
176 supported by cgit. The value is a space-separated list of zero or more 180 supported by cgit. The value is a space-separated list of zero or more
177 of the following values: 181 of the following values:
178 "tar" uncompressed tar-file 182 "tar" uncompressed tar-file
179 "tar.gz"gzip-compressed tar-file 183 "tar.gz"gzip-compressed tar-file
180 "tar.bz2"bzip-compressed tar-file 184 "tar.bz2"bzip-compressed tar-file
181 "zip" zip-file 185 "zip" zip-file
182 Default value: none. 186 Default value: none.
183 187
184summary-branches 188summary-branches
185 Specifies the number of branches to display in the repository "summary" 189 Specifies the number of branches to display in the repository "summary"
186 view. Default value: "10". 190 view. Default value: "10".
187 191
188summary-log 192summary-log
189 Specifies the number of log entries to display in the repository 193 Specifies the number of log entries to display in the repository
190 "summary" view. Default value: "10". 194 "summary" view. Default value: "10".
191 195
192summary-tags 196summary-tags
193 Specifies the number of tags to display in the repository "summary" 197 Specifies the number of tags to display in the repository "summary"
194 view. Default value: "10". 198 view. Default value: "10".
195 199
196virtual-root 200virtual-root
197 Url which, if specified, will be used as root for all cgit links. It 201 Url which, if specified, will be used as root for all cgit links. It
198 will also cause cgit to generate 'virtual urls', i.e. urls like 202 will also cause cgit to generate 'virtual urls', i.e. urls like
199 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 203 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
200 value: none. 204 value: none.
201 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 205 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
202 same kind of virtual urls, so this option will probably be deprecated. 206 same kind of virtual urls, so this option will probably be deprecated.
203 207
204REPOSITORY SETTINGS 208REPOSITORY SETTINGS
205------------------- 209-------------------
206repo.clone-url 210repo.clone-url
207 A list of space-separated urls which can be used to clone this repo. 211 A list of space-separated urls which can be used to clone this repo.
208 Default value: none. 212 Default value: none.
209 213
210repo.defbranch 214repo.defbranch
211 The name of the default branch for this repository. If no such branch 215 The name of the default branch for this repository. If no such branch
212 exists in the repository, the first branch name (when sorted) is used 216 exists in the repository, the first branch name (when sorted) is used
213 as default instead. Default value: "master". 217 as default instead. Default value: "master".
214 218
215repo.desc 219repo.desc
216 The value to show as repository description. Default value: none. 220 The value to show as repository description. Default value: none.
217 221
218repo.enable-log-filecount 222repo.enable-log-filecount
219 A flag which can be used to disable the global setting 223 A flag which can be used to disable the global setting
220 `enable-log-filecount'. Default value: none. 224 `enable-log-filecount'. Default value: none.
221 225
222repo.enable-log-linecount 226repo.enable-log-linecount
223 A flag which can be used to disable the global setting 227 A flag which can be used to disable the global setting
224 `enable-log-linecount'. Default value: none. 228 `enable-log-linecount'. Default value: none.
225 229
226repo.max-stats 230repo.max-stats
227 Override the default maximum statistics period. Valid values are equal 231 Override the default maximum statistics period. Valid values are equal
228 to the values specified for the global "max-stats" setting. Default 232 to the values specified for the global "max-stats" setting. Default
229 value: none. 233 value: none.
230 234
231repo.name 235repo.name
232 The value to show as repository name. Default value: <repo.url>. 236 The value to show as repository name. Default value: <repo.url>.
233 237
234repo.owner 238repo.owner
235 A value used to identify the owner of the repository. Default value: 239 A value used to identify the owner of the repository. Default value:
236 none. 240 none.
237 241
238repo.path 242repo.path
239 An absolute path to the repository directory. For non-bare repositories 243 An absolute path to the repository directory. For non-bare repositories
240 this is the .git-directory. Default value: none. 244 this is the .git-directory. Default value: none.
241 245
242repo.readme 246repo.readme
243 A path (relative to <repo.path>) which specifies a file to include 247 A path (relative to <repo.path>) which specifies a file to include
244 verbatim as the "About" page for this repo. Default value: none. 248 verbatim as the "About" page for this repo. Default value: none.
245 249
246repo.snapshots 250repo.snapshots
247 A mask of allowed snapshot-formats for this repo, restricted by the 251 A mask of allowed snapshot-formats for this repo, restricted by the
248 "snapshots" global setting. Default value: <snapshots>. 252 "snapshots" global setting. Default value: <snapshots>.
249 253
250repo.url 254repo.url
251 The relative url used to access the repository. This must be the first 255 The relative url used to access the repository. This must be the first
252 setting specified for each repo. Default value: none. 256 setting specified for each repo. Default value: none.
253 257
254 258
255EXAMPLE CGITRC FILE 259EXAMPLE CGITRC FILE
256------------------- 260-------------------
257 261
258# Enable caching of up to 1000 output entriess 262# Enable caching of up to 1000 output entriess
259cache-size=1000 263cache-size=1000
260 264
261 265
262# Specify some default clone prefixes 266# Specify some default clone prefixes
263clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 267clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
264 268
265# Specify the css url 269# Specify the css url
266css=/css/cgit.css 270css=/css/cgit.css
267 271
268 272
269# Show extra links for each repository on the index page 273# Show extra links for each repository on the index page
270enable-index-links=1 274enable-index-links=1
271 275
272 276
273# Show number of affected files per commit on the log pages 277# Show number of affected files per commit on the log pages
274enable-log-filecount=1 278enable-log-filecount=1
275 279
276 280
277# Show number of added/removed lines per commit on the log pages 281# Show number of added/removed lines per commit on the log pages
278enable-log-linecount=1 282enable-log-linecount=1
279 283
280 284
281# Add a cgit favicon 285# Add a cgit favicon
282favicon=/favicon.ico 286favicon=/favicon.ico
283 287
284 288
285# Use a custom logo 289# Use a custom logo
286logo=/img/mylogo.png 290logo=/img/mylogo.png
287 291
288 292
289# Enable statistics per week, month and quarter 293# Enable statistics per week, month and quarter
290max-stats=quarter 294max-stats=quarter
291 295
292 296
293# Set the title and heading of the repository index page 297# Set the title and heading of the repository index page
294root-title=foobar.com git repositories 298root-title=foobar.com git repositories
295 299
296 300
297# Set a subheading for the repository index page 301# Set a subheading for the repository index page
298root-desc=tracking the foobar development 302root-desc=tracking the foobar development
299 303
300 304
301# Include some more info about foobar.com on the index page 305# Include some more info about foobar.com on the index page
302root-readme=/var/www/htdocs/about.html 306root-readme=/var/www/htdocs/about.html
303 307
304 308
305# Allow download of tar.gz, tar.bz2 and zip-files 309# Allow download of tar.gz, tar.bz2 and zip-files
306snapshots=tar.gz tar.bz2 zip 310snapshots=tar.gz tar.bz2 zip
307 311
308 312
309## 313##
310## List of repositories. 314## List of repositories.
311## PS: Any repositories listed when repo.group is unset will not be 315## PS: Any repositories listed when repo.group is unset will not be
312## displayed under a group heading 316## displayed under a group heading
313## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 317## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
314## and included like this: 318## and included like this:
315## include=/etc/cgitrepos 319## include=/etc/cgitrepos
316## 320##
317 321
318 322
319repo.url=foo 323repo.url=foo
320repo.path=/pub/git/foo.git 324repo.path=/pub/git/foo.git
321repo.desc=the master foo repository 325repo.desc=the master foo repository
322repo.owner=fooman@foobar.com 326repo.owner=fooman@foobar.com
323repo.readme=info/web/about.html 327repo.readme=info/web/about.html
324 328
325 329
326repo.url=bar 330repo.url=bar
327repo.path=/pub/git/bar.git 331repo.path=/pub/git/bar.git
328repo.desc=the bars for your foo 332repo.desc=the bars for your foo
329repo.owner=barman@foobar.com 333repo.owner=barman@foobar.com
330repo.readme=info/web/about.html 334repo.readme=info/web/about.html
331 335
332 336
333# The next repositories will be displayed under the 'extras' heading 337# The next repositories will be displayed under the 'extras' heading
334repo.group=extras 338repo.group=extras
335 339
336 340
337repo.url=baz 341repo.url=baz
338repo.path=/pub/git/baz.git 342repo.path=/pub/git/baz.git
339repo.desc=a set of extensions for bar users 343repo.desc=a set of extensions for bar users
340 344
341repo.url=wiz 345repo.url=wiz
342repo.path=/pub/git/wiz.git 346repo.path=/pub/git/wiz.git
343repo.desc=the wizard of foo 347repo.desc=the wizard of foo
344 348
345 349
346# Add some mirrored repositories 350# Add some mirrored repositories
347repo.group=mirrors 351repo.group=mirrors
348 352
349 353
350repo.url=git 354repo.url=git
351repo.path=/pub/git/git.git 355repo.path=/pub/git/git.git
352repo.desc=the dscm 356repo.desc=the dscm
353 357
354 358
355repo.url=linux 359repo.url=linux
356repo.path=/pub/git/linux.git 360repo.path=/pub/git/linux.git
357repo.desc=the kernel 361repo.desc=the kernel
358 362
359# Disable adhoc downloads of this repo 363# Disable adhoc downloads of this repo
360repo.snapshots=0 364repo.snapshots=0
361 365
362# Disable line-counts for this repo 366# Disable line-counts for this repo
363repo.enable-log-linecount=0 367repo.enable-log-linecount=0
364 368
365# Restrict the max statistics period for this repo 369# Restrict the max statistics period for this repo
366repo.max-stats=month 370repo.max-stats=month
367 371
368 372
369BUGS 373BUGS
370---- 374----
371Comments currently cannot appear on the same line as a setting; the comment 375Comments currently cannot appear on the same line as a setting; the comment
372will be included as part of the value. E.g. this line: 376will be included as part of the value. E.g. this line:
373 377
374 robots=index # allow indexing 378 robots=index # allow indexing
375 379
376will generate the following html element: 380will generate the following html element:
377 381
378 <meta name='robots' content='index # allow indexing'/> 382 <meta name='robots' content='index # allow indexing'/>
379 383
380 384
381 385
382AUTHOR 386AUTHOR
383------ 387------
384Lars Hjemli <hjemli@gmail.com> 388Lars Hjemli <hjemli@gmail.com>
diff --git a/ui-shared.c b/ui-shared.c
index 4f28512..de77bbf 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -1,737 +1,739 @@
1/* ui-shared.c: common web output functions 1/* ui-shared.c: common web output functions
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 "cmd.h" 10#include "cmd.h"
11#include "html.h" 11#include "html.h"
12 12
13const char cgit_doctype[] = 13const char cgit_doctype[] =
14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" 14"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n"
15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; 15" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
16 16
17static char *http_date(time_t t) 17static char *http_date(time_t t)
18{ 18{
19 static char day[][4] = 19 static char day[][4] =
20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 20 {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
21 static char month[][4] = 21 static char month[][4] =
22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun", 22 {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
23 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"}; 23 "Jul", "Aug", "Sep", "Oct", "Now", "Dec"};
24 struct tm *tm = gmtime(&t); 24 struct tm *tm = gmtime(&t);
25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], 25 return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday],
26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, 26 tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year,
27 tm->tm_hour, tm->tm_min, tm->tm_sec); 27 tm->tm_hour, tm->tm_min, tm->tm_sec);
28} 28}
29 29
30void cgit_print_error(char *msg) 30void cgit_print_error(char *msg)
31{ 31{
32 html("<div class='error'>"); 32 html("<div class='error'>");
33 html_txt(msg); 33 html_txt(msg);
34 html("</div>\n"); 34 html("</div>\n");
35} 35}
36 36
37char *cgit_hosturl() 37char *cgit_hosturl()
38{ 38{
39 char *host, *port; 39 char *host, *port;
40 40
41 host = getenv("HTTP_HOST"); 41 host = getenv("HTTP_HOST");
42 if (host) { 42 if (host) {
43 host = xstrdup(host); 43 host = xstrdup(host);
44 } else { 44 } else {
45 host = getenv("SERVER_NAME"); 45 host = getenv("SERVER_NAME");
46 if (!host) 46 if (!host)
47 return NULL; 47 return NULL;
48 port = getenv("SERVER_PORT"); 48 port = getenv("SERVER_PORT");
49 if (port && atoi(port) != 80) 49 if (port && atoi(port) != 80)
50 host = xstrdup(fmt("%s:%d", host, atoi(port))); 50 host = xstrdup(fmt("%s:%d", host, atoi(port)));
51 else 51 else
52 host = xstrdup(host); 52 host = xstrdup(host);
53 } 53 }
54 return host; 54 return host;
55} 55}
56 56
57char *cgit_rooturl() 57char *cgit_rooturl()
58{ 58{
59 if (ctx.cfg.virtual_root) 59 if (ctx.cfg.virtual_root)
60 return fmt("%s/", ctx.cfg.virtual_root); 60 return fmt("%s/", ctx.cfg.virtual_root);
61 else 61 else
62 return ctx.cfg.script_name; 62 return ctx.cfg.script_name;
63} 63}
64 64
65char *cgit_repourl(const char *reponame) 65char *cgit_repourl(const char *reponame)
66{ 66{
67 if (ctx.cfg.virtual_root) { 67 if (ctx.cfg.virtual_root) {
68 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame); 68 return fmt("%s/%s/", ctx.cfg.virtual_root, reponame);
69 } else { 69 } else {
70 return fmt("?r=%s", reponame); 70 return fmt("?r=%s", reponame);
71 } 71 }
72} 72}
73 73
74char *cgit_fileurl(const char *reponame, const char *pagename, 74char *cgit_fileurl(const char *reponame, const char *pagename,
75 const char *filename, const char *query) 75 const char *filename, const char *query)
76{ 76{
77 char *tmp; 77 char *tmp;
78 char *delim; 78 char *delim;
79 79
80 if (ctx.cfg.virtual_root) { 80 if (ctx.cfg.virtual_root) {
81 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame, 81 tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame,
82 pagename, (filename ? filename:"")); 82 pagename, (filename ? filename:""));
83 delim = "?"; 83 delim = "?";
84 } else { 84 } else {
85 tmp = fmt("?url=%s/%s/%s", reponame, pagename, 85 tmp = fmt("?url=%s/%s/%s", reponame, pagename,
86 (filename ? filename : "")); 86 (filename ? filename : ""));
87 delim = "&"; 87 delim = "&";
88 } 88 }
89 if (query) 89 if (query)
90 tmp = fmt("%s%s%s", tmp, delim, query); 90 tmp = fmt("%s%s%s", tmp, delim, query);
91 return tmp; 91 return tmp;
92} 92}
93 93
94char *cgit_pageurl(const char *reponame, const char *pagename, 94char *cgit_pageurl(const char *reponame, const char *pagename,
95 const char *query) 95 const char *query)
96{ 96{
97 return cgit_fileurl(reponame,pagename,0,query); 97 return cgit_fileurl(reponame,pagename,0,query);
98} 98}
99 99
100const char *cgit_repobasename(const char *reponame) 100const char *cgit_repobasename(const char *reponame)
101{ 101{
102 /* I assume we don't need to store more than one repo basename */ 102 /* I assume we don't need to store more than one repo basename */
103 static char rvbuf[1024]; 103 static char rvbuf[1024];
104 int p; 104 int p;
105 const char *rv; 105 const char *rv;
106 strncpy(rvbuf,reponame,sizeof(rvbuf)); 106 strncpy(rvbuf,reponame,sizeof(rvbuf));
107 if(rvbuf[sizeof(rvbuf)-1]) 107 if(rvbuf[sizeof(rvbuf)-1])
108 die("cgit_repobasename: truncated repository name '%s'", reponame); 108 die("cgit_repobasename: truncated repository name '%s'", reponame);
109 p = strlen(rvbuf)-1; 109 p = strlen(rvbuf)-1;
110 /* strip trailing slashes */ 110 /* strip trailing slashes */
111 while(p && rvbuf[p]=='/') rvbuf[p--]=0; 111 while(p && rvbuf[p]=='/') rvbuf[p--]=0;
112 /* strip trailing .git */ 112 /* strip trailing .git */
113 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { 113 if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) {
114 p -= 3; rvbuf[p--] = 0; 114 p -= 3; rvbuf[p--] = 0;
115 } 115 }
116 /* strip more trailing slashes if any */ 116 /* strip more trailing slashes if any */
117 while( p && rvbuf[p]=='/') rvbuf[p--]=0; 117 while( p && rvbuf[p]=='/') rvbuf[p--]=0;
118 /* find last slash in the remaining string */ 118 /* find last slash in the remaining string */
119 rv = strrchr(rvbuf,'/'); 119 rv = strrchr(rvbuf,'/');
120 if(rv) 120 if(rv)
121 return ++rv; 121 return ++rv;
122 return rvbuf; 122 return rvbuf;
123} 123}
124 124
125char *cgit_currurl() 125char *cgit_currurl()
126{ 126{
127 if (!ctx.cfg.virtual_root) 127 if (!ctx.cfg.virtual_root)
128 return ctx.cfg.script_name; 128 return ctx.cfg.script_name;
129 else if (ctx.qry.page) 129 else if (ctx.qry.page)
130 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); 130 return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page);
131 else if (ctx.qry.repo) 131 else if (ctx.qry.repo)
132 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); 132 return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo);
133 else 133 else
134 return fmt("%s/", ctx.cfg.virtual_root); 134 return fmt("%s/", ctx.cfg.virtual_root);
135} 135}
136 136
137static void site_url(char *page, char *search, int ofs) 137static void site_url(char *page, char *search, int ofs)
138{ 138{
139 char *delim = "?"; 139 char *delim = "?";
140 140
141 if (ctx.cfg.virtual_root) { 141 if (ctx.cfg.virtual_root) {
142 html_attr(ctx.cfg.virtual_root); 142 html_attr(ctx.cfg.virtual_root);
143 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 143 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
144 html("/"); 144 html("/");
145 } else 145 } else
146 html(ctx.cfg.script_name); 146 html(ctx.cfg.script_name);
147 147
148 if (page) { 148 if (page) {
149 htmlf("?p=%s", page); 149 htmlf("?p=%s", page);
150 delim = "&"; 150 delim = "&";
151 } 151 }
152 if (search) { 152 if (search) {
153 html(delim); 153 html(delim);
154 html("q="); 154 html("q=");
155 html_attr(search); 155 html_attr(search);
156 delim = "&"; 156 delim = "&";
157 } 157 }
158 if (ofs) { 158 if (ofs) {
159 html(delim); 159 html(delim);
160 htmlf("ofs=%d", ofs); 160 htmlf("ofs=%d", ofs);
161 } 161 }
162} 162}
163 163
164static void site_link(char *page, char *name, char *title, char *class, 164static void site_link(char *page, char *name, char *title, char *class,
165 char *search, int ofs) 165 char *search, int ofs)
166{ 166{
167 html("<a"); 167 html("<a");
168 if (title) { 168 if (title) {
169 html(" title='"); 169 html(" title='");
170 html_attr(title); 170 html_attr(title);
171 html("'"); 171 html("'");
172 } 172 }
173 if (class) { 173 if (class) {
174 html(" class='"); 174 html(" class='");
175 html_attr(class); 175 html_attr(class);
176 html("'"); 176 html("'");
177 } 177 }
178 html(" href='"); 178 html(" href='");
179 site_url(page, search, ofs); 179 site_url(page, search, ofs);
180 html("'>"); 180 html("'>");
181 html_txt(name); 181 html_txt(name);
182 html("</a>"); 182 html("</a>");
183} 183}
184 184
185void cgit_index_link(char *name, char *title, char *class, char *pattern, 185void cgit_index_link(char *name, char *title, char *class, char *pattern,
186 int ofs) 186 int ofs)
187{ 187{
188 site_link(NULL, name, title, class, pattern, ofs); 188 site_link(NULL, name, title, class, pattern, ofs);
189} 189}
190 190
191static char *repolink(char *title, char *class, char *page, char *head, 191static char *repolink(char *title, char *class, char *page, char *head,
192 char *path) 192 char *path)
193{ 193{
194 char *delim = "?"; 194 char *delim = "?";
195 195
196 html("<a"); 196 html("<a");
197 if (title) { 197 if (title) {
198 html(" title='"); 198 html(" title='");
199 html_attr(title); 199 html_attr(title);
200 html("'"); 200 html("'");
201 } 201 }
202 if (class) { 202 if (class) {
203 html(" class='"); 203 html(" class='");
204 html_attr(class); 204 html_attr(class);
205 html("'"); 205 html("'");
206 } 206 }
207 html(" href='"); 207 html(" href='");
208 if (ctx.cfg.virtual_root) { 208 if (ctx.cfg.virtual_root) {
209 html_url_path(ctx.cfg.virtual_root); 209 html_url_path(ctx.cfg.virtual_root);
210 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') 210 if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/')
211 html("/"); 211 html("/");
212 html_url_path(ctx.repo->url); 212 html_url_path(ctx.repo->url);
213 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 213 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
214 html("/"); 214 html("/");
215 if (page) { 215 if (page) {
216 html_url_path(page); 216 html_url_path(page);
217 html("/"); 217 html("/");
218 if (path) 218 if (path)
219 html_url_path(path); 219 html_url_path(path);
220 } 220 }
221 } else { 221 } else {
222 html(ctx.cfg.script_name); 222 html(ctx.cfg.script_name);
223 html("?url="); 223 html("?url=");
224 html_url_arg(ctx.repo->url); 224 html_url_arg(ctx.repo->url);
225 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') 225 if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/')
226 html("/"); 226 html("/");
227 if (page) { 227 if (page) {
228 html_url_arg(page); 228 html_url_arg(page);
229 html("/"); 229 html("/");
230 if (path) 230 if (path)
231 html_url_arg(path); 231 html_url_arg(path);
232 } 232 }
233 delim = "&amp;"; 233 delim = "&amp;";
234 } 234 }
235 if (head && strcmp(head, ctx.repo->defbranch)) { 235 if (head && strcmp(head, ctx.repo->defbranch)) {
236 html(delim); 236 html(delim);
237 html("h="); 237 html("h=");
238 html_url_arg(head); 238 html_url_arg(head);
239 delim = "&amp;"; 239 delim = "&amp;";
240 } 240 }
241 return fmt("%s", delim); 241 return fmt("%s", delim);
242} 242}
243 243
244static void reporevlink(char *page, char *name, char *title, char *class, 244static void reporevlink(char *page, char *name, char *title, char *class,
245 char *head, char *rev, char *path) 245 char *head, char *rev, char *path)
246{ 246{
247 char *delim; 247 char *delim;
248 248
249 delim = repolink(title, class, page, head, path); 249 delim = repolink(title, class, page, head, path);
250 if (rev && strcmp(rev, ctx.qry.head)) { 250 if (rev && strcmp(rev, ctx.qry.head)) {
251 html(delim); 251 html(delim);
252 html("id="); 252 html("id=");
253 html_url_arg(rev); 253 html_url_arg(rev);
254 } 254 }
255 html("'>"); 255 html("'>");
256 html_txt(name); 256 html_txt(name);
257 html("</a>"); 257 html("</a>");
258} 258}
259 259
260void cgit_summary_link(char *name, char *title, char *class, char *head) 260void cgit_summary_link(char *name, char *title, char *class, char *head)
261{ 261{
262 reporevlink(NULL, name, title, class, head, NULL, NULL); 262 reporevlink(NULL, name, title, class, head, NULL, NULL);
263} 263}
264 264
265void cgit_tag_link(char *name, char *title, char *class, char *head, 265void cgit_tag_link(char *name, char *title, char *class, char *head,
266 char *rev) 266 char *rev)
267{ 267{
268 reporevlink("tag", name, title, class, head, rev, NULL); 268 reporevlink("tag", name, title, class, head, rev, NULL);
269} 269}
270 270
271void cgit_tree_link(char *name, char *title, char *class, char *head, 271void cgit_tree_link(char *name, char *title, char *class, char *head,
272 char *rev, char *path) 272 char *rev, char *path)
273{ 273{
274 reporevlink("tree", name, title, class, head, rev, path); 274 reporevlink("tree", name, title, class, head, rev, path);
275} 275}
276 276
277void cgit_plain_link(char *name, char *title, char *class, char *head, 277void cgit_plain_link(char *name, char *title, char *class, char *head,
278 char *rev, char *path) 278 char *rev, char *path)
279{ 279{
280 reporevlink("plain", name, title, class, head, rev, path); 280 reporevlink("plain", name, title, class, head, rev, path);
281} 281}
282 282
283void cgit_log_link(char *name, char *title, char *class, char *head, 283void cgit_log_link(char *name, char *title, char *class, char *head,
284 char *rev, char *path, int ofs, char *grep, char *pattern, 284 char *rev, char *path, int ofs, char *grep, char *pattern,
285 int showmsg) 285 int showmsg)
286{ 286{
287 char *delim; 287 char *delim;
288 288
289 delim = repolink(title, class, "log", head, path); 289 delim = repolink(title, class, "log", head, path);
290 if (rev && strcmp(rev, ctx.qry.head)) { 290 if (rev && strcmp(rev, ctx.qry.head)) {
291 html(delim); 291 html(delim);
292 html("id="); 292 html("id=");
293 html_url_arg(rev); 293 html_url_arg(rev);
294 delim = "&"; 294 delim = "&";
295 } 295 }
296 if (grep && pattern) { 296 if (grep && pattern) {
297 html(delim); 297 html(delim);
298 html("qt="); 298 html("qt=");
299 html_url_arg(grep); 299 html_url_arg(grep);
300 delim = "&"; 300 delim = "&";
301 html(delim); 301 html(delim);
302 html("q="); 302 html("q=");
303 html_url_arg(pattern); 303 html_url_arg(pattern);
304 } 304 }
305 if (ofs > 0) { 305 if (ofs > 0) {
306 html(delim); 306 html(delim);
307 html("ofs="); 307 html("ofs=");
308 htmlf("%d", ofs); 308 htmlf("%d", ofs);
309 delim = "&"; 309 delim = "&";
310 } 310 }
311 if (showmsg) { 311 if (showmsg) {
312 html(delim); 312 html(delim);
313 html("showmsg=1"); 313 html("showmsg=1");
314 } 314 }
315 html("'>"); 315 html("'>");
316 html_txt(name); 316 html_txt(name);
317 html("</a>"); 317 html("</a>");
318} 318}
319 319
320void cgit_commit_link(char *name, char *title, char *class, char *head, 320void cgit_commit_link(char *name, char *title, char *class, char *head,
321 char *rev) 321 char *rev)
322{ 322{
323 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { 323 if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) {
324 name[ctx.cfg.max_msg_len] = '\0'; 324 name[ctx.cfg.max_msg_len] = '\0';
325 name[ctx.cfg.max_msg_len - 1] = '.'; 325 name[ctx.cfg.max_msg_len - 1] = '.';
326 name[ctx.cfg.max_msg_len - 2] = '.'; 326 name[ctx.cfg.max_msg_len - 2] = '.';
327 name[ctx.cfg.max_msg_len - 3] = '.'; 327 name[ctx.cfg.max_msg_len - 3] = '.';
328 } 328 }
329 reporevlink("commit", name, title, class, head, rev, NULL); 329 reporevlink("commit", name, title, class, head, rev, NULL);
330} 330}
331 331
332void cgit_refs_link(char *name, char *title, char *class, char *head, 332void cgit_refs_link(char *name, char *title, char *class, char *head,
333 char *rev, char *path) 333 char *rev, char *path)
334{ 334{
335 reporevlink("refs", name, title, class, head, rev, path); 335 reporevlink("refs", name, title, class, head, rev, path);
336} 336}
337 337
338void cgit_snapshot_link(char *name, char *title, char *class, char *head, 338void cgit_snapshot_link(char *name, char *title, char *class, char *head,
339 char *rev, char *archivename) 339 char *rev, char *archivename)
340{ 340{
341 reporevlink("snapshot", name, title, class, head, rev, archivename); 341 reporevlink("snapshot", name, title, class, head, rev, archivename);
342} 342}
343 343
344void cgit_diff_link(char *name, char *title, char *class, char *head, 344void cgit_diff_link(char *name, char *title, char *class, char *head,
345 char *new_rev, char *old_rev, char *path) 345 char *new_rev, char *old_rev, char *path)
346{ 346{
347 char *delim; 347 char *delim;
348 348
349 delim = repolink(title, class, "diff", head, path); 349 delim = repolink(title, class, "diff", head, path);
350 if (new_rev && strcmp(new_rev, ctx.qry.head)) { 350 if (new_rev && strcmp(new_rev, ctx.qry.head)) {
351 html(delim); 351 html(delim);
352 html("id="); 352 html("id=");
353 html_url_arg(new_rev); 353 html_url_arg(new_rev);
354 delim = "&amp;"; 354 delim = "&amp;";
355 } 355 }
356 if (old_rev) { 356 if (old_rev) {
357 html(delim); 357 html(delim);
358 html("id2="); 358 html("id2=");
359 html_url_arg(old_rev); 359 html_url_arg(old_rev);
360 } 360 }
361 html("'>"); 361 html("'>");
362 html_txt(name); 362 html_txt(name);
363 html("</a>"); 363 html("</a>");
364} 364}
365 365
366void cgit_patch_link(char *name, char *title, char *class, char *head, 366void cgit_patch_link(char *name, char *title, char *class, char *head,
367 char *rev) 367 char *rev)
368{ 368{
369 reporevlink("patch", name, title, class, head, rev, NULL); 369 reporevlink("patch", name, title, class, head, rev, NULL);
370} 370}
371 371
372void cgit_stats_link(char *name, char *title, char *class, char *head, 372void cgit_stats_link(char *name, char *title, char *class, char *head,
373 char *path) 373 char *path)
374{ 374{
375 reporevlink("stats", name, title, class, head, NULL, path); 375 reporevlink("stats", name, title, class, head, NULL, path);
376} 376}
377 377
378void cgit_object_link(struct object *obj) 378void cgit_object_link(struct object *obj)
379{ 379{
380 char *page, *shortrev, *fullrev, *name; 380 char *page, *shortrev, *fullrev, *name;
381 381
382 fullrev = sha1_to_hex(obj->sha1); 382 fullrev = sha1_to_hex(obj->sha1);
383 shortrev = xstrdup(fullrev); 383 shortrev = xstrdup(fullrev);
384 shortrev[10] = '\0'; 384 shortrev[10] = '\0';
385 if (obj->type == OBJ_COMMIT) { 385 if (obj->type == OBJ_COMMIT) {
386 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, 386 cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL,
387 ctx.qry.head, fullrev); 387 ctx.qry.head, fullrev);
388 return; 388 return;
389 } else if (obj->type == OBJ_TREE) 389 } else if (obj->type == OBJ_TREE)
390 page = "tree"; 390 page = "tree";
391 else if (obj->type == OBJ_TAG) 391 else if (obj->type == OBJ_TAG)
392 page = "tag"; 392 page = "tag";
393 else 393 else
394 page = "blob"; 394 page = "blob";
395 name = fmt("%s %s...", typename(obj->type), shortrev); 395 name = fmt("%s %s...", typename(obj->type), shortrev);
396 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); 396 reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL);
397} 397}
398 398
399void cgit_print_date(time_t secs, char *format, int local_time) 399void cgit_print_date(time_t secs, char *format, int local_time)
400{ 400{
401 char buf[64]; 401 char buf[64];
402 struct tm *time; 402 struct tm *time;
403 403
404 if (!secs) 404 if (!secs)
405 return; 405 return;
406 if(local_time) 406 if(local_time)
407 time = localtime(&secs); 407 time = localtime(&secs);
408 else 408 else
409 time = gmtime(&secs); 409 time = gmtime(&secs);
410 strftime(buf, sizeof(buf)-1, format, time); 410 strftime(buf, sizeof(buf)-1, format, time);
411 html_txt(buf); 411 html_txt(buf);
412} 412}
413 413
414void cgit_print_age(time_t t, time_t max_relative, char *format) 414void cgit_print_age(time_t t, time_t max_relative, char *format)
415{ 415{
416 time_t now, secs; 416 time_t now, secs;
417 417
418 if (!t) 418 if (!t)
419 return; 419 return;
420 time(&now); 420 time(&now);
421 secs = now - t; 421 secs = now - t;
422 422
423 if (secs > max_relative && max_relative >= 0) { 423 if (secs > max_relative && max_relative >= 0) {
424 cgit_print_date(t, format, ctx.cfg.local_time); 424 cgit_print_date(t, format, ctx.cfg.local_time);
425 return; 425 return;
426 } 426 }
427 427
428 if (secs < TM_HOUR * 2) { 428 if (secs < TM_HOUR * 2) {
429 htmlf("<span class='age-mins'>%.0f min.</span>", 429 htmlf("<span class='age-mins'>%.0f min.</span>",
430 secs * 1.0 / TM_MIN); 430 secs * 1.0 / TM_MIN);
431 return; 431 return;
432 } 432 }
433 if (secs < TM_DAY * 2) { 433 if (secs < TM_DAY * 2) {
434 htmlf("<span class='age-hours'>%.0f hours</span>", 434 htmlf("<span class='age-hours'>%.0f hours</span>",
435 secs * 1.0 / TM_HOUR); 435 secs * 1.0 / TM_HOUR);
436 return; 436 return;
437 } 437 }
438 if (secs < TM_WEEK * 2) { 438 if (secs < TM_WEEK * 2) {
439 htmlf("<span class='age-days'>%.0f days</span>", 439 htmlf("<span class='age-days'>%.0f days</span>",
440 secs * 1.0 / TM_DAY); 440 secs * 1.0 / TM_DAY);
441 return; 441 return;
442 } 442 }
443 if (secs < TM_MONTH * 2) { 443 if (secs < TM_MONTH * 2) {
444 htmlf("<span class='age-weeks'>%.0f weeks</span>", 444 htmlf("<span class='age-weeks'>%.0f weeks</span>",
445 secs * 1.0 / TM_WEEK); 445 secs * 1.0 / TM_WEEK);
446 return; 446 return;
447 } 447 }
448 if (secs < TM_YEAR * 2) { 448 if (secs < TM_YEAR * 2) {
449 htmlf("<span class='age-months'>%.0f months</span>", 449 htmlf("<span class='age-months'>%.0f months</span>",
450 secs * 1.0 / TM_MONTH); 450 secs * 1.0 / TM_MONTH);
451 return; 451 return;
452 } 452 }
453 htmlf("<span class='age-years'>%.0f years</span>", 453 htmlf("<span class='age-years'>%.0f years</span>",
454 secs * 1.0 / TM_YEAR); 454 secs * 1.0 / TM_YEAR);
455} 455}
456 456
457void cgit_print_http_headers(struct cgit_context *ctx) 457void cgit_print_http_headers(struct cgit_context *ctx)
458{ 458{
459 if (ctx->page.mimetype && ctx->page.charset) 459 if (ctx->page.mimetype && ctx->page.charset)
460 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, 460 htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype,
461 ctx->page.charset); 461 ctx->page.charset);
462 else if (ctx->page.mimetype) 462 else if (ctx->page.mimetype)
463 htmlf("Content-Type: %s\n", ctx->page.mimetype); 463 htmlf("Content-Type: %s\n", ctx->page.mimetype);
464 if (ctx->page.size) 464 if (ctx->page.size)
465 htmlf("Content-Length: %ld\n", ctx->page.size); 465 htmlf("Content-Length: %ld\n", ctx->page.size);
466 if (ctx->page.filename) 466 if (ctx->page.filename)
467 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 467 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
468 ctx->page.filename); 468 ctx->page.filename);
469 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 469 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
470 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 470 htmlf("Expires: %s\n", http_date(ctx->page.expires));
471 html("\n"); 471 html("\n");
472} 472}
473 473
474void cgit_print_docstart(struct cgit_context *ctx) 474void cgit_print_docstart(struct cgit_context *ctx)
475{ 475{
476 char *host = cgit_hosturl(); 476 char *host = cgit_hosturl();
477 html(cgit_doctype); 477 html(cgit_doctype);
478 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); 478 html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n");
479 html("<head>\n"); 479 html("<head>\n");
480 html("<title>"); 480 html("<title>");
481 html_txt(ctx->page.title); 481 html_txt(ctx->page.title);
482 html("</title>\n"); 482 html("</title>\n");
483 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); 483 htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version);
484 if (ctx->cfg.robots && *ctx->cfg.robots) 484 if (ctx->cfg.robots && *ctx->cfg.robots)
485 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); 485 htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots);
486 html("<link rel='stylesheet' type='text/css' href='"); 486 html("<link rel='stylesheet' type='text/css' href='");
487 html_attr(ctx->cfg.css); 487 html_attr(ctx->cfg.css);
488 html("'/>\n"); 488 html("'/>\n");
489 if (ctx->cfg.favicon) { 489 if (ctx->cfg.favicon) {
490 html("<link rel='shortcut icon' href='"); 490 html("<link rel='shortcut icon' href='");
491 html_attr(ctx->cfg.favicon); 491 html_attr(ctx->cfg.favicon);
492 html("'/>\n"); 492 html("'/>\n");
493 } 493 }
494 if (host && ctx->repo) { 494 if (host && ctx->repo) {
495 html("<link rel='alternate' title='Atom feed' href='http://"); 495 html("<link rel='alternate' title='Atom feed' href='http://");
496 html_attr(cgit_hosturl()); 496 html_attr(cgit_hosturl());
497 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 497 html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path,
498 fmt("h=%s", ctx->qry.head))); 498 fmt("h=%s", ctx->qry.head)));
499 html("' type='application/atom+xml'/>"); 499 html("' type='application/atom+xml'/>");
500 } 500 }
501 html("</head>\n"); 501 html("</head>\n");
502 html("<body>\n"); 502 html("<body>\n");
503 if (ctx->cfg.header)
504 html_include(ctx->cfg.header);
503} 505}
504 506
505void cgit_print_docend() 507void cgit_print_docend()
506{ 508{
507 html("</div>"); 509 html("</div>");
508 if (ctx.cfg.footer) 510 if (ctx.cfg.footer)
509 html_include(ctx.cfg.footer); 511 html_include(ctx.cfg.footer);
510 else { 512 else {
511 htmlf("<div class='footer'>generated by cgit %s at ", 513 htmlf("<div class='footer'>generated by cgit %s at ",
512 cgit_version); 514 cgit_version);
513 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); 515 cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time);
514 html("</div>\n"); 516 html("</div>\n");
515 } 517 }
516 html("</body>\n</html>\n"); 518 html("</body>\n</html>\n");
517} 519}
518 520
519int print_branch_option(const char *refname, const unsigned char *sha1, 521int print_branch_option(const char *refname, const unsigned char *sha1,
520 int flags, void *cb_data) 522 int flags, void *cb_data)
521{ 523{
522 char *name = (char *)refname; 524 char *name = (char *)refname;
523 html_option(name, name, ctx.qry.head); 525 html_option(name, name, ctx.qry.head);
524 return 0; 526 return 0;
525} 527}
526 528
527int print_archive_ref(const char *refname, const unsigned char *sha1, 529int print_archive_ref(const char *refname, const unsigned char *sha1,
528 int flags, void *cb_data) 530 int flags, void *cb_data)
529{ 531{
530 struct tag *tag; 532 struct tag *tag;
531 struct taginfo *info; 533 struct taginfo *info;
532 struct object *obj; 534 struct object *obj;
533 char buf[256], *url; 535 char buf[256], *url;
534 unsigned char fileid[20]; 536 unsigned char fileid[20];
535 int *header = (int *)cb_data; 537 int *header = (int *)cb_data;
536 538
537 if (prefixcmp(refname, "refs/archives")) 539 if (prefixcmp(refname, "refs/archives"))
538 return 0; 540 return 0;
539 strncpy(buf, refname+14, sizeof(buf)); 541 strncpy(buf, refname+14, sizeof(buf));
540 obj = parse_object(sha1); 542 obj = parse_object(sha1);
541 if (!obj) 543 if (!obj)
542 return 1; 544 return 1;
543 if (obj->type == OBJ_TAG) { 545 if (obj->type == OBJ_TAG) {
544 tag = lookup_tag(sha1); 546 tag = lookup_tag(sha1);
545 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) 547 if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag)))
546 return 0; 548 return 0;
547 hashcpy(fileid, tag->tagged->sha1); 549 hashcpy(fileid, tag->tagged->sha1);
548 } else if (obj->type != OBJ_BLOB) { 550 } else if (obj->type != OBJ_BLOB) {
549 return 0; 551 return 0;
550 } else { 552 } else {
551 hashcpy(fileid, sha1); 553 hashcpy(fileid, sha1);
552 } 554 }
553 if (!*header) { 555 if (!*header) {
554 html("<h1>download</h1>\n"); 556 html("<h1>download</h1>\n");
555 *header = 1; 557 *header = 1;
556 } 558 }
557 url = cgit_pageurl(ctx.qry.repo, "blob", 559 url = cgit_pageurl(ctx.qry.repo, "blob",
558 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid), 560 fmt("id=%s&amp;path=%s", sha1_to_hex(fileid),
559 buf)); 561 buf));
560 html_link_open(url, NULL, "menu"); 562 html_link_open(url, NULL, "menu");
561 html_txt(strlpart(buf, 20)); 563 html_txt(strlpart(buf, 20));
562 html_link_close(); 564 html_link_close();
563 return 0; 565 return 0;
564} 566}
565 567
566void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page) 568void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page)
567{ 569{
568 char *url; 570 char *url;
569 571
570 if (!ctx.cfg.virtual_root) { 572 if (!ctx.cfg.virtual_root) {
571 url = fmt("%s/%s", ctx.qry.repo, page); 573 url = fmt("%s/%s", ctx.qry.repo, page);
572 if (ctx.qry.path) 574 if (ctx.qry.path)
573 url = fmt("%s/%s", url, ctx.qry.path); 575 url = fmt("%s/%s", url, ctx.qry.path);
574 html_hidden("url", url); 576 html_hidden("url", url);
575 } 577 }
576 578
577 if (incl_head && ctx.qry.head && ctx.repo->defbranch && 579 if (incl_head && ctx.qry.head && ctx.repo->defbranch &&
578 strcmp(ctx.qry.head, ctx.repo->defbranch)) 580 strcmp(ctx.qry.head, ctx.repo->defbranch))
579 html_hidden("h", ctx.qry.head); 581 html_hidden("h", ctx.qry.head);
580 582
581 if (ctx.qry.sha1) 583 if (ctx.qry.sha1)
582 html_hidden("id", ctx.qry.sha1); 584 html_hidden("id", ctx.qry.sha1);
583 if (ctx.qry.sha2) 585 if (ctx.qry.sha2)
584 html_hidden("id2", ctx.qry.sha2); 586 html_hidden("id2", ctx.qry.sha2);
585 if (ctx.qry.showmsg) 587 if (ctx.qry.showmsg)
586 html_hidden("showmsg", "1"); 588 html_hidden("showmsg", "1");
587 589
588 if (incl_search) { 590 if (incl_search) {
589 if (ctx.qry.grep) 591 if (ctx.qry.grep)
590 html_hidden("qt", ctx.qry.grep); 592 html_hidden("qt", ctx.qry.grep);
591 if (ctx.qry.search) 593 if (ctx.qry.search)
592 html_hidden("q", ctx.qry.search); 594 html_hidden("q", ctx.qry.search);
593 } 595 }
594} 596}
595 597
596const char *fallback_cmd = "repolist"; 598const char *fallback_cmd = "repolist";
597 599
598char *hc(struct cgit_cmd *cmd, const char *page) 600char *hc(struct cgit_cmd *cmd, const char *page)
599{ 601{
600 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); 602 return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active");
601} 603}
602 604
603void cgit_print_pageheader(struct cgit_context *ctx) 605void cgit_print_pageheader(struct cgit_context *ctx)
604{ 606{
605 struct cgit_cmd *cmd = cgit_get_cmd(ctx); 607 struct cgit_cmd *cmd = cgit_get_cmd(ctx);
606 608
607 if (!cmd && ctx->repo) 609 if (!cmd && ctx->repo)
608 fallback_cmd = "summary"; 610 fallback_cmd = "summary";
609 611
610 html("<table id='header'>\n"); 612 html("<table id='header'>\n");
611 html("<tr>\n"); 613 html("<tr>\n");
612 html("<td class='logo' rowspan='2'><a href='"); 614 html("<td class='logo' rowspan='2'><a href='");
613 if (ctx->cfg.logo_link) 615 if (ctx->cfg.logo_link)
614 html_attr(ctx->cfg.logo_link); 616 html_attr(ctx->cfg.logo_link);
615 else 617 else
616 html_attr(cgit_rooturl()); 618 html_attr(cgit_rooturl());
617 html("'><img src='"); 619 html("'><img src='");
618 html_attr(ctx->cfg.logo); 620 html_attr(ctx->cfg.logo);
619 html("' alt='cgit logo'/></a></td>\n"); 621 html("' alt='cgit logo'/></a></td>\n");
620 622
621 html("<td class='main'>"); 623 html("<td class='main'>");
622 if (ctx->repo) { 624 if (ctx->repo) {
623 cgit_index_link("index", NULL, NULL, NULL, 0); 625 cgit_index_link("index", NULL, NULL, NULL, 0);
624 html(" : "); 626 html(" : ");
625 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 627 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
626 html("</td><td class='form'>"); 628 html("</td><td class='form'>");
627 html("<form method='get' action=''>\n"); 629 html("<form method='get' action=''>\n");
628 cgit_add_hidden_formfields(0, 1, ctx->qry.page); 630 cgit_add_hidden_formfields(0, 1, ctx->qry.page);
629 html("<select name='h' onchange='this.form.submit();'>\n"); 631 html("<select name='h' onchange='this.form.submit();'>\n");
630 for_each_branch_ref(print_branch_option, ctx->qry.head); 632 for_each_branch_ref(print_branch_option, ctx->qry.head);
631 html("</select> "); 633 html("</select> ");
632 html("<input type='submit' name='' value='switch'/>"); 634 html("<input type='submit' name='' value='switch'/>");
633 html("</form>"); 635 html("</form>");
634 } else 636 } else
635 html_txt(ctx->cfg.root_title); 637 html_txt(ctx->cfg.root_title);
636 html("</td></tr>\n"); 638 html("</td></tr>\n");
637 639
638 html("<tr><td class='sub'>"); 640 html("<tr><td class='sub'>");
639 if (ctx->repo) { 641 if (ctx->repo) {
640 html_txt(ctx->repo->desc); 642 html_txt(ctx->repo->desc);
641 html("</td><td class='sub right'>"); 643 html("</td><td class='sub right'>");
642 html_txt(ctx->repo->owner); 644 html_txt(ctx->repo->owner);
643 } else { 645 } else {
644 if (ctx->cfg.root_desc) 646 if (ctx->cfg.root_desc)
645 html_txt(ctx->cfg.root_desc); 647 html_txt(ctx->cfg.root_desc);
646 else if (ctx->cfg.index_info) 648 else if (ctx->cfg.index_info)
647 html_include(ctx->cfg.index_info); 649 html_include(ctx->cfg.index_info);
648 } 650 }
649 html("</td></tr></table>\n"); 651 html("</td></tr></table>\n");
650 652
651 html("<table class='tabs'><tr><td>\n"); 653 html("<table class='tabs'><tr><td>\n");
652 if (ctx->repo) { 654 if (ctx->repo) {
653 cgit_summary_link("summary", NULL, hc(cmd, "summary"), 655 cgit_summary_link("summary", NULL, hc(cmd, "summary"),
654 ctx->qry.head); 656 ctx->qry.head);
655 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, 657 cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head,
656 ctx->qry.sha1, NULL); 658 ctx->qry.sha1, NULL);
657 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, 659 cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head,
658 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); 660 NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg);
659 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, 661 cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head,
660 ctx->qry.sha1, NULL); 662 ctx->qry.sha1, NULL);
661 cgit_commit_link("commit", NULL, hc(cmd, "commit"), 663 cgit_commit_link("commit", NULL, hc(cmd, "commit"),
662 ctx->qry.head, ctx->qry.sha1); 664 ctx->qry.head, ctx->qry.sha1);
663 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, 665 cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head,
664 ctx->qry.sha1, ctx->qry.sha2, NULL); 666 ctx->qry.sha1, ctx->qry.sha2, NULL);
665 if (ctx->repo->max_stats) 667 if (ctx->repo->max_stats)
666 cgit_stats_link("stats", NULL, hc(cmd, "stats"), 668 cgit_stats_link("stats", NULL, hc(cmd, "stats"),
667 ctx->qry.head, NULL); 669 ctx->qry.head, NULL);
668 if (ctx->repo->readme) 670 if (ctx->repo->readme)
669 reporevlink("about", "about", NULL, 671 reporevlink("about", "about", NULL,
670 hc(cmd, "about"), ctx->qry.head, NULL, 672 hc(cmd, "about"), ctx->qry.head, NULL,
671 NULL); 673 NULL);
672 html("</td><td class='form'>"); 674 html("</td><td class='form'>");
673 html("<form class='right' method='get' action='"); 675 html("<form class='right' method='get' action='");
674 if (ctx->cfg.virtual_root) 676 if (ctx->cfg.virtual_root)
675 html_url_path(cgit_fileurl(ctx->qry.repo, "log", 677 html_url_path(cgit_fileurl(ctx->qry.repo, "log",
676 ctx->qry.path, NULL)); 678 ctx->qry.path, NULL));
677 html("'>\n"); 679 html("'>\n");
678 cgit_add_hidden_formfields(1, 0, "log"); 680 cgit_add_hidden_formfields(1, 0, "log");
679 html("<select name='qt'>\n"); 681 html("<select name='qt'>\n");
680 html_option("grep", "log msg", ctx->qry.grep); 682 html_option("grep", "log msg", ctx->qry.grep);
681 html_option("author", "author", ctx->qry.grep); 683 html_option("author", "author", ctx->qry.grep);
682 html_option("committer", "committer", ctx->qry.grep); 684 html_option("committer", "committer", ctx->qry.grep);
683 html("</select>\n"); 685 html("</select>\n");
684 html("<input class='txt' type='text' size='10' name='q' value='"); 686 html("<input class='txt' type='text' size='10' name='q' value='");
685 html_attr(ctx->qry.search); 687 html_attr(ctx->qry.search);
686 html("'/>\n"); 688 html("'/>\n");
687 html("<input type='submit' value='search'/>\n"); 689 html("<input type='submit' value='search'/>\n");
688 html("</form>\n"); 690 html("</form>\n");
689 } else { 691 } else {
690 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); 692 site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0);
691 if (ctx->cfg.root_readme) 693 if (ctx->cfg.root_readme)
692 site_link("about", "about", NULL, hc(cmd, "about"), 694 site_link("about", "about", NULL, hc(cmd, "about"),
693 NULL, 0); 695 NULL, 0);
694 html("</td><td class='form'>"); 696 html("</td><td class='form'>");
695 html("<form method='get' action='"); 697 html("<form method='get' action='");
696 html_attr(cgit_rooturl()); 698 html_attr(cgit_rooturl());
697 html("'>\n"); 699 html("'>\n");
698 html("<input type='text' name='q' size='10' value='"); 700 html("<input type='text' name='q' size='10' value='");
699 html_attr(ctx->qry.search); 701 html_attr(ctx->qry.search);
700 html("'/>\n"); 702 html("'/>\n");
701 html("<input type='submit' value='search'/>\n"); 703 html("<input type='submit' value='search'/>\n");
702 html("</form>"); 704 html("</form>");
703 } 705 }
704 html("</td></tr></table>\n"); 706 html("</td></tr></table>\n");
705 html("<div class='content'>"); 707 html("<div class='content'>");
706} 708}
707 709
708void cgit_print_filemode(unsigned short mode) 710void cgit_print_filemode(unsigned short mode)
709{ 711{
710 if (S_ISDIR(mode)) 712 if (S_ISDIR(mode))
711 html("d"); 713 html("d");
712 else if (S_ISLNK(mode)) 714 else if (S_ISLNK(mode))
713 html("l"); 715 html("l");
714 else if (S_ISGITLINK(mode)) 716 else if (S_ISGITLINK(mode))
715 html("m"); 717 html("m");
716 else 718 else
717 html("-"); 719 html("-");
718 html_fileperm(mode >> 6); 720 html_fileperm(mode >> 6);
719 html_fileperm(mode >> 3); 721 html_fileperm(mode >> 3);
720 html_fileperm(mode); 722 html_fileperm(mode);
721} 723}
722 724
723void cgit_print_snapshot_links(const char *repo, const char *head, 725void cgit_print_snapshot_links(const char *repo, const char *head,
724 const char *hex, int snapshots) 726 const char *hex, int snapshots)
725{ 727{
726 const struct cgit_snapshot_format* f; 728 const struct cgit_snapshot_format* f;
727 char *filename; 729 char *filename;
728 730
729 for (f = cgit_snapshot_formats; f->suffix; f++) { 731 for (f = cgit_snapshot_formats; f->suffix; f++) {
730 if (!(snapshots & f->bit)) 732 if (!(snapshots & f->bit))
731 continue; 733 continue;
732 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, 734 filename = fmt("%s-%s%s", cgit_repobasename(repo), hex,
733 f->suffix); 735 f->suffix);
734 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 736 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
735 html("<br/>"); 737 html("<br/>");
736 } 738 }
737} 739}