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