summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c4
-rw-r--r--cgit.h2
-rw-r--r--cgitrc.5.txt10
-rw-r--r--cmd.c2
-rw-r--r--shared.c1
-rw-r--r--ui-repolist.c9
-rw-r--r--ui-summary.c28
-rw-r--r--ui-summary.h2
8 files changed, 48 insertions, 10 deletions
diff --git a/cgit.c b/cgit.c
index dbec196..7b228af 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,534 +1,538 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cache.h" 10#include "cache.h"
11#include "cmd.h" 11#include "cmd.h"
12#include "configfile.h" 12#include "configfile.h"
13#include "html.h" 13#include "html.h"
14#include "ui-shared.h" 14#include "ui-shared.h"
15#include "ui-stats.h" 15#include "ui-stats.h"
16#include "scan-tree.h" 16#include "scan-tree.h"
17 17
18const char *cgit_version = CGIT_VERSION; 18const char *cgit_version = CGIT_VERSION;
19 19
20void add_mimetype(const char *name, const char *value) 20void add_mimetype(const char *name, const char *value)
21{ 21{
22 struct string_list_item *item; 22 struct string_list_item *item;
23 23
24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); 24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
25 item->util = xstrdup(value); 25 item->util = xstrdup(value);
26} 26}
27 27
28struct cgit_filter *new_filter(const char *cmd, int extra_args) 28struct cgit_filter *new_filter(const char *cmd, int extra_args)
29{ 29{
30 struct cgit_filter *f; 30 struct cgit_filter *f;
31 31
32 if (!cmd || !cmd[0]) 32 if (!cmd || !cmd[0])
33 return NULL; 33 return NULL;
34 34
35 f = xmalloc(sizeof(struct cgit_filter)); 35 f = xmalloc(sizeof(struct cgit_filter));
36 f->cmd = xstrdup(cmd); 36 f->cmd = xstrdup(cmd);
37 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 37 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
38 f->argv[0] = f->cmd; 38 f->argv[0] = f->cmd;
39 f->argv[1] = NULL; 39 f->argv[1] = NULL;
40 return f; 40 return f;
41} 41}
42 42
43void config_cb(const char *name, const char *value) 43void config_cb(const char *name, const char *value)
44{ 44{
45 if (!strcmp(name, "root-title")) 45 if (!strcmp(name, "root-title"))
46 ctx.cfg.root_title = xstrdup(value); 46 ctx.cfg.root_title = xstrdup(value);
47 else if (!strcmp(name, "root-desc")) 47 else if (!strcmp(name, "root-desc"))
48 ctx.cfg.root_desc = xstrdup(value); 48 ctx.cfg.root_desc = xstrdup(value);
49 else if (!strcmp(name, "root-readme")) 49 else if (!strcmp(name, "root-readme"))
50 ctx.cfg.root_readme = xstrdup(value); 50 ctx.cfg.root_readme = xstrdup(value);
51 else if (!strcmp(name, "css")) 51 else if (!strcmp(name, "css"))
52 ctx.cfg.css = xstrdup(value); 52 ctx.cfg.css = xstrdup(value);
53 else if (!strcmp(name, "favicon")) 53 else if (!strcmp(name, "favicon"))
54 ctx.cfg.favicon = xstrdup(value); 54 ctx.cfg.favicon = xstrdup(value);
55 else if (!strcmp(name, "footer")) 55 else if (!strcmp(name, "footer"))
56 ctx.cfg.footer = xstrdup(value); 56 ctx.cfg.footer = xstrdup(value);
57 else if (!strcmp(name, "head-include")) 57 else if (!strcmp(name, "head-include"))
58 ctx.cfg.head_include = xstrdup(value); 58 ctx.cfg.head_include = xstrdup(value);
59 else if (!strcmp(name, "header")) 59 else if (!strcmp(name, "header"))
60 ctx.cfg.header = xstrdup(value); 60 ctx.cfg.header = xstrdup(value);
61 else if (!strcmp(name, "logo")) 61 else if (!strcmp(name, "logo"))
62 ctx.cfg.logo = xstrdup(value); 62 ctx.cfg.logo = xstrdup(value);
63 else if (!strcmp(name, "index-header")) 63 else if (!strcmp(name, "index-header"))
64 ctx.cfg.index_header = xstrdup(value); 64 ctx.cfg.index_header = xstrdup(value);
65 else if (!strcmp(name, "index-info")) 65 else if (!strcmp(name, "index-info"))
66 ctx.cfg.index_info = xstrdup(value); 66 ctx.cfg.index_info = xstrdup(value);
67 else if (!strcmp(name, "logo-link")) 67 else if (!strcmp(name, "logo-link"))
68 ctx.cfg.logo_link = xstrdup(value); 68 ctx.cfg.logo_link = xstrdup(value);
69 else if (!strcmp(name, "module-link")) 69 else if (!strcmp(name, "module-link"))
70 ctx.cfg.module_link = xstrdup(value); 70 ctx.cfg.module_link = xstrdup(value);
71 else if (!strcmp(name, "virtual-root")) { 71 else if (!strcmp(name, "virtual-root")) {
72 ctx.cfg.virtual_root = trim_end(value, '/'); 72 ctx.cfg.virtual_root = trim_end(value, '/');
73 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 73 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
74 ctx.cfg.virtual_root = ""; 74 ctx.cfg.virtual_root = "";
75 } else if (!strcmp(name, "nocache")) 75 } else if (!strcmp(name, "nocache"))
76 ctx.cfg.nocache = atoi(value); 76 ctx.cfg.nocache = atoi(value);
77 else if (!strcmp(name, "noplainemail")) 77 else if (!strcmp(name, "noplainemail"))
78 ctx.cfg.noplainemail = atoi(value); 78 ctx.cfg.noplainemail = atoi(value);
79 else if (!strcmp(name, "noheader")) 79 else if (!strcmp(name, "noheader"))
80 ctx.cfg.noheader = atoi(value); 80 ctx.cfg.noheader = atoi(value);
81 else if (!strcmp(name, "snapshots")) 81 else if (!strcmp(name, "snapshots"))
82 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 82 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
83 else if (!strcmp(name, "enable-index-links")) 83 else if (!strcmp(name, "enable-index-links"))
84 ctx.cfg.enable_index_links = atoi(value); 84 ctx.cfg.enable_index_links = atoi(value);
85 else if (!strcmp(name, "enable-log-filecount")) 85 else if (!strcmp(name, "enable-log-filecount"))
86 ctx.cfg.enable_log_filecount = atoi(value); 86 ctx.cfg.enable_log_filecount = atoi(value);
87 else if (!strcmp(name, "enable-log-linecount")) 87 else if (!strcmp(name, "enable-log-linecount"))
88 ctx.cfg.enable_log_linecount = atoi(value); 88 ctx.cfg.enable_log_linecount = atoi(value);
89 else if (!strcmp(name, "max-stats")) 89 else if (!strcmp(name, "max-stats"))
90 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 90 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
91 else if (!strcmp(name, "cache-size")) 91 else if (!strcmp(name, "cache-size"))
92 ctx.cfg.cache_size = atoi(value); 92 ctx.cfg.cache_size = atoi(value);
93 else if (!strcmp(name, "cache-root")) 93 else if (!strcmp(name, "cache-root"))
94 ctx.cfg.cache_root = xstrdup(value); 94 ctx.cfg.cache_root = xstrdup(value);
95 else if (!strcmp(name, "cache-root-ttl")) 95 else if (!strcmp(name, "cache-root-ttl"))
96 ctx.cfg.cache_root_ttl = atoi(value); 96 ctx.cfg.cache_root_ttl = atoi(value);
97 else if (!strcmp(name, "cache-repo-ttl")) 97 else if (!strcmp(name, "cache-repo-ttl"))
98 ctx.cfg.cache_repo_ttl = atoi(value); 98 ctx.cfg.cache_repo_ttl = atoi(value);
99 else if (!strcmp(name, "cache-static-ttl")) 99 else if (!strcmp(name, "cache-static-ttl"))
100 ctx.cfg.cache_static_ttl = atoi(value); 100 ctx.cfg.cache_static_ttl = atoi(value);
101 else if (!strcmp(name, "cache-dynamic-ttl")) 101 else if (!strcmp(name, "cache-dynamic-ttl"))
102 ctx.cfg.cache_dynamic_ttl = atoi(value); 102 ctx.cfg.cache_dynamic_ttl = atoi(value);
103 else if (!strcmp(name, "about-filter"))
104 ctx.cfg.about_filter = new_filter(value, 0);
103 else if (!strcmp(name, "commit-filter")) 105 else if (!strcmp(name, "commit-filter"))
104 ctx.cfg.commit_filter = new_filter(value, 0); 106 ctx.cfg.commit_filter = new_filter(value, 0);
105 else if (!strcmp(name, "embedded")) 107 else if (!strcmp(name, "embedded"))
106 ctx.cfg.embedded = atoi(value); 108 ctx.cfg.embedded = atoi(value);
107 else if (!strcmp(name, "max-message-length")) 109 else if (!strcmp(name, "max-message-length"))
108 ctx.cfg.max_msg_len = atoi(value); 110 ctx.cfg.max_msg_len = atoi(value);
109 else if (!strcmp(name, "max-repodesc-length")) 111 else if (!strcmp(name, "max-repodesc-length"))
110 ctx.cfg.max_repodesc_len = atoi(value); 112 ctx.cfg.max_repodesc_len = atoi(value);
111 else if (!strcmp(name, "max-repo-count")) 113 else if (!strcmp(name, "max-repo-count"))
112 ctx.cfg.max_repo_count = atoi(value); 114 ctx.cfg.max_repo_count = atoi(value);
113 else if (!strcmp(name, "max-commit-count")) 115 else if (!strcmp(name, "max-commit-count"))
114 ctx.cfg.max_commit_count = atoi(value); 116 ctx.cfg.max_commit_count = atoi(value);
115 else if (!strcmp(name, "source-filter")) 117 else if (!strcmp(name, "source-filter"))
116 ctx.cfg.source_filter = new_filter(value, 1); 118 ctx.cfg.source_filter = new_filter(value, 1);
117 else if (!strcmp(name, "summary-log")) 119 else if (!strcmp(name, "summary-log"))
118 ctx.cfg.summary_log = atoi(value); 120 ctx.cfg.summary_log = atoi(value);
119 else if (!strcmp(name, "summary-branches")) 121 else if (!strcmp(name, "summary-branches"))
120 ctx.cfg.summary_branches = atoi(value); 122 ctx.cfg.summary_branches = atoi(value);
121 else if (!strcmp(name, "summary-tags")) 123 else if (!strcmp(name, "summary-tags"))
122 ctx.cfg.summary_tags = atoi(value); 124 ctx.cfg.summary_tags = atoi(value);
123 else if (!strcmp(name, "agefile")) 125 else if (!strcmp(name, "agefile"))
124 ctx.cfg.agefile = xstrdup(value); 126 ctx.cfg.agefile = xstrdup(value);
125 else if (!strcmp(name, "renamelimit")) 127 else if (!strcmp(name, "renamelimit"))
126 ctx.cfg.renamelimit = atoi(value); 128 ctx.cfg.renamelimit = atoi(value);
127 else if (!strcmp(name, "robots")) 129 else if (!strcmp(name, "robots"))
128 ctx.cfg.robots = xstrdup(value); 130 ctx.cfg.robots = xstrdup(value);
129 else if (!strcmp(name, "clone-prefix")) 131 else if (!strcmp(name, "clone-prefix"))
130 ctx.cfg.clone_prefix = xstrdup(value); 132 ctx.cfg.clone_prefix = xstrdup(value);
131 else if (!strcmp(name, "local-time")) 133 else if (!strcmp(name, "local-time"))
132 ctx.cfg.local_time = atoi(value); 134 ctx.cfg.local_time = atoi(value);
133 else if (!prefixcmp(name, "mimetype.")) 135 else if (!prefixcmp(name, "mimetype."))
134 add_mimetype(name + 9, value); 136 add_mimetype(name + 9, value);
135 else if (!strcmp(name, "repo.group")) 137 else if (!strcmp(name, "repo.group"))
136 ctx.cfg.repo_group = xstrdup(value); 138 ctx.cfg.repo_group = xstrdup(value);
137 else if (!strcmp(name, "repo.url")) 139 else if (!strcmp(name, "repo.url"))
138 ctx.repo = cgit_add_repo(value); 140 ctx.repo = cgit_add_repo(value);
139 else if (!strcmp(name, "repo.name")) 141 else if (!strcmp(name, "repo.name"))
140 ctx.repo->name = xstrdup(value); 142 ctx.repo->name = xstrdup(value);
141 else if (ctx.repo && !strcmp(name, "repo.path")) 143 else if (ctx.repo && !strcmp(name, "repo.path"))
142 ctx.repo->path = trim_end(value, '/'); 144 ctx.repo->path = trim_end(value, '/');
143 else if (ctx.repo && !strcmp(name, "repo.clone-url")) 145 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
144 ctx.repo->clone_url = xstrdup(value); 146 ctx.repo->clone_url = xstrdup(value);
145 else if (ctx.repo && !strcmp(name, "repo.desc")) 147 else if (ctx.repo && !strcmp(name, "repo.desc"))
146 ctx.repo->desc = xstrdup(value); 148 ctx.repo->desc = xstrdup(value);
147 else if (ctx.repo && !strcmp(name, "repo.owner")) 149 else if (ctx.repo && !strcmp(name, "repo.owner"))
148 ctx.repo->owner = xstrdup(value); 150 ctx.repo->owner = xstrdup(value);
149 else if (ctx.repo && !strcmp(name, "repo.defbranch")) 151 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
150 ctx.repo->defbranch = xstrdup(value); 152 ctx.repo->defbranch = xstrdup(value);
151 else if (ctx.repo && !strcmp(name, "repo.snapshots")) 153 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
152 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ 154 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
153 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) 155 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
154 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 156 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
155 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) 157 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
156 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 158 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
157 else if (ctx.repo && !strcmp(name, "repo.max-stats")) 159 else if (ctx.repo && !strcmp(name, "repo.max-stats"))
158 ctx.repo->max_stats = cgit_find_stats_period(value, NULL); 160 ctx.repo->max_stats = cgit_find_stats_period(value, NULL);
159 else if (ctx.repo && !strcmp(name, "repo.module-link")) 161 else if (ctx.repo && !strcmp(name, "repo.module-link"))
160 ctx.repo->module_link= xstrdup(value); 162 ctx.repo->module_link= xstrdup(value);
163 else if (ctx.repo && !strcmp(name, "repo.about-filter"))
164 ctx.repo->about_filter = new_filter(value, 0);
161 else if (ctx.repo && !strcmp(name, "repo.commit-filter")) 165 else if (ctx.repo && !strcmp(name, "repo.commit-filter"))
162 ctx.repo->commit_filter = new_filter(value, 0); 166 ctx.repo->commit_filter = new_filter(value, 0);
163 else if (ctx.repo && !strcmp(name, "repo.source-filter")) 167 else if (ctx.repo && !strcmp(name, "repo.source-filter"))
164 ctx.repo->source_filter = new_filter(value, 1); 168 ctx.repo->source_filter = new_filter(value, 1);
165 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 169 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
166 if (*value == '/') 170 if (*value == '/')
167 ctx.repo->readme = xstrdup(value); 171 ctx.repo->readme = xstrdup(value);
168 else 172 else
169 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 173 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
170 } else if (!strcmp(name, "include")) 174 } else if (!strcmp(name, "include"))
171 parse_configfile(value, config_cb); 175 parse_configfile(value, config_cb);
172} 176}
173 177
174static void querystring_cb(const char *name, const char *value) 178static void querystring_cb(const char *name, const char *value)
175{ 179{
176 if (!strcmp(name,"r")) { 180 if (!strcmp(name,"r")) {
177 ctx.qry.repo = xstrdup(value); 181 ctx.qry.repo = xstrdup(value);
178 ctx.repo = cgit_get_repoinfo(value); 182 ctx.repo = cgit_get_repoinfo(value);
179 } else if (!strcmp(name, "p")) { 183 } else if (!strcmp(name, "p")) {
180 ctx.qry.page = xstrdup(value); 184 ctx.qry.page = xstrdup(value);
181 } else if (!strcmp(name, "url")) { 185 } else if (!strcmp(name, "url")) {
182 ctx.qry.url = xstrdup(value); 186 ctx.qry.url = xstrdup(value);
183 cgit_parse_url(value); 187 cgit_parse_url(value);
184 } else if (!strcmp(name, "qt")) { 188 } else if (!strcmp(name, "qt")) {
185 ctx.qry.grep = xstrdup(value); 189 ctx.qry.grep = xstrdup(value);
186 } else if (!strcmp(name, "q")) { 190 } else if (!strcmp(name, "q")) {
187 ctx.qry.search = xstrdup(value); 191 ctx.qry.search = xstrdup(value);
188 } else if (!strcmp(name, "h")) { 192 } else if (!strcmp(name, "h")) {
189 ctx.qry.head = xstrdup(value); 193 ctx.qry.head = xstrdup(value);
190 ctx.qry.has_symref = 1; 194 ctx.qry.has_symref = 1;
191 } else if (!strcmp(name, "id")) { 195 } else if (!strcmp(name, "id")) {
192 ctx.qry.sha1 = xstrdup(value); 196 ctx.qry.sha1 = xstrdup(value);
193 ctx.qry.has_sha1 = 1; 197 ctx.qry.has_sha1 = 1;
194 } else if (!strcmp(name, "id2")) { 198 } else if (!strcmp(name, "id2")) {
195 ctx.qry.sha2 = xstrdup(value); 199 ctx.qry.sha2 = xstrdup(value);
196 ctx.qry.has_sha1 = 1; 200 ctx.qry.has_sha1 = 1;
197 } else if (!strcmp(name, "ofs")) { 201 } else if (!strcmp(name, "ofs")) {
198 ctx.qry.ofs = atoi(value); 202 ctx.qry.ofs = atoi(value);
199 } else if (!strcmp(name, "path")) { 203 } else if (!strcmp(name, "path")) {
200 ctx.qry.path = trim_end(value, '/'); 204 ctx.qry.path = trim_end(value, '/');
201 } else if (!strcmp(name, "name")) { 205 } else if (!strcmp(name, "name")) {
202 ctx.qry.name = xstrdup(value); 206 ctx.qry.name = xstrdup(value);
203 } else if (!strcmp(name, "mimetype")) { 207 } else if (!strcmp(name, "mimetype")) {
204 ctx.qry.mimetype = xstrdup(value); 208 ctx.qry.mimetype = xstrdup(value);
205 } else if (!strcmp(name, "s")){ 209 } else if (!strcmp(name, "s")){
206 ctx.qry.sort = xstrdup(value); 210 ctx.qry.sort = xstrdup(value);
207 } else if (!strcmp(name, "showmsg")) { 211 } else if (!strcmp(name, "showmsg")) {
208 ctx.qry.showmsg = atoi(value); 212 ctx.qry.showmsg = atoi(value);
209 } else if (!strcmp(name, "period")) { 213 } else if (!strcmp(name, "period")) {
210 ctx.qry.period = xstrdup(value); 214 ctx.qry.period = xstrdup(value);
211 } 215 }
212} 216}
213 217
214static void prepare_context(struct cgit_context *ctx) 218static void prepare_context(struct cgit_context *ctx)
215{ 219{
216 memset(ctx, 0, sizeof(ctx)); 220 memset(ctx, 0, sizeof(ctx));
217 ctx->cfg.agefile = "info/web/last-modified"; 221 ctx->cfg.agefile = "info/web/last-modified";
218 ctx->cfg.nocache = 0; 222 ctx->cfg.nocache = 0;
219 ctx->cfg.cache_size = 0; 223 ctx->cfg.cache_size = 0;
220 ctx->cfg.cache_dynamic_ttl = 5; 224 ctx->cfg.cache_dynamic_ttl = 5;
221 ctx->cfg.cache_max_create_time = 5; 225 ctx->cfg.cache_max_create_time = 5;
222 ctx->cfg.cache_repo_ttl = 5; 226 ctx->cfg.cache_repo_ttl = 5;
223 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 227 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
224 ctx->cfg.cache_root_ttl = 5; 228 ctx->cfg.cache_root_ttl = 5;
225 ctx->cfg.cache_static_ttl = -1; 229 ctx->cfg.cache_static_ttl = -1;
226 ctx->cfg.css = "/cgit.css"; 230 ctx->cfg.css = "/cgit.css";
227 ctx->cfg.logo = "/git-logo.png"; 231 ctx->cfg.logo = "/git-logo.png";
228 ctx->cfg.local_time = 0; 232 ctx->cfg.local_time = 0;
229 ctx->cfg.max_repo_count = 50; 233 ctx->cfg.max_repo_count = 50;
230 ctx->cfg.max_commit_count = 50; 234 ctx->cfg.max_commit_count = 50;
231 ctx->cfg.max_lock_attempts = 5; 235 ctx->cfg.max_lock_attempts = 5;
232 ctx->cfg.max_msg_len = 80; 236 ctx->cfg.max_msg_len = 80;
233 ctx->cfg.max_repodesc_len = 80; 237 ctx->cfg.max_repodesc_len = 80;
234 ctx->cfg.max_stats = 0; 238 ctx->cfg.max_stats = 0;
235 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 239 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
236 ctx->cfg.renamelimit = -1; 240 ctx->cfg.renamelimit = -1;
237 ctx->cfg.robots = "index, nofollow"; 241 ctx->cfg.robots = "index, nofollow";
238 ctx->cfg.root_title = "Git repository browser"; 242 ctx->cfg.root_title = "Git repository browser";
239 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 243 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
240 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 244 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
241 ctx->cfg.summary_branches = 10; 245 ctx->cfg.summary_branches = 10;
242 ctx->cfg.summary_log = 10; 246 ctx->cfg.summary_log = 10;
243 ctx->cfg.summary_tags = 10; 247 ctx->cfg.summary_tags = 10;
244 ctx->page.mimetype = "text/html"; 248 ctx->page.mimetype = "text/html";
245 ctx->page.charset = PAGE_ENCODING; 249 ctx->page.charset = PAGE_ENCODING;
246 ctx->page.filename = NULL; 250 ctx->page.filename = NULL;
247 ctx->page.size = 0; 251 ctx->page.size = 0;
248 ctx->page.modified = time(NULL); 252 ctx->page.modified = time(NULL);
249 ctx->page.expires = ctx->page.modified; 253 ctx->page.expires = ctx->page.modified;
250 ctx->page.etag = NULL; 254 ctx->page.etag = NULL;
251 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 255 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
252} 256}
253 257
254struct refmatch { 258struct refmatch {
255 char *req_ref; 259 char *req_ref;
256 char *first_ref; 260 char *first_ref;
257 int match; 261 int match;
258}; 262};
259 263
260int find_current_ref(const char *refname, const unsigned char *sha1, 264int find_current_ref(const char *refname, const unsigned char *sha1,
261 int flags, void *cb_data) 265 int flags, void *cb_data)
262{ 266{
263 struct refmatch *info; 267 struct refmatch *info;
264 268
265 info = (struct refmatch *)cb_data; 269 info = (struct refmatch *)cb_data;
266 if (!strcmp(refname, info->req_ref)) 270 if (!strcmp(refname, info->req_ref))
267 info->match = 1; 271 info->match = 1;
268 if (!info->first_ref) 272 if (!info->first_ref)
269 info->first_ref = xstrdup(refname); 273 info->first_ref = xstrdup(refname);
270 return info->match; 274 return info->match;
271} 275}
272 276
273char *find_default_branch(struct cgit_repo *repo) 277char *find_default_branch(struct cgit_repo *repo)
274{ 278{
275 struct refmatch info; 279 struct refmatch info;
276 char *ref; 280 char *ref;
277 281
278 info.req_ref = repo->defbranch; 282 info.req_ref = repo->defbranch;
279 info.first_ref = NULL; 283 info.first_ref = NULL;
280 info.match = 0; 284 info.match = 0;
281 for_each_branch_ref(find_current_ref, &info); 285 for_each_branch_ref(find_current_ref, &info);
282 if (info.match) 286 if (info.match)
283 ref = info.req_ref; 287 ref = info.req_ref;
284 else 288 else
285 ref = info.first_ref; 289 ref = info.first_ref;
286 if (ref) 290 if (ref)
287 ref = xstrdup(ref); 291 ref = xstrdup(ref);
288 return ref; 292 return ref;
289} 293}
290 294
291static int prepare_repo_cmd(struct cgit_context *ctx) 295static int prepare_repo_cmd(struct cgit_context *ctx)
292{ 296{
293 char *tmp; 297 char *tmp;
294 unsigned char sha1[20]; 298 unsigned char sha1[20];
295 int nongit = 0; 299 int nongit = 0;
296 300
297 setenv("GIT_DIR", ctx->repo->path, 1); 301 setenv("GIT_DIR", ctx->repo->path, 1);
298 setup_git_directory_gently(&nongit); 302 setup_git_directory_gently(&nongit);
299 if (nongit) { 303 if (nongit) {
300 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 304 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
301 "config error"); 305 "config error");
302 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 306 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
303 ctx->repo = NULL; 307 ctx->repo = NULL;
304 cgit_print_http_headers(ctx); 308 cgit_print_http_headers(ctx);
305 cgit_print_docstart(ctx); 309 cgit_print_docstart(ctx);
306 cgit_print_pageheader(ctx); 310 cgit_print_pageheader(ctx);
307 cgit_print_error(tmp); 311 cgit_print_error(tmp);
308 cgit_print_docend(); 312 cgit_print_docend();
309 return 1; 313 return 1;
310 } 314 }
311 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 315 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
312 316
313 if (!ctx->qry.head) { 317 if (!ctx->qry.head) {
314 ctx->qry.nohead = 1; 318 ctx->qry.nohead = 1;
315 ctx->qry.head = find_default_branch(ctx->repo); 319 ctx->qry.head = find_default_branch(ctx->repo);
316 ctx->repo->defbranch = ctx->qry.head; 320 ctx->repo->defbranch = ctx->qry.head;
317 } 321 }
318 322
319 if (!ctx->qry.head) { 323 if (!ctx->qry.head) {
320 cgit_print_http_headers(ctx); 324 cgit_print_http_headers(ctx);
321 cgit_print_docstart(ctx); 325 cgit_print_docstart(ctx);
322 cgit_print_pageheader(ctx); 326 cgit_print_pageheader(ctx);
323 cgit_print_error("Repository seems to be empty"); 327 cgit_print_error("Repository seems to be empty");
324 cgit_print_docend(); 328 cgit_print_docend();
325 return 1; 329 return 1;
326 } 330 }
327 331
328 if (get_sha1(ctx->qry.head, sha1)) { 332 if (get_sha1(ctx->qry.head, sha1)) {
329 tmp = xstrdup(ctx->qry.head); 333 tmp = xstrdup(ctx->qry.head);
330 ctx->qry.head = ctx->repo->defbranch; 334 ctx->qry.head = ctx->repo->defbranch;
331 ctx->page.status = 404; 335 ctx->page.status = 404;
332 ctx->page.statusmsg = "not found"; 336 ctx->page.statusmsg = "not found";
333 cgit_print_http_headers(ctx); 337 cgit_print_http_headers(ctx);
334 cgit_print_docstart(ctx); 338 cgit_print_docstart(ctx);
335 cgit_print_pageheader(ctx); 339 cgit_print_pageheader(ctx);
336 cgit_print_error(fmt("Invalid branch: %s", tmp)); 340 cgit_print_error(fmt("Invalid branch: %s", tmp));
337 cgit_print_docend(); 341 cgit_print_docend();
338 return 1; 342 return 1;
339 } 343 }
340 return 0; 344 return 0;
341} 345}
342 346
343static void process_request(void *cbdata) 347static void process_request(void *cbdata)
344{ 348{
345 struct cgit_context *ctx = cbdata; 349 struct cgit_context *ctx = cbdata;
346 struct cgit_cmd *cmd; 350 struct cgit_cmd *cmd;
347 351
348 cmd = cgit_get_cmd(ctx); 352 cmd = cgit_get_cmd(ctx);
349 if (!cmd) { 353 if (!cmd) {
350 ctx->page.title = "cgit error"; 354 ctx->page.title = "cgit error";
351 cgit_print_http_headers(ctx); 355 cgit_print_http_headers(ctx);
352 cgit_print_docstart(ctx); 356 cgit_print_docstart(ctx);
353 cgit_print_pageheader(ctx); 357 cgit_print_pageheader(ctx);
354 cgit_print_error("Invalid request"); 358 cgit_print_error("Invalid request");
355 cgit_print_docend(); 359 cgit_print_docend();
356 return; 360 return;
357 } 361 }
358 362
359 if (cmd->want_repo && !ctx->repo) { 363 if (cmd->want_repo && !ctx->repo) {
360 cgit_print_http_headers(ctx); 364 cgit_print_http_headers(ctx);
361 cgit_print_docstart(ctx); 365 cgit_print_docstart(ctx);
362 cgit_print_pageheader(ctx); 366 cgit_print_pageheader(ctx);
363 cgit_print_error(fmt("No repository selected")); 367 cgit_print_error(fmt("No repository selected"));
364 cgit_print_docend(); 368 cgit_print_docend();
365 return; 369 return;
366 } 370 }
367 371
368 if (ctx->repo && prepare_repo_cmd(ctx)) 372 if (ctx->repo && prepare_repo_cmd(ctx))
369 return; 373 return;
370 374
371 if (cmd->want_layout) { 375 if (cmd->want_layout) {
372 cgit_print_http_headers(ctx); 376 cgit_print_http_headers(ctx);
373 cgit_print_docstart(ctx); 377 cgit_print_docstart(ctx);
374 cgit_print_pageheader(ctx); 378 cgit_print_pageheader(ctx);
375 } 379 }
376 380
377 cmd->fn(ctx); 381 cmd->fn(ctx);
378 382
379 if (cmd->want_layout) 383 if (cmd->want_layout)
380 cgit_print_docend(); 384 cgit_print_docend();
381} 385}
382 386
383int cmp_repos(const void *a, const void *b) 387int cmp_repos(const void *a, const void *b)
384{ 388{
385 const struct cgit_repo *ra = a, *rb = b; 389 const struct cgit_repo *ra = a, *rb = b;
386 return strcmp(ra->url, rb->url); 390 return strcmp(ra->url, rb->url);
387} 391}
388 392
389void print_repo(struct cgit_repo *repo) 393void print_repo(struct cgit_repo *repo)
390{ 394{
391 printf("repo.url=%s\n", repo->url); 395 printf("repo.url=%s\n", repo->url);
392 printf("repo.name=%s\n", repo->name); 396 printf("repo.name=%s\n", repo->name);
393 printf("repo.path=%s\n", repo->path); 397 printf("repo.path=%s\n", repo->path);
394 if (repo->owner) 398 if (repo->owner)
395 printf("repo.owner=%s\n", repo->owner); 399 printf("repo.owner=%s\n", repo->owner);
396 if (repo->desc) 400 if (repo->desc)
397 printf("repo.desc=%s\n", repo->desc); 401 printf("repo.desc=%s\n", repo->desc);
398 if (repo->readme) 402 if (repo->readme)
399 printf("repo.readme=%s\n", repo->readme); 403 printf("repo.readme=%s\n", repo->readme);
400 printf("\n"); 404 printf("\n");
401} 405}
402 406
403void print_repolist(struct cgit_repolist *list) 407void print_repolist(struct cgit_repolist *list)
404{ 408{
405 int i; 409 int i;
406 410
407 for(i = 0; i < list->count; i++) 411 for(i = 0; i < list->count; i++)
408 print_repo(&list->repos[i]); 412 print_repo(&list->repos[i]);
409} 413}
410 414
411 415
412static void cgit_parse_args(int argc, const char **argv) 416static void cgit_parse_args(int argc, const char **argv)
413{ 417{
414 int i; 418 int i;
415 int scan = 0; 419 int scan = 0;
416 420
417 for (i = 1; i < argc; i++) { 421 for (i = 1; i < argc; i++) {
418 if (!strncmp(argv[i], "--cache=", 8)) { 422 if (!strncmp(argv[i], "--cache=", 8)) {
419 ctx.cfg.cache_root = xstrdup(argv[i]+8); 423 ctx.cfg.cache_root = xstrdup(argv[i]+8);
420 } 424 }
421 if (!strcmp(argv[i], "--nocache")) { 425 if (!strcmp(argv[i], "--nocache")) {
422 ctx.cfg.nocache = 1; 426 ctx.cfg.nocache = 1;
423 } 427 }
424 if (!strncmp(argv[i], "--query=", 8)) { 428 if (!strncmp(argv[i], "--query=", 8)) {
425 ctx.qry.raw = xstrdup(argv[i]+8); 429 ctx.qry.raw = xstrdup(argv[i]+8);
426 } 430 }
427 if (!strncmp(argv[i], "--repo=", 7)) { 431 if (!strncmp(argv[i], "--repo=", 7)) {
428 ctx.qry.repo = xstrdup(argv[i]+7); 432 ctx.qry.repo = xstrdup(argv[i]+7);
429 } 433 }
430 if (!strncmp(argv[i], "--page=", 7)) { 434 if (!strncmp(argv[i], "--page=", 7)) {
431 ctx.qry.page = xstrdup(argv[i]+7); 435 ctx.qry.page = xstrdup(argv[i]+7);
432 } 436 }
433 if (!strncmp(argv[i], "--head=", 7)) { 437 if (!strncmp(argv[i], "--head=", 7)) {
434 ctx.qry.head = xstrdup(argv[i]+7); 438 ctx.qry.head = xstrdup(argv[i]+7);
435 ctx.qry.has_symref = 1; 439 ctx.qry.has_symref = 1;
436 } 440 }
437 if (!strncmp(argv[i], "--sha1=", 7)) { 441 if (!strncmp(argv[i], "--sha1=", 7)) {
438 ctx.qry.sha1 = xstrdup(argv[i]+7); 442 ctx.qry.sha1 = xstrdup(argv[i]+7);
439 ctx.qry.has_sha1 = 1; 443 ctx.qry.has_sha1 = 1;
440 } 444 }
441 if (!strncmp(argv[i], "--ofs=", 6)) { 445 if (!strncmp(argv[i], "--ofs=", 6)) {
442 ctx.qry.ofs = atoi(argv[i]+6); 446 ctx.qry.ofs = atoi(argv[i]+6);
443 } 447 }
444 if (!strncmp(argv[i], "--scan-tree=", 12)) { 448 if (!strncmp(argv[i], "--scan-tree=", 12)) {
445 scan++; 449 scan++;
446 scan_tree(argv[i] + 12); 450 scan_tree(argv[i] + 12);
447 } 451 }
448 } 452 }
449 if (scan) { 453 if (scan) {
450 qsort(cgit_repolist.repos, cgit_repolist.count, 454 qsort(cgit_repolist.repos, cgit_repolist.count,
451 sizeof(struct cgit_repo), cmp_repos); 455 sizeof(struct cgit_repo), cmp_repos);
452 print_repolist(&cgit_repolist); 456 print_repolist(&cgit_repolist);
453 exit(0); 457 exit(0);
454 } 458 }
455} 459}
456 460
457static int calc_ttl() 461static int calc_ttl()
458{ 462{
459 if (!ctx.repo) 463 if (!ctx.repo)
460 return ctx.cfg.cache_root_ttl; 464 return ctx.cfg.cache_root_ttl;
461 465
462 if (!ctx.qry.page) 466 if (!ctx.qry.page)
463 return ctx.cfg.cache_repo_ttl; 467 return ctx.cfg.cache_repo_ttl;
464 468
465 if (ctx.qry.has_symref) 469 if (ctx.qry.has_symref)
466 return ctx.cfg.cache_dynamic_ttl; 470 return ctx.cfg.cache_dynamic_ttl;
467 471
468 if (ctx.qry.has_sha1) 472 if (ctx.qry.has_sha1)
469 return ctx.cfg.cache_static_ttl; 473 return ctx.cfg.cache_static_ttl;
470 474
471 return ctx.cfg.cache_repo_ttl; 475 return ctx.cfg.cache_repo_ttl;
472} 476}
473 477
474int main(int argc, const char **argv) 478int main(int argc, const char **argv)
475{ 479{
476 const char *cgit_config_env = getenv("CGIT_CONFIG"); 480 const char *cgit_config_env = getenv("CGIT_CONFIG");
477 const char *method = getenv("REQUEST_METHOD"); 481 const char *method = getenv("REQUEST_METHOD");
478 const char *path; 482 const char *path;
479 char *qry; 483 char *qry;
480 int err, ttl; 484 int err, ttl;
481 485
482 prepare_context(&ctx); 486 prepare_context(&ctx);
483 cgit_repolist.length = 0; 487 cgit_repolist.length = 0;
484 cgit_repolist.count = 0; 488 cgit_repolist.count = 0;
485 cgit_repolist.repos = NULL; 489 cgit_repolist.repos = NULL;
486 490
487 if (getenv("SCRIPT_NAME")) 491 if (getenv("SCRIPT_NAME"))
488 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME")); 492 ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME"));
489 if (getenv("QUERY_STRING")) 493 if (getenv("QUERY_STRING"))
490 ctx.qry.raw = xstrdup(getenv("QUERY_STRING")); 494 ctx.qry.raw = xstrdup(getenv("QUERY_STRING"));
491 cgit_parse_args(argc, argv); 495 cgit_parse_args(argc, argv);
492 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG, 496 parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG,
493 config_cb); 497 config_cb);
494 ctx.repo = NULL; 498 ctx.repo = NULL;
495 http_parse_querystring(ctx.qry.raw, querystring_cb); 499 http_parse_querystring(ctx.qry.raw, querystring_cb);
496 500
497 /* If virtual-root isn't specified in cgitrc, lets pretend 501 /* If virtual-root isn't specified in cgitrc, lets pretend
498 * that virtual-root equals SCRIPT_NAME. 502 * that virtual-root equals SCRIPT_NAME.
499 */ 503 */
500 if (!ctx.cfg.virtual_root) 504 if (!ctx.cfg.virtual_root)
501 ctx.cfg.virtual_root = ctx.cfg.script_name; 505 ctx.cfg.virtual_root = ctx.cfg.script_name;
502 506
503 /* If no url parameter is specified on the querystring, lets 507 /* If no url parameter is specified on the querystring, lets
504 * use PATH_INFO as url. This allows cgit to work with virtual 508 * use PATH_INFO as url. This allows cgit to work with virtual
505 * urls without the need for rewriterules in the webserver (as 509 * urls without the need for rewriterules in the webserver (as
506 * long as PATH_INFO is included in the cache lookup key). 510 * long as PATH_INFO is included in the cache lookup key).
507 */ 511 */
508 path = getenv("PATH_INFO"); 512 path = getenv("PATH_INFO");
509 if (!ctx.qry.url && path) { 513 if (!ctx.qry.url && path) {
510 if (path[0] == '/') 514 if (path[0] == '/')
511 path++; 515 path++;
512 ctx.qry.url = xstrdup(path); 516 ctx.qry.url = xstrdup(path);
513 if (ctx.qry.raw) { 517 if (ctx.qry.raw) {
514 qry = ctx.qry.raw; 518 qry = ctx.qry.raw;
515 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 519 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
516 free(qry); 520 free(qry);
517 } else 521 } else
518 ctx.qry.raw = ctx.qry.url; 522 ctx.qry.raw = ctx.qry.url;
519 cgit_parse_url(ctx.qry.url); 523 cgit_parse_url(ctx.qry.url);
520 } 524 }
521 525
522 ttl = calc_ttl(); 526 ttl = calc_ttl();
523 ctx.page.expires += ttl*60; 527 ctx.page.expires += ttl*60;
524 if (method && !strcmp(method, "HEAD")) 528 if (method && !strcmp(method, "HEAD"))
525 ctx.cfg.nocache = 1; 529 ctx.cfg.nocache = 1;
526 if (ctx.cfg.nocache) 530 if (ctx.cfg.nocache)
527 ctx.cfg.cache_size = 0; 531 ctx.cfg.cache_size = 0;
528 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 532 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
529 ctx.qry.raw, ttl, process_request, &ctx); 533 ctx.qry.raw, ttl, process_request, &ctx);
530 if (err) 534 if (err)
531 cgit_print_error(fmt("Error processing page: %s (%d)", 535 cgit_print_error(fmt("Error processing page: %s (%d)",
532 strerror(err), err)); 536 strerror(err), err));
533 return err; 537 return err;
534} 538}
diff --git a/cgit.h b/cgit.h
index b8557ac..4c854ea 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,271 +1,273 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22 22
23 23
24/* 24/*
25 * Dateformats used on misc. pages 25 * Dateformats used on misc. pages
26 */ 26 */
27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
28#define FMT_SHORTDATE "%Y-%m-%d" 28#define FMT_SHORTDATE "%Y-%m-%d"
29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
30 30
31 31
32/* 32/*
33 * Limits used for relative dates 33 * Limits used for relative dates
34 */ 34 */
35#define TM_MIN 60 35#define TM_MIN 60
36#define TM_HOUR (TM_MIN * 60) 36#define TM_HOUR (TM_MIN * 60)
37#define TM_DAY (TM_HOUR * 24) 37#define TM_DAY (TM_HOUR * 24)
38#define TM_WEEK (TM_DAY * 7) 38#define TM_WEEK (TM_DAY * 7)
39#define TM_YEAR (TM_DAY * 365) 39#define TM_YEAR (TM_DAY * 365)
40#define TM_MONTH (TM_YEAR / 12.0) 40#define TM_MONTH (TM_YEAR / 12.0)
41 41
42 42
43/* 43/*
44 * Default encoding 44 * Default encoding
45 */ 45 */
46#define PAGE_ENCODING "UTF-8" 46#define PAGE_ENCODING "UTF-8"
47 47
48typedef void (*configfn)(const char *name, const char *value); 48typedef void (*configfn)(const char *name, const char *value);
49typedef void (*filepair_fn)(struct diff_filepair *pair); 49typedef void (*filepair_fn)(struct diff_filepair *pair);
50typedef void (*linediff_fn)(char *line, int len); 50typedef void (*linediff_fn)(char *line, int len);
51 51
52struct cgit_filter { 52struct cgit_filter {
53 char *cmd; 53 char *cmd;
54 char **argv; 54 char **argv;
55 int old_stdout; 55 int old_stdout;
56 int pipe_fh[2]; 56 int pipe_fh[2];
57 int pid; 57 int pid;
58 int exitstatus; 58 int exitstatus;
59}; 59};
60 60
61struct cgit_repo { 61struct cgit_repo {
62 char *url; 62 char *url;
63 char *name; 63 char *name;
64 char *path; 64 char *path;
65 char *desc; 65 char *desc;
66 char *owner; 66 char *owner;
67 char *defbranch; 67 char *defbranch;
68 char *group; 68 char *group;
69 char *module_link; 69 char *module_link;
70 char *readme; 70 char *readme;
71 char *clone_url; 71 char *clone_url;
72 int snapshots; 72 int snapshots;
73 int enable_log_filecount; 73 int enable_log_filecount;
74 int enable_log_linecount; 74 int enable_log_linecount;
75 int max_stats; 75 int max_stats;
76 time_t mtime; 76 time_t mtime;
77 struct cgit_filter *about_filter;
77 struct cgit_filter *commit_filter; 78 struct cgit_filter *commit_filter;
78 struct cgit_filter *source_filter; 79 struct cgit_filter *source_filter;
79}; 80};
80 81
81struct cgit_repolist { 82struct cgit_repolist {
82 int length; 83 int length;
83 int count; 84 int count;
84 struct cgit_repo *repos; 85 struct cgit_repo *repos;
85}; 86};
86 87
87struct commitinfo { 88struct commitinfo {
88 struct commit *commit; 89 struct commit *commit;
89 char *author; 90 char *author;
90 char *author_email; 91 char *author_email;
91 unsigned long author_date; 92 unsigned long author_date;
92 char *committer; 93 char *committer;
93 char *committer_email; 94 char *committer_email;
94 unsigned long committer_date; 95 unsigned long committer_date;
95 char *subject; 96 char *subject;
96 char *msg; 97 char *msg;
97 char *msg_encoding; 98 char *msg_encoding;
98}; 99};
99 100
100struct taginfo { 101struct taginfo {
101 char *tagger; 102 char *tagger;
102 char *tagger_email; 103 char *tagger_email;
103 unsigned long tagger_date; 104 unsigned long tagger_date;
104 char *msg; 105 char *msg;
105}; 106};
106 107
107struct refinfo { 108struct refinfo {
108 const char *refname; 109 const char *refname;
109 struct object *object; 110 struct object *object;
110 union { 111 union {
111 struct taginfo *tag; 112 struct taginfo *tag;
112 struct commitinfo *commit; 113 struct commitinfo *commit;
113 }; 114 };
114}; 115};
115 116
116struct reflist { 117struct reflist {
117 struct refinfo **refs; 118 struct refinfo **refs;
118 int alloc; 119 int alloc;
119 int count; 120 int count;
120}; 121};
121 122
122struct cgit_query { 123struct cgit_query {
123 int has_symref; 124 int has_symref;
124 int has_sha1; 125 int has_sha1;
125 char *raw; 126 char *raw;
126 char *repo; 127 char *repo;
127 char *page; 128 char *page;
128 char *search; 129 char *search;
129 char *grep; 130 char *grep;
130 char *head; 131 char *head;
131 char *sha1; 132 char *sha1;
132 char *sha2; 133 char *sha2;
133 char *path; 134 char *path;
134 char *name; 135 char *name;
135 char *mimetype; 136 char *mimetype;
136 char *url; 137 char *url;
137 char *period; 138 char *period;
138 int ofs; 139 int ofs;
139 int nohead; 140 int nohead;
140 char *sort; 141 char *sort;
141 int showmsg; 142 int showmsg;
142}; 143};
143 144
144struct cgit_config { 145struct cgit_config {
145 char *agefile; 146 char *agefile;
146 char *cache_root; 147 char *cache_root;
147 char *clone_prefix; 148 char *clone_prefix;
148 char *css; 149 char *css;
149 char *favicon; 150 char *favicon;
150 char *footer; 151 char *footer;
151 char *head_include; 152 char *head_include;
152 char *header; 153 char *header;
153 char *index_header; 154 char *index_header;
154 char *index_info; 155 char *index_info;
155 char *logo; 156 char *logo;
156 char *logo_link; 157 char *logo_link;
157 char *module_link; 158 char *module_link;
158 char *repo_group; 159 char *repo_group;
159 char *robots; 160 char *robots;
160 char *root_title; 161 char *root_title;
161 char *root_desc; 162 char *root_desc;
162 char *root_readme; 163 char *root_readme;
163 char *script_name; 164 char *script_name;
164 char *virtual_root; 165 char *virtual_root;
165 int cache_size; 166 int cache_size;
166 int cache_dynamic_ttl; 167 int cache_dynamic_ttl;
167 int cache_max_create_time; 168 int cache_max_create_time;
168 int cache_repo_ttl; 169 int cache_repo_ttl;
169 int cache_root_ttl; 170 int cache_root_ttl;
170 int cache_static_ttl; 171 int cache_static_ttl;
171 int embedded; 172 int embedded;
172 int enable_index_links; 173 int enable_index_links;
173 int enable_log_filecount; 174 int enable_log_filecount;
174 int enable_log_linecount; 175 int enable_log_linecount;
175 int local_time; 176 int local_time;
176 int max_repo_count; 177 int max_repo_count;
177 int max_commit_count; 178 int max_commit_count;
178 int max_lock_attempts; 179 int max_lock_attempts;
179 int max_msg_len; 180 int max_msg_len;
180 int max_repodesc_len; 181 int max_repodesc_len;
181 int max_stats; 182 int max_stats;
182 int nocache; 183 int nocache;
183 int noplainemail; 184 int noplainemail;
184 int noheader; 185 int noheader;
185 int renamelimit; 186 int renamelimit;
186 int snapshots; 187 int snapshots;
187 int summary_branches; 188 int summary_branches;
188 int summary_log; 189 int summary_log;
189 int summary_tags; 190 int summary_tags;
190 struct string_list mimetypes; 191 struct string_list mimetypes;
192 struct cgit_filter *about_filter;
191 struct cgit_filter *commit_filter; 193 struct cgit_filter *commit_filter;
192 struct cgit_filter *source_filter; 194 struct cgit_filter *source_filter;
193}; 195};
194 196
195struct cgit_page { 197struct cgit_page {
196 time_t modified; 198 time_t modified;
197 time_t expires; 199 time_t expires;
198 size_t size; 200 size_t size;
199 char *mimetype; 201 char *mimetype;
200 char *charset; 202 char *charset;
201 char *filename; 203 char *filename;
202 char *etag; 204 char *etag;
203 char *title; 205 char *title;
204 int status; 206 int status;
205 char *statusmsg; 207 char *statusmsg;
206}; 208};
207 209
208struct cgit_context { 210struct cgit_context {
209 struct cgit_query qry; 211 struct cgit_query qry;
210 struct cgit_config cfg; 212 struct cgit_config cfg;
211 struct cgit_repo *repo; 213 struct cgit_repo *repo;
212 struct cgit_page page; 214 struct cgit_page page;
213}; 215};
214 216
215struct cgit_snapshot_format { 217struct cgit_snapshot_format {
216 const char *suffix; 218 const char *suffix;
217 const char *mimetype; 219 const char *mimetype;
218 write_archive_fn_t write_func; 220 write_archive_fn_t write_func;
219 int bit; 221 int bit;
220}; 222};
221 223
222extern const char *cgit_version; 224extern const char *cgit_version;
223 225
224extern struct cgit_repolist cgit_repolist; 226extern struct cgit_repolist cgit_repolist;
225extern struct cgit_context ctx; 227extern struct cgit_context ctx;
226extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 228extern const struct cgit_snapshot_format cgit_snapshot_formats[];
227 229
228extern struct cgit_repo *cgit_add_repo(const char *url); 230extern struct cgit_repo *cgit_add_repo(const char *url);
229extern struct cgit_repo *cgit_get_repoinfo(const char *url); 231extern struct cgit_repo *cgit_get_repoinfo(const char *url);
230extern void cgit_repo_config_cb(const char *name, const char *value); 232extern void cgit_repo_config_cb(const char *name, const char *value);
231 233
232extern int chk_zero(int result, char *msg); 234extern int chk_zero(int result, char *msg);
233extern int chk_positive(int result, char *msg); 235extern int chk_positive(int result, char *msg);
234extern int chk_non_negative(int result, char *msg); 236extern int chk_non_negative(int result, char *msg);
235 237
236extern char *trim_end(const char *str, char c); 238extern char *trim_end(const char *str, char c);
237extern char *strlpart(char *txt, int maxlen); 239extern char *strlpart(char *txt, int maxlen);
238extern char *strrpart(char *txt, int maxlen); 240extern char *strrpart(char *txt, int maxlen);
239 241
240extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 242extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
241extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 243extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
242 int flags, void *cb_data); 244 int flags, void *cb_data);
243 245
244extern void *cgit_free_commitinfo(struct commitinfo *info); 246extern void *cgit_free_commitinfo(struct commitinfo *info);
245 247
246extern int cgit_diff_files(const unsigned char *old_sha1, 248extern int cgit_diff_files(const unsigned char *old_sha1,
247 const unsigned char *new_sha1, 249 const unsigned char *new_sha1,
248 unsigned long *old_size, unsigned long *new_size, 250 unsigned long *old_size, unsigned long *new_size,
249 int *binary, linediff_fn fn); 251 int *binary, linediff_fn fn);
250 252
251extern void cgit_diff_tree(const unsigned char *old_sha1, 253extern void cgit_diff_tree(const unsigned char *old_sha1,
252 const unsigned char *new_sha1, 254 const unsigned char *new_sha1,
253 filepair_fn fn, const char *prefix); 255 filepair_fn fn, const char *prefix);
254 256
255extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 257extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
256 258
257extern char *fmt(const char *format,...); 259extern char *fmt(const char *format,...);
258 260
259extern struct commitinfo *cgit_parse_commit(struct commit *commit); 261extern struct commitinfo *cgit_parse_commit(struct commit *commit);
260extern struct taginfo *cgit_parse_tag(struct tag *tag); 262extern struct taginfo *cgit_parse_tag(struct tag *tag);
261extern void cgit_parse_url(const char *url); 263extern void cgit_parse_url(const char *url);
262 264
263extern const char *cgit_repobasename(const char *reponame); 265extern const char *cgit_repobasename(const char *reponame);
264 266
265extern int cgit_parse_snapshots_mask(const char *str); 267extern int cgit_parse_snapshots_mask(const char *str);
266 268
267extern int cgit_open_filter(struct cgit_filter *filter); 269extern int cgit_open_filter(struct cgit_filter *filter);
268extern int cgit_close_filter(struct cgit_filter *filter); 270extern int cgit_close_filter(struct cgit_filter *filter);
269 271
270 272
271#endif /* CGIT_H */ 273#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index dc63637..4d656fe 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,444 +1,454 @@
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---------------
19about-filter::
20 Specifies a command which will be invoked to format the content of
21 about pages (both top-level and for each repository). The command will
22 get the content of the about-file on its STDIN, and the STDOUT from the
23 command will be included verbatim on the about page. Default value:
24 none.
25
19agefile:: 26agefile::
20 Specifies a path, relative to each repository path, which can be used 27 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. 28 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" 29 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 30 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
24 hh:mm:ss". Default value: "info/web/last-modified". 31 hh:mm:ss". Default value: "info/web/last-modified".
25 32
26cache-root:: 33cache-root::
27 Path used to store the cgit cache entries. Default value: 34 Path used to store the cgit cache entries. Default value:
28 "/var/cache/cgit". 35 "/var/cache/cgit".
29 36
30cache-dynamic-ttl:: 37cache-dynamic-ttl::
31 Number which specifies the time-to-live, in minutes, for the cached 38 Number which specifies the time-to-live, in minutes, for the cached
32 version of repository pages accessed without a fixed SHA1. Default 39 version of repository pages accessed without a fixed SHA1. Default
33 value: "5". 40 value: "5".
34 41
35cache-repo-ttl:: 42cache-repo-ttl::
36 Number which specifies the time-to-live, in minutes, for the cached 43 Number which specifies the time-to-live, in minutes, for the cached
37 version of the repository summary page. Default value: "5". 44 version of the repository summary page. Default value: "5".
38 45
39cache-root-ttl:: 46cache-root-ttl::
40 Number which specifies the time-to-live, in minutes, for the cached 47 Number which specifies the time-to-live, in minutes, for the cached
41 version of the repository index page. Default value: "5". 48 version of the repository index page. Default value: "5".
42 49
43cache-size:: 50cache-size::
44 The maximum number of entries in the cgit cache. Default value: "0" 51 The maximum number of entries in the cgit cache. Default value: "0"
45 (i.e. caching is disabled). 52 (i.e. caching is disabled).
46 53
47cache-static-ttl:: 54cache-static-ttl::
48 Number which specifies the time-to-live, in minutes, for the cached 55 Number which specifies the time-to-live, in minutes, for the cached
49 version of repository pages accessed with a fixed SHA1. Default value: 56 version of repository pages accessed with a fixed SHA1. Default value:
50 "5". 57 "5".
51 58
52clone-prefix:: 59clone-prefix::
53 Space-separated list of common prefixes which, when combined with a 60 Space-separated list of common prefixes which, when combined with a
54 repository url, generates valid clone urls for the repository. This 61 repository url, generates valid clone urls for the repository. This
55 setting is only used if `repo.clone-url` is unspecified. Default value: 62 setting is only used if `repo.clone-url` is unspecified. Default value:
56 none. 63 none.
57 64
58commit-filter:: 65commit-filter::
59 Specifies a command which will be invoked to format commit messages. 66 Specifies a command which will be invoked to format commit messages.
60 The command will get the message on its STDIN, and the STDOUT from the 67 The command will get the message on its STDIN, and the STDOUT from the
61 command will be included verbatim as the commit message, i.e. this can 68 command will be included verbatim as the commit message, i.e. this can
62 be used to implement bugtracker integration. Default value: none. 69 be used to implement bugtracker integration. Default value: none.
63 70
64css:: 71css::
65 Url which specifies the css document to include in all cgit pages. 72 Url which specifies the css document to include in all cgit pages.
66 Default value: "/cgit.css". 73 Default value: "/cgit.css".
67 74
68embedded:: 75embedded::
69 Flag which, when set to "1", will make cgit generate a html fragment 76 Flag which, when set to "1", will make cgit generate a html fragment
70 suitable for embedding in other html pages. Default value: none. See 77 suitable for embedding in other html pages. Default value: none. See
71 also: "noheader". 78 also: "noheader".
72 79
73enable-index-links:: 80enable-index-links::
74 Flag which, when set to "1", will make cgit generate extra links for 81 Flag which, when set to "1", will make cgit generate extra links for
75 each repo in the repository index (specifically, to the "summary", 82 each repo in the repository index (specifically, to the "summary",
76 "commit" and "tree" pages). Default value: "0". 83 "commit" and "tree" pages). Default value: "0".
77 84
78enable-log-filecount:: 85enable-log-filecount::
79 Flag which, when set to "1", will make cgit print the number of 86 Flag which, when set to "1", will make cgit print the number of
80 modified files for each commit on the repository log page. Default 87 modified files for each commit on the repository log page. Default
81 value: "0". 88 value: "0".
82 89
83enable-log-linecount:: 90enable-log-linecount::
84 Flag which, when set to "1", will make cgit print the number of added 91 Flag which, when set to "1", will make cgit print the number of added
85 and removed lines for each commit on the repository log page. Default 92 and removed lines for each commit on the repository log page. Default
86 value: "0". 93 value: "0".
87 94
88favicon:: 95favicon::
89 Url used as link to a shortcut icon for cgit. If specified, it is 96 Url used as link to a shortcut icon for cgit. If specified, it is
90 suggested to use the value "/favicon.ico" since certain browsers will 97 suggested to use the value "/favicon.ico" since certain browsers will
91 ignore other values. Default value: none. 98 ignore other values. Default value: none.
92 99
93footer:: 100footer::
94 The content of the file specified with this option will be included 101 The content of the file specified with this option will be included
95 verbatim at the bottom of all pages (i.e. it replaces the standard 102 verbatim at the bottom of all pages (i.e. it replaces the standard
96 "generated by..." message. Default value: none. 103 "generated by..." message. Default value: none.
97 104
98head-include:: 105head-include::
99 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
100 verbatim in the html HEAD section on all pages. Default value: none. 107 verbatim in the html HEAD section on all pages. Default value: none.
101 108
102header:: 109header::
103 The content of the file specified with this option will be included 110 The content of the file specified with this option will be included
104 verbatim at the top of all pages. Default value: none. 111 verbatim at the top of all pages. Default value: none.
105 112
106include:: 113include::
107 Name of a configfile to include before the rest of the current config- 114 Name of a configfile to include before the rest of the current config-
108 file is parsed. Default value: none. 115 file is parsed. Default value: none.
109 116
110index-header:: 117index-header::
111 The content of the file specified with this option will be included 118 The content of the file specified with this option will be included
112 verbatim above the repository index. This setting is deprecated, and 119 verbatim above the repository index. This setting is deprecated, and
113 will not be supported by cgit-1.0 (use root-readme instead). Default 120 will not be supported by cgit-1.0 (use root-readme instead). Default
114 value: none. 121 value: none.
115 122
116index-info:: 123index-info::
117 The content of the file specified with this option will be included 124 The content of the file specified with this option will be included
118 verbatim below the heading on the repository index page. This setting 125 verbatim below the heading on the repository index page. This setting
119 is deprecated, and will not be supported by cgit-1.0 (use root-desc 126 is deprecated, and will not be supported by cgit-1.0 (use root-desc
120 instead). Default value: none. 127 instead). Default value: none.
121 128
122local-time:: 129local-time::
123 Flag which, if set to "1", makes cgit print commit and tag times in the 130 Flag which, if set to "1", makes cgit print commit and tag times in the
124 servers timezone. Default value: "0". 131 servers timezone. Default value: "0".
125 132
126logo:: 133logo::
127 Url which specifies the source of an image which will be used as a logo 134 Url which specifies the source of an image which will be used as a logo
128 on all cgit pages. 135 on all cgit pages.
129 136
130logo-link:: 137logo-link::
131 Url loaded when clicking on the cgit logo image. If unspecified the 138 Url loaded when clicking on the cgit logo image. If unspecified the
132 calculated url of the repository index page will be used. Default 139 calculated url of the repository index page will be used. Default
133 value: none. 140 value: none.
134 141
135max-commit-count:: 142max-commit-count::
136 Specifies the number of entries to list per page in "log" view. Default 143 Specifies the number of entries to list per page in "log" view. Default
137 value: "50". 144 value: "50".
138 145
139max-message-length:: 146max-message-length::
140 Specifies the maximum number of commit message characters to display in 147 Specifies the maximum number of commit message characters to display in
141 "log" view. Default value: "80". 148 "log" view. Default value: "80".
142 149
143max-repo-count:: 150max-repo-count::
144 Specifies the number of entries to list per page on therepository 151 Specifies the number of entries to list per page on therepository
145 index page. Default value: "50". 152 index page. Default value: "50".
146 153
147max-repodesc-length:: 154max-repodesc-length::
148 Specifies the maximum number of repo description characters to display 155 Specifies the maximum number of repo description characters to display
149 on the repository index page. Default value: "80". 156 on the repository index page. Default value: "80".
150 157
151max-stats:: 158max-stats::
152 Set the default maximum statistics period. Valid values are "week", 159 Set the default maximum statistics period. Valid values are "week",
153 "month", "quarter" and "year". If unspecified, statistics are 160 "month", "quarter" and "year". If unspecified, statistics are
154 disabled. Default value: none. See also: "repo.max-stats". 161 disabled. Default value: none. See also: "repo.max-stats".
155 162
156mimetype.<ext>:: 163mimetype.<ext>::
157 Set the mimetype for the specified filename extension. This is used 164 Set the mimetype for the specified filename extension. This is used
158 by the `plain` command when returning blob content. 165 by the `plain` command when returning blob content.
159 166
160module-link:: 167module-link::
161 Text which will be used as the formatstring for a hyperlink when a 168 Text which will be used as the formatstring for a hyperlink when a
162 submodule is printed in a directory listing. The arguments for the 169 submodule is printed in a directory listing. The arguments for the
163 formatstring are the path and SHA1 of the submodule commit. Default 170 formatstring are the path and SHA1 of the submodule commit. Default
164 value: "./?repo=%s&page=commit&id=%s" 171 value: "./?repo=%s&page=commit&id=%s"
165 172
166nocache:: 173nocache::
167 If set to the value "1" caching will be disabled. This settings is 174 If set to the value "1" caching will be disabled. This settings is
168 deprecated, and will not be honored starting with cgit-1.0. Default 175 deprecated, and will not be honored starting with cgit-1.0. Default
169 value: "0". 176 value: "0".
170 177
171noplainemail:: 178noplainemail::
172 If set to "1" showing full author email adresses will be disabled. 179 If set to "1" showing full author email adresses will be disabled.
173 Default value: "0". 180 Default value: "0".
174 181
175noheader:: 182noheader::
176 Flag which, when set to "1", will make cgit omit the standard header 183 Flag which, when set to "1", will make cgit omit the standard header
177 on all pages. Default value: none. See also: "embedded". 184 on all pages. Default value: none. See also: "embedded".
178 185
179renamelimit:: 186renamelimit::
180 Maximum number of files to consider when detecting renames. The value 187 Maximum number of files to consider when detecting renames. The value
181 "-1" uses the compiletime value in git (for further info, look at 188 "-1" uses the compiletime value in git (for further info, look at
182 `man git-diff`). Default value: "-1". 189 `man git-diff`). Default value: "-1".
183 190
184repo.group:: 191repo.group::
185 A value for the current repository group, which all repositories 192 A value for the current repository group, which all repositories
186 specified after this setting will inherit. Default value: none. 193 specified after this setting will inherit. Default value: none.
187 194
188robots:: 195robots::
189 Text used as content for the "robots" meta-tag. Default value: 196 Text used as content for the "robots" meta-tag. Default value:
190 "index, nofollow". 197 "index, nofollow".
191 198
192root-desc:: 199root-desc::
193 Text printed below the heading on the repository index page. Default 200 Text printed below the heading on the repository index page. Default
194 value: "a fast webinterface for the git dscm". 201 value: "a fast webinterface for the git dscm".
195 202
196root-readme:: 203root-readme::
197 The content of the file specified with this option will be included 204 The content of the file specified with this option will be included
198 verbatim below the "about" link on the repository index page. Default 205 verbatim below the "about" link on the repository index page. Default
199 value: none. 206 value: none.
200 207
201root-title:: 208root-title::
202 Text printed as heading on the repository index page. Default value: 209 Text printed as heading on the repository index page. Default value:
203 "Git Repository Browser". 210 "Git Repository Browser".
204 211
205snapshots:: 212snapshots::
206 Text which specifies the default (and allowed) set of snapshot formats 213 Text which specifies the default (and allowed) set of snapshot formats
207 supported by cgit. The value is a space-separated list of zero or more 214 supported by cgit. The value is a space-separated list of zero or more
208 of the following values: 215 of the following values:
209 "tar" uncompressed tar-file 216 "tar" uncompressed tar-file
210 "tar.gz"gzip-compressed tar-file 217 "tar.gz"gzip-compressed tar-file
211 "tar.bz2"bzip-compressed tar-file 218 "tar.bz2"bzip-compressed tar-file
212 "zip" zip-file 219 "zip" zip-file
213 Default value: none. 220 Default value: none.
214 221
215source-filter:: 222source-filter::
216 Specifies a command which will be invoked to format plaintext blobs 223 Specifies a command which will be invoked to format plaintext blobs
217 in the tree view. The command will get the blob content on its STDIN 224 in the tree view. The command will get the blob content on its STDIN
218 and the name of the blob as its only command line argument. The STDOUT 225 and the name of the blob as its only command line argument. The STDOUT
219 from the command will be included verbatim as the blob contents, i.e. 226 from the command will be included verbatim as the blob contents, i.e.
220 this can be used to implement e.g. syntax highlighting. Default value: 227 this can be used to implement e.g. syntax highlighting. Default value:
221 none. 228 none.
222 229
223summary-branches:: 230summary-branches::
224 Specifies the number of branches to display in the repository "summary" 231 Specifies the number of branches to display in the repository "summary"
225 view. Default value: "10". 232 view. Default value: "10".
226 233
227summary-log:: 234summary-log::
228 Specifies the number of log entries to display in the repository 235 Specifies the number of log entries to display in the repository
229 "summary" view. Default value: "10". 236 "summary" view. Default value: "10".
230 237
231summary-tags:: 238summary-tags::
232 Specifies the number of tags to display in the repository "summary" 239 Specifies the number of tags to display in the repository "summary"
233 view. Default value: "10". 240 view. Default value: "10".
234 241
235virtual-root:: 242virtual-root::
236 Url which, if specified, will be used as root for all cgit links. It 243 Url which, if specified, will be used as root for all cgit links. It
237 will also cause cgit to generate 'virtual urls', i.e. urls like 244 will also cause cgit to generate 'virtual urls', i.e. urls like
238 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 245 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
239 value: none. 246 value: none.
240 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 247 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
241 same kind of virtual urls, so this option will probably be deprecated. 248 same kind of virtual urls, so this option will probably be deprecated.
242 249
243REPOSITORY SETTINGS 250REPOSITORY SETTINGS
244------------------- 251-------------------
252repo.about-filter::
253 Override the default about-filter. Default value: <about-filter>.
254
245repo.clone-url:: 255repo.clone-url::
246 A list of space-separated urls which can be used to clone this repo. 256 A list of space-separated urls which can be used to clone this repo.
247 Default value: none. 257 Default value: none.
248 258
249repo.commit-filter:: 259repo.commit-filter::
250 Override the default commit-filter. Default value: <commit-filter>. 260 Override the default commit-filter. Default value: <commit-filter>.
251 261
252repo.defbranch:: 262repo.defbranch::
253 The name of the default branch for this repository. If no such branch 263 The name of the default branch for this repository. If no such branch
254 exists in the repository, the first branch name (when sorted) is used 264 exists in the repository, the first branch name (when sorted) is used
255 as default instead. Default value: "master". 265 as default instead. Default value: "master".
256 266
257repo.desc:: 267repo.desc::
258 The value to show as repository description. Default value: none. 268 The value to show as repository description. Default value: none.
259 269
260repo.enable-log-filecount:: 270repo.enable-log-filecount::
261 A flag which can be used to disable the global setting 271 A flag which can be used to disable the global setting
262 `enable-log-filecount'. Default value: none. 272 `enable-log-filecount'. Default value: none.
263 273
264repo.enable-log-linecount:: 274repo.enable-log-linecount::
265 A flag which can be used to disable the global setting 275 A flag which can be used to disable the global setting
266 `enable-log-linecount'. Default value: none. 276 `enable-log-linecount'. Default value: none.
267 277
268repo.max-stats:: 278repo.max-stats::
269 Override the default maximum statistics period. Valid values are equal 279 Override the default maximum statistics period. Valid values are equal
270 to the values specified for the global "max-stats" setting. Default 280 to the values specified for the global "max-stats" setting. Default
271 value: none. 281 value: none.
272 282
273repo.name:: 283repo.name::
274 The value to show as repository name. Default value: <repo.url>. 284 The value to show as repository name. Default value: <repo.url>.
275 285
276repo.owner:: 286repo.owner::
277 A value used to identify the owner of the repository. Default value: 287 A value used to identify the owner of the repository. Default value:
278 none. 288 none.
279 289
280repo.path:: 290repo.path::
281 An absolute path to the repository directory. For non-bare repositories 291 An absolute path to the repository directory. For non-bare repositories
282 this is the .git-directory. Default value: none. 292 this is the .git-directory. Default value: none.
283 293
284repo.readme:: 294repo.readme::
285 A path (relative to <repo.path>) which specifies a file to include 295 A path (relative to <repo.path>) which specifies a file to include
286 verbatim as the "About" page for this repo. Default value: none. 296 verbatim as the "About" page for this repo. Default value: none.
287 297
288repo.snapshots:: 298repo.snapshots::
289 A mask of allowed snapshot-formats for this repo, restricted by the 299 A mask of allowed snapshot-formats for this repo, restricted by the
290 "snapshots" global setting. Default value: <snapshots>. 300 "snapshots" global setting. Default value: <snapshots>.
291 301
292repo.source-filter:: 302repo.source-filter::
293 Override the default source-filter. Default value: <source-filter>. 303 Override the default source-filter. Default value: <source-filter>.
294 304
295repo.url:: 305repo.url::
296 The relative url used to access the repository. This must be the first 306 The relative url used to access the repository. This must be the first
297 setting specified for each repo. Default value: none. 307 setting specified for each repo. Default value: none.
298 308
299 309
300EXAMPLE CGITRC FILE 310EXAMPLE CGITRC FILE
301------------------- 311-------------------
302 312
303.... 313....
304# Enable caching of up to 1000 output entriess 314# Enable caching of up to 1000 output entriess
305cache-size=1000 315cache-size=1000
306 316
307 317
308# Specify some default clone prefixes 318# Specify some default clone prefixes
309clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 319clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
310 320
311# Specify the css url 321# Specify the css url
312css=/css/cgit.css 322css=/css/cgit.css
313 323
314 324
315# Show extra links for each repository on the index page 325# Show extra links for each repository on the index page
316enable-index-links=1 326enable-index-links=1
317 327
318 328
319# Show number of affected files per commit on the log pages 329# Show number of affected files per commit on the log pages
320enable-log-filecount=1 330enable-log-filecount=1
321 331
322 332
323# Show number of added/removed lines per commit on the log pages 333# Show number of added/removed lines per commit on the log pages
324enable-log-linecount=1 334enable-log-linecount=1
325 335
326 336
327# Add a cgit favicon 337# Add a cgit favicon
328favicon=/favicon.ico 338favicon=/favicon.ico
329 339
330 340
331# Use a custom logo 341# Use a custom logo
332logo=/img/mylogo.png 342logo=/img/mylogo.png
333 343
334 344
335# Enable statistics per week, month and quarter 345# Enable statistics per week, month and quarter
336max-stats=quarter 346max-stats=quarter
337 347
338 348
339# Set the title and heading of the repository index page 349# Set the title and heading of the repository index page
340root-title=foobar.com git repositories 350root-title=foobar.com git repositories
341 351
342 352
343# Set a subheading for the repository index page 353# Set a subheading for the repository index page
344root-desc=tracking the foobar development 354root-desc=tracking the foobar development
345 355
346 356
347# Include some more info about foobar.com on the index page 357# Include some more info about foobar.com on the index page
348root-readme=/var/www/htdocs/about.html 358root-readme=/var/www/htdocs/about.html
349 359
350 360
351# Allow download of tar.gz, tar.bz2 and zip-files 361# Allow download of tar.gz, tar.bz2 and zip-files
352snapshots=tar.gz tar.bz2 zip 362snapshots=tar.gz tar.bz2 zip
353 363
354 364
355## 365##
356## List of common mimetypes 366## List of common mimetypes
357## 367##
358 368
359mimetype.git=image/git 369mimetype.git=image/git
360mimetype.html=text/html 370mimetype.html=text/html
361mimetype.jpg=image/jpeg 371mimetype.jpg=image/jpeg
362mimetype.jpeg=image/jpeg 372mimetype.jpeg=image/jpeg
363mimetype.pdf=application/pdf 373mimetype.pdf=application/pdf
364mimetype.png=image/png 374mimetype.png=image/png
365mimetype.svg=image/svg+xml 375mimetype.svg=image/svg+xml
366 376
367 377
368## 378##
369## List of repositories. 379## List of repositories.
370## PS: Any repositories listed when repo.group is unset will not be 380## PS: Any repositories listed when repo.group is unset will not be
371## displayed under a group heading 381## displayed under a group heading
372## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 382## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
373## and included like this: 383## and included like this:
374## include=/etc/cgitrepos 384## include=/etc/cgitrepos
375## 385##
376 386
377 387
378repo.url=foo 388repo.url=foo
379repo.path=/pub/git/foo.git 389repo.path=/pub/git/foo.git
380repo.desc=the master foo repository 390repo.desc=the master foo repository
381repo.owner=fooman@foobar.com 391repo.owner=fooman@foobar.com
382repo.readme=info/web/about.html 392repo.readme=info/web/about.html
383 393
384 394
385repo.url=bar 395repo.url=bar
386repo.path=/pub/git/bar.git 396repo.path=/pub/git/bar.git
387repo.desc=the bars for your foo 397repo.desc=the bars for your foo
388repo.owner=barman@foobar.com 398repo.owner=barman@foobar.com
389repo.readme=info/web/about.html 399repo.readme=info/web/about.html
390 400
391 401
392# The next repositories will be displayed under the 'extras' heading 402# The next repositories will be displayed under the 'extras' heading
393repo.group=extras 403repo.group=extras
394 404
395 405
396repo.url=baz 406repo.url=baz
397repo.path=/pub/git/baz.git 407repo.path=/pub/git/baz.git
398repo.desc=a set of extensions for bar users 408repo.desc=a set of extensions for bar users
399 409
400repo.url=wiz 410repo.url=wiz
401repo.path=/pub/git/wiz.git 411repo.path=/pub/git/wiz.git
402repo.desc=the wizard of foo 412repo.desc=the wizard of foo
403 413
404 414
405# Add some mirrored repositories 415# Add some mirrored repositories
406repo.group=mirrors 416repo.group=mirrors
407 417
408 418
409repo.url=git 419repo.url=git
410repo.path=/pub/git/git.git 420repo.path=/pub/git/git.git
411repo.desc=the dscm 421repo.desc=the dscm
412 422
413 423
414repo.url=linux 424repo.url=linux
415repo.path=/pub/git/linux.git 425repo.path=/pub/git/linux.git
416repo.desc=the kernel 426repo.desc=the kernel
417 427
418# Disable adhoc downloads of this repo 428# Disable adhoc downloads of this repo
419repo.snapshots=0 429repo.snapshots=0
420 430
421# Disable line-counts for this repo 431# Disable line-counts for this repo
422repo.enable-log-linecount=0 432repo.enable-log-linecount=0
423 433
424# Restrict the max statistics period for this repo 434# Restrict the max statistics period for this repo
425repo.max-stats=month 435repo.max-stats=month
426.... 436....
427 437
428 438
429BUGS 439BUGS
430---- 440----
431Comments currently cannot appear on the same line as a setting; the comment 441Comments currently cannot appear on the same line as a setting; the comment
432will be included as part of the value. E.g. this line: 442will be included as part of the value. E.g. this line:
433 443
434 robots=index # allow indexing 444 robots=index # allow indexing
435 445
436will generate the following html element: 446will generate the following html element:
437 447
438 <meta name='robots' content='index # allow indexing'/> 448 <meta name='robots' content='index # allow indexing'/>
439 449
440 450
441 451
442AUTHOR 452AUTHOR
443------ 453------
444Lars Hjemli <hjemli@gmail.com> 454Lars Hjemli <hjemli@gmail.com>
diff --git a/cmd.c b/cmd.c
index cf97da7..766f903 100644
--- a/cmd.c
+++ b/cmd.c
@@ -1,171 +1,171 @@
1/* cmd.c: the cgit command dispatcher 1/* cmd.c: the cgit command dispatcher
2 * 2 *
3 * Copyright (C) 2008 Lars Hjemli 3 * Copyright (C) 2008 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 "cache.h" 11#include "cache.h"
12#include "ui-shared.h" 12#include "ui-shared.h"
13#include "ui-atom.h" 13#include "ui-atom.h"
14#include "ui-blob.h" 14#include "ui-blob.h"
15#include "ui-clone.h" 15#include "ui-clone.h"
16#include "ui-commit.h" 16#include "ui-commit.h"
17#include "ui-diff.h" 17#include "ui-diff.h"
18#include "ui-log.h" 18#include "ui-log.h"
19#include "ui-patch.h" 19#include "ui-patch.h"
20#include "ui-plain.h" 20#include "ui-plain.h"
21#include "ui-refs.h" 21#include "ui-refs.h"
22#include "ui-repolist.h" 22#include "ui-repolist.h"
23#include "ui-snapshot.h" 23#include "ui-snapshot.h"
24#include "ui-stats.h" 24#include "ui-stats.h"
25#include "ui-summary.h" 25#include "ui-summary.h"
26#include "ui-tag.h" 26#include "ui-tag.h"
27#include "ui-tree.h" 27#include "ui-tree.h"
28 28
29static void HEAD_fn(struct cgit_context *ctx) 29static void HEAD_fn(struct cgit_context *ctx)
30{ 30{
31 cgit_clone_head(ctx); 31 cgit_clone_head(ctx);
32} 32}
33 33
34static void atom_fn(struct cgit_context *ctx) 34static void atom_fn(struct cgit_context *ctx)
35{ 35{
36 cgit_print_atom(ctx->qry.head, ctx->qry.path, 10); 36 cgit_print_atom(ctx->qry.head, ctx->qry.path, 10);
37} 37}
38 38
39static void about_fn(struct cgit_context *ctx) 39static void about_fn(struct cgit_context *ctx)
40{ 40{
41 if (ctx->repo) 41 if (ctx->repo)
42 cgit_print_repo_readme(); 42 cgit_print_repo_readme(ctx->qry.path);
43 else 43 else
44 cgit_print_site_readme(); 44 cgit_print_site_readme();
45} 45}
46 46
47static void blob_fn(struct cgit_context *ctx) 47static void blob_fn(struct cgit_context *ctx)
48{ 48{
49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); 49 cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head);
50} 50}
51 51
52static void commit_fn(struct cgit_context *ctx) 52static void commit_fn(struct cgit_context *ctx)
53{ 53{
54 cgit_print_commit(ctx->qry.sha1); 54 cgit_print_commit(ctx->qry.sha1);
55} 55}
56 56
57static void diff_fn(struct cgit_context *ctx) 57static void diff_fn(struct cgit_context *ctx)
58{ 58{
59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); 59 cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path);
60} 60}
61 61
62static void info_fn(struct cgit_context *ctx) 62static void info_fn(struct cgit_context *ctx)
63{ 63{
64 cgit_clone_info(ctx); 64 cgit_clone_info(ctx);
65} 65}
66 66
67static void log_fn(struct cgit_context *ctx) 67static void log_fn(struct cgit_context *ctx)
68{ 68{
69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, 69 cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count,
70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); 70 ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1);
71} 71}
72 72
73static void ls_cache_fn(struct cgit_context *ctx) 73static void ls_cache_fn(struct cgit_context *ctx)
74{ 74{
75 ctx->page.mimetype = "text/plain"; 75 ctx->page.mimetype = "text/plain";
76 ctx->page.filename = "ls-cache.txt"; 76 ctx->page.filename = "ls-cache.txt";
77 cgit_print_http_headers(ctx); 77 cgit_print_http_headers(ctx);
78 cache_ls(ctx->cfg.cache_root); 78 cache_ls(ctx->cfg.cache_root);
79} 79}
80 80
81static void objects_fn(struct cgit_context *ctx) 81static void objects_fn(struct cgit_context *ctx)
82{ 82{
83 cgit_clone_objects(ctx); 83 cgit_clone_objects(ctx);
84} 84}
85 85
86static void repolist_fn(struct cgit_context *ctx) 86static void repolist_fn(struct cgit_context *ctx)
87{ 87{
88 cgit_print_repolist(); 88 cgit_print_repolist();
89} 89}
90 90
91static void patch_fn(struct cgit_context *ctx) 91static void patch_fn(struct cgit_context *ctx)
92{ 92{
93 cgit_print_patch(ctx->qry.sha1); 93 cgit_print_patch(ctx->qry.sha1);
94} 94}
95 95
96static void plain_fn(struct cgit_context *ctx) 96static void plain_fn(struct cgit_context *ctx)
97{ 97{
98 cgit_print_plain(ctx); 98 cgit_print_plain(ctx);
99} 99}
100 100
101static void refs_fn(struct cgit_context *ctx) 101static void refs_fn(struct cgit_context *ctx)
102{ 102{
103 cgit_print_refs(); 103 cgit_print_refs();
104} 104}
105 105
106static void snapshot_fn(struct cgit_context *ctx) 106static void snapshot_fn(struct cgit_context *ctx)
107{ 107{
108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path, 108 cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path,
109 ctx->repo->snapshots, ctx->qry.nohead); 109 ctx->repo->snapshots, ctx->qry.nohead);
110} 110}
111 111
112static void stats_fn(struct cgit_context *ctx) 112static void stats_fn(struct cgit_context *ctx)
113{ 113{
114 cgit_show_stats(ctx); 114 cgit_show_stats(ctx);
115} 115}
116 116
117static void summary_fn(struct cgit_context *ctx) 117static void summary_fn(struct cgit_context *ctx)
118{ 118{
119 cgit_print_summary(); 119 cgit_print_summary();
120} 120}
121 121
122static void tag_fn(struct cgit_context *ctx) 122static void tag_fn(struct cgit_context *ctx)
123{ 123{
124 cgit_print_tag(ctx->qry.sha1); 124 cgit_print_tag(ctx->qry.sha1);
125} 125}
126 126
127static void tree_fn(struct cgit_context *ctx) 127static void tree_fn(struct cgit_context *ctx)
128{ 128{
129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path); 129 cgit_print_tree(ctx->qry.sha1, ctx->qry.path);
130} 130}
131 131
132#define def_cmd(name, want_repo, want_layout) \ 132#define def_cmd(name, want_repo, want_layout) \
133 {#name, name##_fn, want_repo, want_layout} 133 {#name, name##_fn, want_repo, want_layout}
134 134
135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) 135struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx)
136{ 136{
137 static struct cgit_cmd cmds[] = { 137 static struct cgit_cmd cmds[] = {
138 def_cmd(HEAD, 1, 0), 138 def_cmd(HEAD, 1, 0),
139 def_cmd(atom, 1, 0), 139 def_cmd(atom, 1, 0),
140 def_cmd(about, 0, 1), 140 def_cmd(about, 0, 1),
141 def_cmd(blob, 1, 0), 141 def_cmd(blob, 1, 0),
142 def_cmd(commit, 1, 1), 142 def_cmd(commit, 1, 1),
143 def_cmd(diff, 1, 1), 143 def_cmd(diff, 1, 1),
144 def_cmd(info, 1, 0), 144 def_cmd(info, 1, 0),
145 def_cmd(log, 1, 1), 145 def_cmd(log, 1, 1),
146 def_cmd(ls_cache, 0, 0), 146 def_cmd(ls_cache, 0, 0),
147 def_cmd(objects, 1, 0), 147 def_cmd(objects, 1, 0),
148 def_cmd(patch, 1, 0), 148 def_cmd(patch, 1, 0),
149 def_cmd(plain, 1, 0), 149 def_cmd(plain, 1, 0),
150 def_cmd(refs, 1, 1), 150 def_cmd(refs, 1, 1),
151 def_cmd(repolist, 0, 0), 151 def_cmd(repolist, 0, 0),
152 def_cmd(snapshot, 1, 0), 152 def_cmd(snapshot, 1, 0),
153 def_cmd(stats, 1, 1), 153 def_cmd(stats, 1, 1),
154 def_cmd(summary, 1, 1), 154 def_cmd(summary, 1, 1),
155 def_cmd(tag, 1, 1), 155 def_cmd(tag, 1, 1),
156 def_cmd(tree, 1, 1), 156 def_cmd(tree, 1, 1),
157 }; 157 };
158 int i; 158 int i;
159 159
160 if (ctx->qry.page == NULL) { 160 if (ctx->qry.page == NULL) {
161 if (ctx->repo) 161 if (ctx->repo)
162 ctx->qry.page = "summary"; 162 ctx->qry.page = "summary";
163 else 163 else
164 ctx->qry.page = "repolist"; 164 ctx->qry.page = "repolist";
165 } 165 }
166 166
167 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) 167 for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++)
168 if (!strcmp(ctx->qry.page, cmds[i].name)) 168 if (!strcmp(ctx->qry.page, cmds[i].name))
169 return &cmds[i]; 169 return &cmds[i];
170 return NULL; 170 return NULL;
171} 171}
diff --git a/shared.c b/shared.c
index 783604b..911a55a 100644
--- a/shared.c
+++ b/shared.c
@@ -1,394 +1,395 @@
1/* shared.c: global vars + some callback functions 1/* shared.c: global vars + some callback 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 10
11struct cgit_repolist cgit_repolist; 11struct cgit_repolist cgit_repolist;
12struct cgit_context ctx; 12struct cgit_context ctx;
13int cgit_cmd; 13int cgit_cmd;
14 14
15int chk_zero(int result, char *msg) 15int chk_zero(int result, char *msg)
16{ 16{
17 if (result != 0) 17 if (result != 0)
18 die("%s: %s", msg, strerror(errno)); 18 die("%s: %s", msg, strerror(errno));
19 return result; 19 return result;
20} 20}
21 21
22int chk_positive(int result, char *msg) 22int chk_positive(int result, char *msg)
23{ 23{
24 if (result <= 0) 24 if (result <= 0)
25 die("%s: %s", msg, strerror(errno)); 25 die("%s: %s", msg, strerror(errno));
26 return result; 26 return result;
27} 27}
28 28
29int chk_non_negative(int result, char *msg) 29int chk_non_negative(int result, char *msg)
30{ 30{
31 if (result < 0) 31 if (result < 0)
32 die("%s: %s",msg, strerror(errno)); 32 die("%s: %s",msg, strerror(errno));
33 return result; 33 return result;
34} 34}
35 35
36struct cgit_repo *cgit_add_repo(const char *url) 36struct cgit_repo *cgit_add_repo(const char *url)
37{ 37{
38 struct cgit_repo *ret; 38 struct cgit_repo *ret;
39 39
40 if (++cgit_repolist.count > cgit_repolist.length) { 40 if (++cgit_repolist.count > cgit_repolist.length) {
41 if (cgit_repolist.length == 0) 41 if (cgit_repolist.length == 0)
42 cgit_repolist.length = 8; 42 cgit_repolist.length = 8;
43 else 43 else
44 cgit_repolist.length *= 2; 44 cgit_repolist.length *= 2;
45 cgit_repolist.repos = xrealloc(cgit_repolist.repos, 45 cgit_repolist.repos = xrealloc(cgit_repolist.repos,
46 cgit_repolist.length * 46 cgit_repolist.length *
47 sizeof(struct cgit_repo)); 47 sizeof(struct cgit_repo));
48 } 48 }
49 49
50 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 50 ret = &cgit_repolist.repos[cgit_repolist.count-1];
51 ret->url = trim_end(url, '/'); 51 ret->url = trim_end(url, '/');
52 ret->name = ret->url; 52 ret->name = ret->url;
53 ret->path = NULL; 53 ret->path = NULL;
54 ret->desc = "[no description]"; 54 ret->desc = "[no description]";
55 ret->owner = NULL; 55 ret->owner = NULL;
56 ret->group = ctx.cfg.repo_group; 56 ret->group = ctx.cfg.repo_group;
57 ret->defbranch = "master"; 57 ret->defbranch = "master";
58 ret->snapshots = ctx.cfg.snapshots; 58 ret->snapshots = ctx.cfg.snapshots;
59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 59 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 60 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
61 ret->max_stats = ctx.cfg.max_stats; 61 ret->max_stats = ctx.cfg.max_stats;
62 ret->module_link = ctx.cfg.module_link; 62 ret->module_link = ctx.cfg.module_link;
63 ret->readme = NULL; 63 ret->readme = NULL;
64 ret->mtime = -1; 64 ret->mtime = -1;
65 ret->about_filter = ctx.cfg.about_filter;
65 ret->commit_filter = ctx.cfg.commit_filter; 66 ret->commit_filter = ctx.cfg.commit_filter;
66 ret->source_filter = ctx.cfg.source_filter; 67 ret->source_filter = ctx.cfg.source_filter;
67 return ret; 68 return ret;
68} 69}
69 70
70struct cgit_repo *cgit_get_repoinfo(const char *url) 71struct cgit_repo *cgit_get_repoinfo(const char *url)
71{ 72{
72 int i; 73 int i;
73 struct cgit_repo *repo; 74 struct cgit_repo *repo;
74 75
75 for (i=0; i<cgit_repolist.count; i++) { 76 for (i=0; i<cgit_repolist.count; i++) {
76 repo = &cgit_repolist.repos[i]; 77 repo = &cgit_repolist.repos[i];
77 if (!strcmp(repo->url, url)) 78 if (!strcmp(repo->url, url))
78 return repo; 79 return repo;
79 } 80 }
80 return NULL; 81 return NULL;
81} 82}
82 83
83void *cgit_free_commitinfo(struct commitinfo *info) 84void *cgit_free_commitinfo(struct commitinfo *info)
84{ 85{
85 free(info->author); 86 free(info->author);
86 free(info->author_email); 87 free(info->author_email);
87 free(info->committer); 88 free(info->committer);
88 free(info->committer_email); 89 free(info->committer_email);
89 free(info->subject); 90 free(info->subject);
90 free(info->msg); 91 free(info->msg);
91 free(info->msg_encoding); 92 free(info->msg_encoding);
92 free(info); 93 free(info);
93 return NULL; 94 return NULL;
94} 95}
95 96
96char *trim_end(const char *str, char c) 97char *trim_end(const char *str, char c)
97{ 98{
98 int len; 99 int len;
99 char *s, *t; 100 char *s, *t;
100 101
101 if (str == NULL) 102 if (str == NULL)
102 return NULL; 103 return NULL;
103 t = (char *)str; 104 t = (char *)str;
104 len = strlen(t); 105 len = strlen(t);
105 while(len > 0 && t[len - 1] == c) 106 while(len > 0 && t[len - 1] == c)
106 len--; 107 len--;
107 108
108 if (len == 0) 109 if (len == 0)
109 return NULL; 110 return NULL;
110 111
111 c = t[len]; 112 c = t[len];
112 t[len] = '\0'; 113 t[len] = '\0';
113 s = xstrdup(t); 114 s = xstrdup(t);
114 t[len] = c; 115 t[len] = c;
115 return s; 116 return s;
116} 117}
117 118
118char *strlpart(char *txt, int maxlen) 119char *strlpart(char *txt, int maxlen)
119{ 120{
120 char *result; 121 char *result;
121 122
122 if (!txt) 123 if (!txt)
123 return txt; 124 return txt;
124 125
125 if (strlen(txt) <= maxlen) 126 if (strlen(txt) <= maxlen)
126 return txt; 127 return txt;
127 result = xmalloc(maxlen + 1); 128 result = xmalloc(maxlen + 1);
128 memcpy(result, txt, maxlen - 3); 129 memcpy(result, txt, maxlen - 3);
129 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.'; 130 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.';
130 result[maxlen] = '\0'; 131 result[maxlen] = '\0';
131 return result; 132 return result;
132} 133}
133 134
134char *strrpart(char *txt, int maxlen) 135char *strrpart(char *txt, int maxlen)
135{ 136{
136 char *result; 137 char *result;
137 138
138 if (!txt) 139 if (!txt)
139 return txt; 140 return txt;
140 141
141 if (strlen(txt) <= maxlen) 142 if (strlen(txt) <= maxlen)
142 return txt; 143 return txt;
143 result = xmalloc(maxlen + 1); 144 result = xmalloc(maxlen + 1);
144 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3); 145 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3);
145 result[0] = result[1] = result[2] = '.'; 146 result[0] = result[1] = result[2] = '.';
146 return result; 147 return result;
147} 148}
148 149
149void cgit_add_ref(struct reflist *list, struct refinfo *ref) 150void cgit_add_ref(struct reflist *list, struct refinfo *ref)
150{ 151{
151 size_t size; 152 size_t size;
152 153
153 if (list->count >= list->alloc) { 154 if (list->count >= list->alloc) {
154 list->alloc += (list->alloc ? list->alloc : 4); 155 list->alloc += (list->alloc ? list->alloc : 4);
155 size = list->alloc * sizeof(struct refinfo *); 156 size = list->alloc * sizeof(struct refinfo *);
156 list->refs = xrealloc(list->refs, size); 157 list->refs = xrealloc(list->refs, size);
157 } 158 }
158 list->refs[list->count++] = ref; 159 list->refs[list->count++] = ref;
159} 160}
160 161
161struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1) 162struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1)
162{ 163{
163 struct refinfo *ref; 164 struct refinfo *ref;
164 165
165 ref = xmalloc(sizeof (struct refinfo)); 166 ref = xmalloc(sizeof (struct refinfo));
166 ref->refname = xstrdup(refname); 167 ref->refname = xstrdup(refname);
167 ref->object = parse_object(sha1); 168 ref->object = parse_object(sha1);
168 switch (ref->object->type) { 169 switch (ref->object->type) {
169 case OBJ_TAG: 170 case OBJ_TAG:
170 ref->tag = cgit_parse_tag((struct tag *)ref->object); 171 ref->tag = cgit_parse_tag((struct tag *)ref->object);
171 break; 172 break;
172 case OBJ_COMMIT: 173 case OBJ_COMMIT:
173 ref->commit = cgit_parse_commit((struct commit *)ref->object); 174 ref->commit = cgit_parse_commit((struct commit *)ref->object);
174 break; 175 break;
175 } 176 }
176 return ref; 177 return ref;
177} 178}
178 179
179int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags, 180int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags,
180 void *cb_data) 181 void *cb_data)
181{ 182{
182 struct reflist *list = (struct reflist *)cb_data; 183 struct reflist *list = (struct reflist *)cb_data;
183 struct refinfo *info = cgit_mk_refinfo(refname, sha1); 184 struct refinfo *info = cgit_mk_refinfo(refname, sha1);
184 185
185 if (info) 186 if (info)
186 cgit_add_ref(list, info); 187 cgit_add_ref(list, info);
187 return 0; 188 return 0;
188} 189}
189 190
190void cgit_diff_tree_cb(struct diff_queue_struct *q, 191void cgit_diff_tree_cb(struct diff_queue_struct *q,
191 struct diff_options *options, void *data) 192 struct diff_options *options, void *data)
192{ 193{
193 int i; 194 int i;
194 195
195 for (i = 0; i < q->nr; i++) { 196 for (i = 0; i < q->nr; i++) {
196 if (q->queue[i]->status == 'U') 197 if (q->queue[i]->status == 'U')
197 continue; 198 continue;
198 ((filepair_fn)data)(q->queue[i]); 199 ((filepair_fn)data)(q->queue[i]);
199 } 200 }
200} 201}
201 202
202static int load_mmfile(mmfile_t *file, const unsigned char *sha1) 203static int load_mmfile(mmfile_t *file, const unsigned char *sha1)
203{ 204{
204 enum object_type type; 205 enum object_type type;
205 206
206 if (is_null_sha1(sha1)) { 207 if (is_null_sha1(sha1)) {
207 file->ptr = (char *)""; 208 file->ptr = (char *)"";
208 file->size = 0; 209 file->size = 0;
209 } else { 210 } else {
210 file->ptr = read_sha1_file(sha1, &type, 211 file->ptr = read_sha1_file(sha1, &type,
211 (unsigned long *)&file->size); 212 (unsigned long *)&file->size);
212 } 213 }
213 return 1; 214 return 1;
214} 215}
215 216
216/* 217/*
217 * Receive diff-buffers from xdiff and concatenate them as 218 * Receive diff-buffers from xdiff and concatenate them as
218 * needed across multiple callbacks. 219 * needed across multiple callbacks.
219 * 220 *
220 * This is basically a copy of xdiff-interface.c/xdiff_outf(), 221 * This is basically a copy of xdiff-interface.c/xdiff_outf(),
221 * ripped from git and modified to use globals instead of 222 * ripped from git and modified to use globals instead of
222 * a special callback-struct. 223 * a special callback-struct.
223 */ 224 */
224char *diffbuf = NULL; 225char *diffbuf = NULL;
225int buflen = 0; 226int buflen = 0;
226 227
227int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) 228int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
228{ 229{
229 int i; 230 int i;
230 231
231 for (i = 0; i < nbuf; i++) { 232 for (i = 0; i < nbuf; i++) {
232 if (mb[i].ptr[mb[i].size-1] != '\n') { 233 if (mb[i].ptr[mb[i].size-1] != '\n') {
233 /* Incomplete line */ 234 /* Incomplete line */
234 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 235 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
235 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 236 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
236 buflen += mb[i].size; 237 buflen += mb[i].size;
237 continue; 238 continue;
238 } 239 }
239 240
240 /* we have a complete line */ 241 /* we have a complete line */
241 if (!diffbuf) { 242 if (!diffbuf) {
242 ((linediff_fn)priv)(mb[i].ptr, mb[i].size); 243 ((linediff_fn)priv)(mb[i].ptr, mb[i].size);
243 continue; 244 continue;
244 } 245 }
245 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 246 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
246 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 247 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
247 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); 248 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
248 free(diffbuf); 249 free(diffbuf);
249 diffbuf = NULL; 250 diffbuf = NULL;
250 buflen = 0; 251 buflen = 0;
251 } 252 }
252 if (diffbuf) { 253 if (diffbuf) {
253 ((linediff_fn)priv)(diffbuf, buflen); 254 ((linediff_fn)priv)(diffbuf, buflen);
254 free(diffbuf); 255 free(diffbuf);
255 diffbuf = NULL; 256 diffbuf = NULL;
256 buflen = 0; 257 buflen = 0;
257 } 258 }
258 return 0; 259 return 0;
259} 260}
260 261
261int cgit_diff_files(const unsigned char *old_sha1, 262int cgit_diff_files(const unsigned char *old_sha1,
262 const unsigned char *new_sha1, unsigned long *old_size, 263 const unsigned char *new_sha1, unsigned long *old_size,
263 unsigned long *new_size, int *binary, linediff_fn fn) 264 unsigned long *new_size, int *binary, linediff_fn fn)
264{ 265{
265 mmfile_t file1, file2; 266 mmfile_t file1, file2;
266 xpparam_t diff_params; 267 xpparam_t diff_params;
267 xdemitconf_t emit_params; 268 xdemitconf_t emit_params;
268 xdemitcb_t emit_cb; 269 xdemitcb_t emit_cb;
269 270
270 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) 271 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
271 return 1; 272 return 1;
272 273
273 *old_size = file1.size; 274 *old_size = file1.size;
274 *new_size = file2.size; 275 *new_size = file2.size;
275 276
276 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) || 277 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) ||
277 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) { 278 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) {
278 *binary = 1; 279 *binary = 1;
279 return 0; 280 return 0;
280 } 281 }
281 282
282 memset(&diff_params, 0, sizeof(diff_params)); 283 memset(&diff_params, 0, sizeof(diff_params));
283 memset(&emit_params, 0, sizeof(emit_params)); 284 memset(&emit_params, 0, sizeof(emit_params));
284 memset(&emit_cb, 0, sizeof(emit_cb)); 285 memset(&emit_cb, 0, sizeof(emit_cb));
285 diff_params.flags = XDF_NEED_MINIMAL; 286 diff_params.flags = XDF_NEED_MINIMAL;
286 emit_params.ctxlen = 3; 287 emit_params.ctxlen = 3;
287 emit_params.flags = XDL_EMIT_FUNCNAMES; 288 emit_params.flags = XDL_EMIT_FUNCNAMES;
288 emit_cb.outf = filediff_cb; 289 emit_cb.outf = filediff_cb;
289 emit_cb.priv = fn; 290 emit_cb.priv = fn;
290 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); 291 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
291 return 0; 292 return 0;
292} 293}
293 294
294void cgit_diff_tree(const unsigned char *old_sha1, 295void cgit_diff_tree(const unsigned char *old_sha1,
295 const unsigned char *new_sha1, 296 const unsigned char *new_sha1,
296 filepair_fn fn, const char *prefix) 297 filepair_fn fn, const char *prefix)
297{ 298{
298 struct diff_options opt; 299 struct diff_options opt;
299 int ret; 300 int ret;
300 int prefixlen; 301 int prefixlen;
301 302
302 diff_setup(&opt); 303 diff_setup(&opt);
303 opt.output_format = DIFF_FORMAT_CALLBACK; 304 opt.output_format = DIFF_FORMAT_CALLBACK;
304 opt.detect_rename = 1; 305 opt.detect_rename = 1;
305 opt.rename_limit = ctx.cfg.renamelimit; 306 opt.rename_limit = ctx.cfg.renamelimit;
306 DIFF_OPT_SET(&opt, RECURSIVE); 307 DIFF_OPT_SET(&opt, RECURSIVE);
307 opt.format_callback = cgit_diff_tree_cb; 308 opt.format_callback = cgit_diff_tree_cb;
308 opt.format_callback_data = fn; 309 opt.format_callback_data = fn;
309 if (prefix) { 310 if (prefix) {
310 opt.nr_paths = 1; 311 opt.nr_paths = 1;
311 opt.paths = &prefix; 312 opt.paths = &prefix;
312 prefixlen = strlen(prefix); 313 prefixlen = strlen(prefix);
313 opt.pathlens = &prefixlen; 314 opt.pathlens = &prefixlen;
314 } 315 }
315 diff_setup_done(&opt); 316 diff_setup_done(&opt);
316 317
317 if (old_sha1 && !is_null_sha1(old_sha1)) 318 if (old_sha1 && !is_null_sha1(old_sha1))
318 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt); 319 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt);
319 else 320 else
320 ret = diff_root_tree_sha1(new_sha1, "", &opt); 321 ret = diff_root_tree_sha1(new_sha1, "", &opt);
321 diffcore_std(&opt); 322 diffcore_std(&opt);
322 diff_flush(&opt); 323 diff_flush(&opt);
323} 324}
324 325
325void cgit_diff_commit(struct commit *commit, filepair_fn fn) 326void cgit_diff_commit(struct commit *commit, filepair_fn fn)
326{ 327{
327 unsigned char *old_sha1 = NULL; 328 unsigned char *old_sha1 = NULL;
328 329
329 if (commit->parents) 330 if (commit->parents)
330 old_sha1 = commit->parents->item->object.sha1; 331 old_sha1 = commit->parents->item->object.sha1;
331 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL); 332 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL);
332} 333}
333 334
334int cgit_parse_snapshots_mask(const char *str) 335int cgit_parse_snapshots_mask(const char *str)
335{ 336{
336 const struct cgit_snapshot_format *f; 337 const struct cgit_snapshot_format *f;
337 static const char *delim = " \t,:/|;"; 338 static const char *delim = " \t,:/|;";
338 int tl, sl, rv = 0; 339 int tl, sl, rv = 0;
339 340
340 /* favor legacy setting */ 341 /* favor legacy setting */
341 if(atoi(str)) 342 if(atoi(str))
342 return 1; 343 return 1;
343 for(;;) { 344 for(;;) {
344 str += strspn(str,delim); 345 str += strspn(str,delim);
345 tl = strcspn(str,delim); 346 tl = strcspn(str,delim);
346 if (!tl) 347 if (!tl)
347 break; 348 break;
348 for (f = cgit_snapshot_formats; f->suffix; f++) { 349 for (f = cgit_snapshot_formats; f->suffix; f++) {
349 sl = strlen(f->suffix); 350 sl = strlen(f->suffix);
350 if((tl == sl && !strncmp(f->suffix, str, tl)) || 351 if((tl == sl && !strncmp(f->suffix, str, tl)) ||
351 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) { 352 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) {
352 rv |= f->bit; 353 rv |= f->bit;
353 break; 354 break;
354 } 355 }
355 } 356 }
356 str += tl; 357 str += tl;
357 } 358 }
358 return rv; 359 return rv;
359} 360}
360 361
361int cgit_open_filter(struct cgit_filter *filter) 362int cgit_open_filter(struct cgit_filter *filter)
362{ 363{
363 364
364 filter->old_stdout = chk_positive(dup(STDOUT_FILENO), 365 filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
365 "Unable to duplicate STDOUT"); 366 "Unable to duplicate STDOUT");
366 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess"); 367 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess");
367 filter->pid = chk_non_negative(fork(), "Unable to create subprocess"); 368 filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
368 if (filter->pid == 0) { 369 if (filter->pid == 0) {
369 close(filter->pipe_fh[1]); 370 close(filter->pipe_fh[1]);
370 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO), 371 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO),
371 "Unable to use pipe as STDIN"); 372 "Unable to use pipe as STDIN");
372 execvp(filter->cmd, filter->argv); 373 execvp(filter->cmd, filter->argv);
373 die("Unable to exec subprocess %s: %s (%d)", filter->cmd, 374 die("Unable to exec subprocess %s: %s (%d)", filter->cmd,
374 strerror(errno), errno); 375 strerror(errno), errno);
375 } 376 }
376 close(filter->pipe_fh[0]); 377 close(filter->pipe_fh[0]);
377 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO), 378 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO),
378 "Unable to use pipe as STDOUT"); 379 "Unable to use pipe as STDOUT");
379 close(filter->pipe_fh[1]); 380 close(filter->pipe_fh[1]);
380 return 0; 381 return 0;
381} 382}
382 383
383int cgit_close_filter(struct cgit_filter *filter) 384int cgit_close_filter(struct cgit_filter *filter)
384{ 385{
385 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO), 386 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
386 "Unable to restore STDOUT"); 387 "Unable to restore STDOUT");
387 close(filter->old_stdout); 388 close(filter->old_stdout);
388 if (filter->pid < 0) 389 if (filter->pid < 0)
389 return 0; 390 return 0;
390 waitpid(filter->pid, &filter->exitstatus, 0); 391 waitpid(filter->pid, &filter->exitstatus, 0);
391 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus)) 392 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus))
392 return 0; 393 return 0;
393 die("Subprocess %s exited abnormally", filter->cmd); 394 die("Subprocess %s exited abnormally", filter->cmd);
394} 395}
diff --git a/ui-repolist.c b/ui-repolist.c
index 2c13d50..25f076f 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -1,278 +1,283 @@
1/* ui-repolist.c: functions for generating the repolist page 1/* ui-repolist.c: functions for generating the repolist page
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/* This is needed for strcasestr to be defined by <string.h> */ 9/* This is needed for strcasestr to be defined by <string.h> */
10#define _GNU_SOURCE 1 10#define _GNU_SOURCE 1
11#include <string.h> 11#include <string.h>
12 12
13#include <time.h> 13#include <time.h>
14 14
15#include "cgit.h" 15#include "cgit.h"
16#include "html.h" 16#include "html.h"
17#include "ui-shared.h" 17#include "ui-shared.h"
18 18
19time_t read_agefile(char *path) 19time_t read_agefile(char *path)
20{ 20{
21 FILE *f; 21 FILE *f;
22 static char buf[64], buf2[64]; 22 static char buf[64], buf2[64];
23 23
24 if (!(f = fopen(path, "r"))) 24 if (!(f = fopen(path, "r")))
25 return -1; 25 return -1;
26 if (fgets(buf, sizeof(buf), f) == NULL) 26 if (fgets(buf, sizeof(buf), f) == NULL)
27 return -1; 27 return -1;
28 fclose(f); 28 fclose(f);
29 if (parse_date(buf, buf2, sizeof(buf2))) 29 if (parse_date(buf, buf2, sizeof(buf2)))
30 return strtoul(buf2, NULL, 10); 30 return strtoul(buf2, NULL, 10);
31 else 31 else
32 return 0; 32 return 0;
33} 33}
34 34
35static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) 35static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime)
36{ 36{
37 char *path; 37 char *path;
38 struct stat s; 38 struct stat s;
39 struct cgit_repo *r = (struct cgit_repo *)repo; 39 struct cgit_repo *r = (struct cgit_repo *)repo;
40 40
41 if (repo->mtime != -1) { 41 if (repo->mtime != -1) {
42 *mtime = repo->mtime; 42 *mtime = repo->mtime;
43 return 1; 43 return 1;
44 } 44 }
45 path = fmt("%s/%s", repo->path, ctx.cfg.agefile); 45 path = fmt("%s/%s", repo->path, ctx.cfg.agefile);
46 if (stat(path, &s) == 0) { 46 if (stat(path, &s) == 0) {
47 *mtime = read_agefile(path); 47 *mtime = read_agefile(path);
48 r->mtime = *mtime; 48 r->mtime = *mtime;
49 return 1; 49 return 1;
50 } 50 }
51 51
52 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); 52 path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch);
53 if (stat(path, &s) == 0) 53 if (stat(path, &s) == 0)
54 *mtime = s.st_mtime; 54 *mtime = s.st_mtime;
55 else 55 else
56 *mtime = 0; 56 *mtime = 0;
57 57
58 r->mtime = *mtime; 58 r->mtime = *mtime;
59 return (r->mtime != 0); 59 return (r->mtime != 0);
60} 60}
61 61
62static void print_modtime(struct cgit_repo *repo) 62static void print_modtime(struct cgit_repo *repo)
63{ 63{
64 time_t t; 64 time_t t;
65 if (get_repo_modtime(repo, &t)) 65 if (get_repo_modtime(repo, &t))
66 cgit_print_age(t, -1, NULL); 66 cgit_print_age(t, -1, NULL);
67} 67}
68 68
69int is_match(struct cgit_repo *repo) 69int is_match(struct cgit_repo *repo)
70{ 70{
71 if (!ctx.qry.search) 71 if (!ctx.qry.search)
72 return 1; 72 return 1;
73 if (repo->url && strcasestr(repo->url, ctx.qry.search)) 73 if (repo->url && strcasestr(repo->url, ctx.qry.search))
74 return 1; 74 return 1;
75 if (repo->name && strcasestr(repo->name, ctx.qry.search)) 75 if (repo->name && strcasestr(repo->name, ctx.qry.search))
76 return 1; 76 return 1;
77 if (repo->desc && strcasestr(repo->desc, ctx.qry.search)) 77 if (repo->desc && strcasestr(repo->desc, ctx.qry.search))
78 return 1; 78 return 1;
79 if (repo->owner && strcasestr(repo->owner, ctx.qry.search)) 79 if (repo->owner && strcasestr(repo->owner, ctx.qry.search))
80 return 1; 80 return 1;
81 return 0; 81 return 0;
82} 82}
83 83
84int is_in_url(struct cgit_repo *repo) 84int is_in_url(struct cgit_repo *repo)
85{ 85{
86 if (!ctx.qry.url) 86 if (!ctx.qry.url)
87 return 1; 87 return 1;
88 if (repo->url && !prefixcmp(repo->url, ctx.qry.url)) 88 if (repo->url && !prefixcmp(repo->url, ctx.qry.url))
89 return 1; 89 return 1;
90 return 0; 90 return 0;
91} 91}
92 92
93void print_sort_header(const char *title, const char *sort) 93void print_sort_header(const char *title, const char *sort)
94{ 94{
95 htmlf("<th class='left'><a href='./?s=%s", sort); 95 htmlf("<th class='left'><a href='./?s=%s", sort);
96 if (ctx.qry.search) { 96 if (ctx.qry.search) {
97 html("&q="); 97 html("&q=");
98 html_url_arg(ctx.qry.search); 98 html_url_arg(ctx.qry.search);
99 } 99 }
100 htmlf("'>%s</a></th>", title); 100 htmlf("'>%s</a></th>", title);
101} 101}
102 102
103void print_header(int columns) 103void print_header(int columns)
104{ 104{
105 html("<tr class='nohover'>"); 105 html("<tr class='nohover'>");
106 print_sort_header("Name", "name"); 106 print_sort_header("Name", "name");
107 print_sort_header("Description", "desc"); 107 print_sort_header("Description", "desc");
108 print_sort_header("Owner", "owner"); 108 print_sort_header("Owner", "owner");
109 print_sort_header("Idle", "idle"); 109 print_sort_header("Idle", "idle");
110 if (ctx.cfg.enable_index_links) 110 if (ctx.cfg.enable_index_links)
111 html("<th class='left'>Links</th>"); 111 html("<th class='left'>Links</th>");
112 html("</tr>\n"); 112 html("</tr>\n");
113} 113}
114 114
115 115
116void print_pager(int items, int pagelen, char *search) 116void print_pager(int items, int pagelen, char *search)
117{ 117{
118 int i; 118 int i;
119 html("<div class='pager'>"); 119 html("<div class='pager'>");
120 for(i = 0; i * pagelen < items; i++) 120 for(i = 0; i * pagelen < items; i++)
121 cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL, 121 cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL,
122 search, i * pagelen); 122 search, i * pagelen);
123 html("</div>"); 123 html("</div>");
124} 124}
125 125
126static int cmp(const char *s1, const char *s2) 126static int cmp(const char *s1, const char *s2)
127{ 127{
128 if (s1 && s2) 128 if (s1 && s2)
129 return strcmp(s1, s2); 129 return strcmp(s1, s2);
130 if (s1 && !s2) 130 if (s1 && !s2)
131 return -1; 131 return -1;
132 if (s2 && !s1) 132 if (s2 && !s1)
133 return 1; 133 return 1;
134 return 0; 134 return 0;
135} 135}
136 136
137static int sort_name(const void *a, const void *b) 137static int sort_name(const void *a, const void *b)
138{ 138{
139 const struct cgit_repo *r1 = a; 139 const struct cgit_repo *r1 = a;
140 const struct cgit_repo *r2 = b; 140 const struct cgit_repo *r2 = b;
141 141
142 return cmp(r1->name, r2->name); 142 return cmp(r1->name, r2->name);
143} 143}
144 144
145static int sort_desc(const void *a, const void *b) 145static int sort_desc(const void *a, const void *b)
146{ 146{
147 const struct cgit_repo *r1 = a; 147 const struct cgit_repo *r1 = a;
148 const struct cgit_repo *r2 = b; 148 const struct cgit_repo *r2 = b;
149 149
150 return cmp(r1->desc, r2->desc); 150 return cmp(r1->desc, r2->desc);
151} 151}
152 152
153static int sort_owner(const void *a, const void *b) 153static int sort_owner(const void *a, const void *b)
154{ 154{
155 const struct cgit_repo *r1 = a; 155 const struct cgit_repo *r1 = a;
156 const struct cgit_repo *r2 = b; 156 const struct cgit_repo *r2 = b;
157 157
158 return cmp(r1->owner, r2->owner); 158 return cmp(r1->owner, r2->owner);
159} 159}
160 160
161static int sort_idle(const void *a, const void *b) 161static int sort_idle(const void *a, const void *b)
162{ 162{
163 const struct cgit_repo *r1 = a; 163 const struct cgit_repo *r1 = a;
164 const struct cgit_repo *r2 = b; 164 const struct cgit_repo *r2 = b;
165 time_t t1, t2; 165 time_t t1, t2;
166 166
167 t1 = t2 = 0; 167 t1 = t2 = 0;
168 get_repo_modtime(r1, &t1); 168 get_repo_modtime(r1, &t1);
169 get_repo_modtime(r2, &t2); 169 get_repo_modtime(r2, &t2);
170 return t2 - t1; 170 return t2 - t1;
171} 171}
172 172
173struct sortcolumn { 173struct sortcolumn {
174 const char *name; 174 const char *name;
175 int (*fn)(const void *a, const void *b); 175 int (*fn)(const void *a, const void *b);
176}; 176};
177 177
178struct sortcolumn sortcolumn[] = { 178struct sortcolumn sortcolumn[] = {
179 {"name", sort_name}, 179 {"name", sort_name},
180 {"desc", sort_desc}, 180 {"desc", sort_desc},
181 {"owner", sort_owner}, 181 {"owner", sort_owner},
182 {"idle", sort_idle}, 182 {"idle", sort_idle},
183 {NULL, NULL} 183 {NULL, NULL}
184}; 184};
185 185
186int sort_repolist(char *field) 186int sort_repolist(char *field)
187{ 187{
188 struct sortcolumn *column; 188 struct sortcolumn *column;
189 189
190 for (column = &sortcolumn[0]; column->name; column++) { 190 for (column = &sortcolumn[0]; column->name; column++) {
191 if (strcmp(field, column->name)) 191 if (strcmp(field, column->name))
192 continue; 192 continue;
193 qsort(cgit_repolist.repos, cgit_repolist.count, 193 qsort(cgit_repolist.repos, cgit_repolist.count,
194 sizeof(struct cgit_repo), column->fn); 194 sizeof(struct cgit_repo), column->fn);
195 return 1; 195 return 1;
196 } 196 }
197 return 0; 197 return 0;
198} 198}
199 199
200 200
201void cgit_print_repolist() 201void cgit_print_repolist()
202{ 202{
203 int i, columns = 4, hits = 0, header = 0; 203 int i, columns = 4, hits = 0, header = 0;
204 char *last_group = NULL; 204 char *last_group = NULL;
205 int sorted = 0; 205 int sorted = 0;
206 206
207 if (ctx.cfg.enable_index_links) 207 if (ctx.cfg.enable_index_links)
208 columns++; 208 columns++;
209 209
210 ctx.page.title = ctx.cfg.root_title; 210 ctx.page.title = ctx.cfg.root_title;
211 cgit_print_http_headers(&ctx); 211 cgit_print_http_headers(&ctx);
212 cgit_print_docstart(&ctx); 212 cgit_print_docstart(&ctx);
213 cgit_print_pageheader(&ctx); 213 cgit_print_pageheader(&ctx);
214 214
215 if (ctx.cfg.index_header) 215 if (ctx.cfg.index_header)
216 html_include(ctx.cfg.index_header); 216 html_include(ctx.cfg.index_header);
217 217
218 if(ctx.qry.sort) 218 if(ctx.qry.sort)
219 sorted = sort_repolist(ctx.qry.sort); 219 sorted = sort_repolist(ctx.qry.sort);
220 220
221 html("<table summary='repository list' class='list nowrap'>"); 221 html("<table summary='repository list' class='list nowrap'>");
222 for (i=0; i<cgit_repolist.count; i++) { 222 for (i=0; i<cgit_repolist.count; i++) {
223 ctx.repo = &cgit_repolist.repos[i]; 223 ctx.repo = &cgit_repolist.repos[i];
224 if (!(is_match(ctx.repo) && is_in_url(ctx.repo))) 224 if (!(is_match(ctx.repo) && is_in_url(ctx.repo)))
225 continue; 225 continue;
226 hits++; 226 hits++;
227 if (hits <= ctx.qry.ofs) 227 if (hits <= ctx.qry.ofs)
228 continue; 228 continue;
229 if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count) 229 if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count)
230 continue; 230 continue;
231 if (!header++) 231 if (!header++)
232 print_header(columns); 232 print_header(columns);
233 if (!sorted && 233 if (!sorted &&
234 ((last_group == NULL && ctx.repo->group != NULL) || 234 ((last_group == NULL && ctx.repo->group != NULL) ||
235 (last_group != NULL && ctx.repo->group == NULL) || 235 (last_group != NULL && ctx.repo->group == NULL) ||
236 (last_group != NULL && ctx.repo->group != NULL && 236 (last_group != NULL && ctx.repo->group != NULL &&
237 strcmp(ctx.repo->group, last_group)))) { 237 strcmp(ctx.repo->group, last_group)))) {
238 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>", 238 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>",
239 columns); 239 columns);
240 html_txt(ctx.repo->group); 240 html_txt(ctx.repo->group);
241 html("</td></tr>"); 241 html("</td></tr>");
242 last_group = ctx.repo->group; 242 last_group = ctx.repo->group;
243 } 243 }
244 htmlf("<tr><td class='%s'>", 244 htmlf("<tr><td class='%s'>",
245 !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); 245 !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo");
246 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); 246 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
247 html("</td><td>"); 247 html("</td><td>");
248 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); 248 html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL);
249 html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc); 249 html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc);
250 html_link_close(); 250 html_link_close();
251 html("</td><td>"); 251 html("</td><td>");
252 html_txt(ctx.repo->owner); 252 html_txt(ctx.repo->owner);
253 html("</td><td>"); 253 html("</td><td>");
254 print_modtime(ctx.repo); 254 print_modtime(ctx.repo);
255 html("</td>"); 255 html("</td>");
256 if (ctx.cfg.enable_index_links) { 256 if (ctx.cfg.enable_index_links) {
257 html("<td>"); 257 html("<td>");
258 cgit_summary_link("summary", NULL, "button", NULL); 258 cgit_summary_link("summary", NULL, "button", NULL);
259 cgit_log_link("log", NULL, "button", NULL, NULL, NULL, 259 cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
260 0, NULL, NULL, ctx.qry.showmsg); 260 0, NULL, NULL, ctx.qry.showmsg);
261 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); 261 cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
262 html("</td>"); 262 html("</td>");
263 } 263 }
264 html("</tr>\n"); 264 html("</tr>\n");
265 } 265 }
266 html("</table>"); 266 html("</table>");
267 if (!hits) 267 if (!hits)
268 cgit_print_error("No repositories found"); 268 cgit_print_error("No repositories found");
269 else if (hits > ctx.cfg.max_repo_count) 269 else if (hits > ctx.cfg.max_repo_count)
270 print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search); 270 print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search);
271 cgit_print_docend(); 271 cgit_print_docend();
272} 272}
273 273
274void cgit_print_site_readme() 274void cgit_print_site_readme()
275{ 275{
276 if (ctx.cfg.root_readme) 276 if (!ctx.cfg.root_readme)
277 html_include(ctx.cfg.root_readme); 277 return;
278 if (ctx.cfg.about_filter)
279 cgit_open_filter(ctx.cfg.about_filter);
280 html_include(ctx.cfg.root_readme);
281 if (ctx.cfg.about_filter)
282 cgit_close_filter(ctx.cfg.about_filter);
278} 283}
diff --git a/ui-summary.c b/ui-summary.c
index ede4a62..a2c018e 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -1,76 +1,92 @@
1/* ui-summary.c: functions for generating repo summary page 1/* ui-summary.c: functions for generating repo summary page
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 "html.h" 10#include "html.h"
11#include "ui-log.h" 11#include "ui-log.h"
12#include "ui-refs.h" 12#include "ui-refs.h"
13 13
14int urls = 0; 14int urls = 0;
15 15
16static void print_url(char *base, char *suffix) 16static void print_url(char *base, char *suffix)
17{ 17{
18 if (!base || !*base) 18 if (!base || !*base)
19 return; 19 return;
20 if (urls++ == 0) { 20 if (urls++ == 0) {
21 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 21 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
22 html("<tr><th class='left' colspan='4'>Clone</th></tr>\n"); 22 html("<tr><th class='left' colspan='4'>Clone</th></tr>\n");
23 } 23 }
24 if (suffix && *suffix) 24 if (suffix && *suffix)
25 base = fmt("%s/%s", base, suffix); 25 base = fmt("%s/%s", base, suffix);
26 html("<tr><td colspan='4'><a href='"); 26 html("<tr><td colspan='4'><a href='");
27 html_url_path(base); 27 html_url_path(base);
28 html("'>"); 28 html("'>");
29 html_txt(base); 29 html_txt(base);
30 html("</a></td></tr>\n"); 30 html("</a></td></tr>\n");
31} 31}
32 32
33static void print_urls(char *txt, char *suffix) 33static void print_urls(char *txt, char *suffix)
34{ 34{
35 char *h = txt, *t, c; 35 char *h = txt, *t, c;
36 36
37 while (h && *h) { 37 while (h && *h) {
38 while (h && *h == ' ') 38 while (h && *h == ' ')
39 h++; 39 h++;
40 t = h; 40 t = h;
41 while (t && *t && *t != ' ') 41 while (t && *t && *t != ' ')
42 t++; 42 t++;
43 c = *t; 43 c = *t;
44 *t = 0; 44 *t = 0;
45 print_url(h, suffix); 45 print_url(h, suffix);
46 *t = c; 46 *t = c;
47 h = t; 47 h = t;
48 } 48 }
49} 49}
50 50
51void cgit_print_summary() 51void cgit_print_summary()
52{ 52{
53 html("<table summary='repository info' class='list nowrap'>"); 53 html("<table summary='repository info' class='list nowrap'>");
54 cgit_print_branches(ctx.cfg.summary_branches); 54 cgit_print_branches(ctx.cfg.summary_branches);
55 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 55 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
56 cgit_print_tags(ctx.cfg.summary_tags); 56 cgit_print_tags(ctx.cfg.summary_tags);
57 if (ctx.cfg.summary_log > 0) { 57 if (ctx.cfg.summary_log > 0) {
58 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 58 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
59 cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, 59 cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL,
60 NULL, NULL, 0); 60 NULL, NULL, 0);
61 } 61 }
62 if (ctx.repo->clone_url) 62 if (ctx.repo->clone_url)
63 print_urls(ctx.repo->clone_url, NULL); 63 print_urls(ctx.repo->clone_url, NULL);
64 else if (ctx.cfg.clone_prefix) 64 else if (ctx.cfg.clone_prefix)
65 print_urls(ctx.cfg.clone_prefix, ctx.repo->url); 65 print_urls(ctx.cfg.clone_prefix, ctx.repo->url);
66 html("</table>"); 66 html("</table>");
67} 67}
68 68
69void cgit_print_repo_readme() 69void cgit_print_repo_readme(char *path)
70{ 70{
71 if (ctx.repo->readme) { 71 char *slash, *tmp;
72 html("<div id='summary'>"); 72
73 html_include(ctx.repo->readme); 73 if (!ctx.repo->readme)
74 html("</div>"); 74 return;
75 } 75
76 if (path) {
77 slash = strrchr(ctx.repo->readme, '/');
78 if (!slash)
79 return;
80 tmp = xmalloc(slash - ctx.repo->readme + 1 + strlen(path) + 1);
81 strncpy(tmp, ctx.repo->readme, slash - ctx.repo->readme + 1);
82 strcpy(tmp + (slash - ctx.repo->readme + 1), path);
83 } else
84 tmp = ctx.repo->readme;
85 html("<div id='summary'>");
86 if (ctx.repo->about_filter)
87 cgit_open_filter(ctx.repo->about_filter);
88 html_include(tmp);
89 if (ctx.repo->about_filter)
90 cgit_close_filter(ctx.repo->about_filter);
91 html("</div>");
76} 92}
diff --git a/ui-summary.h b/ui-summary.h
index 3e13039..c01f560 100644
--- a/ui-summary.h
+++ b/ui-summary.h
@@ -1,7 +1,7 @@
1#ifndef UI_SUMMARY_H 1#ifndef UI_SUMMARY_H
2#define UI_SUMMARY_H 2#define UI_SUMMARY_H
3 3
4extern void cgit_print_summary(); 4extern void cgit_print_summary();
5extern void cgit_print_repo_readme(); 5extern void cgit_print_repo_readme(char *path);
6 6
7#endif /* UI_SUMMARY_H */ 7#endif /* UI_SUMMARY_H */