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