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