summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c23
-rw-r--r--cgit.h16
-rw-r--r--cgitrc.5.txt20
-rw-r--r--shared.c37
-rw-r--r--ui-commit.c8
-rw-r--r--ui-snapshot.c35
-rw-r--r--ui-tree.c18
7 files changed, 125 insertions, 32 deletions
diff --git a/cgit.c b/cgit.c
index aa1107a..dbec196 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,333 +1,356 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cache.h" 10#include "cache.h"
11#include "cmd.h" 11#include "cmd.h"
12#include "configfile.h" 12#include "configfile.h"
13#include "html.h" 13#include "html.h"
14#include "ui-shared.h" 14#include "ui-shared.h"
15#include "ui-stats.h" 15#include "ui-stats.h"
16#include "scan-tree.h" 16#include "scan-tree.h"
17 17
18const char *cgit_version = CGIT_VERSION; 18const char *cgit_version = CGIT_VERSION;
19 19
20void add_mimetype(const char *name, const char *value) 20void add_mimetype(const char *name, const char *value)
21{ 21{
22 struct string_list_item *item; 22 struct string_list_item *item;
23 23
24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); 24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
25 item->util = xstrdup(value); 25 item->util = xstrdup(value);
26} 26}
27 27
28struct cgit_filter *new_filter(const char *cmd, int extra_args)
29{
30 struct cgit_filter *f;
31
32 if (!cmd || !cmd[0])
33 return NULL;
34
35 f = xmalloc(sizeof(struct cgit_filter));
36 f->cmd = xstrdup(cmd);
37 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
38 f->argv[0] = f->cmd;
39 f->argv[1] = NULL;
40 return f;
41}
42
28void config_cb(const char *name, const char *value) 43void config_cb(const char *name, const char *value)
29{ 44{
30 if (!strcmp(name, "root-title")) 45 if (!strcmp(name, "root-title"))
31 ctx.cfg.root_title = xstrdup(value); 46 ctx.cfg.root_title = xstrdup(value);
32 else if (!strcmp(name, "root-desc")) 47 else if (!strcmp(name, "root-desc"))
33 ctx.cfg.root_desc = xstrdup(value); 48 ctx.cfg.root_desc = xstrdup(value);
34 else if (!strcmp(name, "root-readme")) 49 else if (!strcmp(name, "root-readme"))
35 ctx.cfg.root_readme = xstrdup(value); 50 ctx.cfg.root_readme = xstrdup(value);
36 else if (!strcmp(name, "css")) 51 else if (!strcmp(name, "css"))
37 ctx.cfg.css = xstrdup(value); 52 ctx.cfg.css = xstrdup(value);
38 else if (!strcmp(name, "favicon")) 53 else if (!strcmp(name, "favicon"))
39 ctx.cfg.favicon = xstrdup(value); 54 ctx.cfg.favicon = xstrdup(value);
40 else if (!strcmp(name, "footer")) 55 else if (!strcmp(name, "footer"))
41 ctx.cfg.footer = xstrdup(value); 56 ctx.cfg.footer = xstrdup(value);
42 else if (!strcmp(name, "head-include")) 57 else if (!strcmp(name, "head-include"))
43 ctx.cfg.head_include = xstrdup(value); 58 ctx.cfg.head_include = xstrdup(value);
44 else if (!strcmp(name, "header")) 59 else if (!strcmp(name, "header"))
45 ctx.cfg.header = xstrdup(value); 60 ctx.cfg.header = xstrdup(value);
46 else if (!strcmp(name, "logo")) 61 else if (!strcmp(name, "logo"))
47 ctx.cfg.logo = xstrdup(value); 62 ctx.cfg.logo = xstrdup(value);
48 else if (!strcmp(name, "index-header")) 63 else if (!strcmp(name, "index-header"))
49 ctx.cfg.index_header = xstrdup(value); 64 ctx.cfg.index_header = xstrdup(value);
50 else if (!strcmp(name, "index-info")) 65 else if (!strcmp(name, "index-info"))
51 ctx.cfg.index_info = xstrdup(value); 66 ctx.cfg.index_info = xstrdup(value);
52 else if (!strcmp(name, "logo-link")) 67 else if (!strcmp(name, "logo-link"))
53 ctx.cfg.logo_link = xstrdup(value); 68 ctx.cfg.logo_link = xstrdup(value);
54 else if (!strcmp(name, "module-link")) 69 else if (!strcmp(name, "module-link"))
55 ctx.cfg.module_link = xstrdup(value); 70 ctx.cfg.module_link = xstrdup(value);
56 else if (!strcmp(name, "virtual-root")) { 71 else if (!strcmp(name, "virtual-root")) {
57 ctx.cfg.virtual_root = trim_end(value, '/'); 72 ctx.cfg.virtual_root = trim_end(value, '/');
58 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 73 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
59 ctx.cfg.virtual_root = ""; 74 ctx.cfg.virtual_root = "";
60 } else if (!strcmp(name, "nocache")) 75 } else if (!strcmp(name, "nocache"))
61 ctx.cfg.nocache = atoi(value); 76 ctx.cfg.nocache = atoi(value);
62 else if (!strcmp(name, "noplainemail")) 77 else if (!strcmp(name, "noplainemail"))
63 ctx.cfg.noplainemail = atoi(value); 78 ctx.cfg.noplainemail = atoi(value);
64 else if (!strcmp(name, "noheader")) 79 else if (!strcmp(name, "noheader"))
65 ctx.cfg.noheader = atoi(value); 80 ctx.cfg.noheader = atoi(value);
66 else if (!strcmp(name, "snapshots")) 81 else if (!strcmp(name, "snapshots"))
67 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 82 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
68 else if (!strcmp(name, "enable-index-links")) 83 else if (!strcmp(name, "enable-index-links"))
69 ctx.cfg.enable_index_links = atoi(value); 84 ctx.cfg.enable_index_links = atoi(value);
70 else if (!strcmp(name, "enable-log-filecount")) 85 else if (!strcmp(name, "enable-log-filecount"))
71 ctx.cfg.enable_log_filecount = atoi(value); 86 ctx.cfg.enable_log_filecount = atoi(value);
72 else if (!strcmp(name, "enable-log-linecount")) 87 else if (!strcmp(name, "enable-log-linecount"))
73 ctx.cfg.enable_log_linecount = atoi(value); 88 ctx.cfg.enable_log_linecount = atoi(value);
74 else if (!strcmp(name, "max-stats")) 89 else if (!strcmp(name, "max-stats"))
75 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 90 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
76 else if (!strcmp(name, "cache-size")) 91 else if (!strcmp(name, "cache-size"))
77 ctx.cfg.cache_size = atoi(value); 92 ctx.cfg.cache_size = atoi(value);
78 else if (!strcmp(name, "cache-root")) 93 else if (!strcmp(name, "cache-root"))
79 ctx.cfg.cache_root = xstrdup(value); 94 ctx.cfg.cache_root = xstrdup(value);
80 else if (!strcmp(name, "cache-root-ttl")) 95 else if (!strcmp(name, "cache-root-ttl"))
81 ctx.cfg.cache_root_ttl = atoi(value); 96 ctx.cfg.cache_root_ttl = atoi(value);
82 else if (!strcmp(name, "cache-repo-ttl")) 97 else if (!strcmp(name, "cache-repo-ttl"))
83 ctx.cfg.cache_repo_ttl = atoi(value); 98 ctx.cfg.cache_repo_ttl = atoi(value);
84 else if (!strcmp(name, "cache-static-ttl")) 99 else if (!strcmp(name, "cache-static-ttl"))
85 ctx.cfg.cache_static_ttl = atoi(value); 100 ctx.cfg.cache_static_ttl = atoi(value);
86 else if (!strcmp(name, "cache-dynamic-ttl")) 101 else if (!strcmp(name, "cache-dynamic-ttl"))
87 ctx.cfg.cache_dynamic_ttl = atoi(value); 102 ctx.cfg.cache_dynamic_ttl = atoi(value);
103 else if (!strcmp(name, "commit-filter"))
104 ctx.cfg.commit_filter = new_filter(value, 0);
88 else if (!strcmp(name, "embedded")) 105 else if (!strcmp(name, "embedded"))
89 ctx.cfg.embedded = atoi(value); 106 ctx.cfg.embedded = atoi(value);
90 else if (!strcmp(name, "max-message-length")) 107 else if (!strcmp(name, "max-message-length"))
91 ctx.cfg.max_msg_len = atoi(value); 108 ctx.cfg.max_msg_len = atoi(value);
92 else if (!strcmp(name, "max-repodesc-length")) 109 else if (!strcmp(name, "max-repodesc-length"))
93 ctx.cfg.max_repodesc_len = atoi(value); 110 ctx.cfg.max_repodesc_len = atoi(value);
94 else if (!strcmp(name, "max-repo-count")) 111 else if (!strcmp(name, "max-repo-count"))
95 ctx.cfg.max_repo_count = atoi(value); 112 ctx.cfg.max_repo_count = atoi(value);
96 else if (!strcmp(name, "max-commit-count")) 113 else if (!strcmp(name, "max-commit-count"))
97 ctx.cfg.max_commit_count = atoi(value); 114 ctx.cfg.max_commit_count = atoi(value);
115 else if (!strcmp(name, "source-filter"))
116 ctx.cfg.source_filter = new_filter(value, 1);
98 else if (!strcmp(name, "summary-log")) 117 else if (!strcmp(name, "summary-log"))
99 ctx.cfg.summary_log = atoi(value); 118 ctx.cfg.summary_log = atoi(value);
100 else if (!strcmp(name, "summary-branches")) 119 else if (!strcmp(name, "summary-branches"))
101 ctx.cfg.summary_branches = atoi(value); 120 ctx.cfg.summary_branches = atoi(value);
102 else if (!strcmp(name, "summary-tags")) 121 else if (!strcmp(name, "summary-tags"))
103 ctx.cfg.summary_tags = atoi(value); 122 ctx.cfg.summary_tags = atoi(value);
104 else if (!strcmp(name, "agefile")) 123 else if (!strcmp(name, "agefile"))
105 ctx.cfg.agefile = xstrdup(value); 124 ctx.cfg.agefile = xstrdup(value);
106 else if (!strcmp(name, "renamelimit")) 125 else if (!strcmp(name, "renamelimit"))
107 ctx.cfg.renamelimit = atoi(value); 126 ctx.cfg.renamelimit = atoi(value);
108 else if (!strcmp(name, "robots")) 127 else if (!strcmp(name, "robots"))
109 ctx.cfg.robots = xstrdup(value); 128 ctx.cfg.robots = xstrdup(value);
110 else if (!strcmp(name, "clone-prefix")) 129 else if (!strcmp(name, "clone-prefix"))
111 ctx.cfg.clone_prefix = xstrdup(value); 130 ctx.cfg.clone_prefix = xstrdup(value);
112 else if (!strcmp(name, "local-time")) 131 else if (!strcmp(name, "local-time"))
113 ctx.cfg.local_time = atoi(value); 132 ctx.cfg.local_time = atoi(value);
114 else if (!prefixcmp(name, "mimetype.")) 133 else if (!prefixcmp(name, "mimetype."))
115 add_mimetype(name + 9, value); 134 add_mimetype(name + 9, value);
116 else if (!strcmp(name, "repo.group")) 135 else if (!strcmp(name, "repo.group"))
117 ctx.cfg.repo_group = xstrdup(value); 136 ctx.cfg.repo_group = xstrdup(value);
118 else if (!strcmp(name, "repo.url")) 137 else if (!strcmp(name, "repo.url"))
119 ctx.repo = cgit_add_repo(value); 138 ctx.repo = cgit_add_repo(value);
120 else if (!strcmp(name, "repo.name")) 139 else if (!strcmp(name, "repo.name"))
121 ctx.repo->name = xstrdup(value); 140 ctx.repo->name = xstrdup(value);
122 else if (ctx.repo && !strcmp(name, "repo.path")) 141 else if (ctx.repo && !strcmp(name, "repo.path"))
123 ctx.repo->path = trim_end(value, '/'); 142 ctx.repo->path = trim_end(value, '/');
124 else if (ctx.repo && !strcmp(name, "repo.clone-url")) 143 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
125 ctx.repo->clone_url = xstrdup(value); 144 ctx.repo->clone_url = xstrdup(value);
126 else if (ctx.repo && !strcmp(name, "repo.desc")) 145 else if (ctx.repo && !strcmp(name, "repo.desc"))
127 ctx.repo->desc = xstrdup(value); 146 ctx.repo->desc = xstrdup(value);
128 else if (ctx.repo && !strcmp(name, "repo.owner")) 147 else if (ctx.repo && !strcmp(name, "repo.owner"))
129 ctx.repo->owner = xstrdup(value); 148 ctx.repo->owner = xstrdup(value);
130 else if (ctx.repo && !strcmp(name, "repo.defbranch")) 149 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
131 ctx.repo->defbranch = xstrdup(value); 150 ctx.repo->defbranch = xstrdup(value);
132 else if (ctx.repo && !strcmp(name, "repo.snapshots")) 151 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
133 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ 152 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
134 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) 153 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
135 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 154 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
136 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) 155 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
137 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 156 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
138 else if (ctx.repo && !strcmp(name, "repo.max-stats")) 157 else if (ctx.repo && !strcmp(name, "repo.max-stats"))
139 ctx.repo->max_stats = cgit_find_stats_period(value, NULL); 158 ctx.repo->max_stats = cgit_find_stats_period(value, NULL);
140 else if (ctx.repo && !strcmp(name, "repo.module-link")) 159 else if (ctx.repo && !strcmp(name, "repo.module-link"))
141 ctx.repo->module_link= xstrdup(value); 160 ctx.repo->module_link= xstrdup(value);
161 else if (ctx.repo && !strcmp(name, "repo.commit-filter"))
162 ctx.repo->commit_filter = new_filter(value, 0);
163 else if (ctx.repo && !strcmp(name, "repo.source-filter"))
164 ctx.repo->source_filter = new_filter(value, 1);
142 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { 165 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
143 if (*value == '/') 166 if (*value == '/')
144 ctx.repo->readme = xstrdup(value); 167 ctx.repo->readme = xstrdup(value);
145 else 168 else
146 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 169 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
147 } else if (!strcmp(name, "include")) 170 } else if (!strcmp(name, "include"))
148 parse_configfile(value, config_cb); 171 parse_configfile(value, config_cb);
149} 172}
150 173
151static void querystring_cb(const char *name, const char *value) 174static void querystring_cb(const char *name, const char *value)
152{ 175{
153 if (!strcmp(name,"r")) { 176 if (!strcmp(name,"r")) {
154 ctx.qry.repo = xstrdup(value); 177 ctx.qry.repo = xstrdup(value);
155 ctx.repo = cgit_get_repoinfo(value); 178 ctx.repo = cgit_get_repoinfo(value);
156 } else if (!strcmp(name, "p")) { 179 } else if (!strcmp(name, "p")) {
157 ctx.qry.page = xstrdup(value); 180 ctx.qry.page = xstrdup(value);
158 } else if (!strcmp(name, "url")) { 181 } else if (!strcmp(name, "url")) {
159 ctx.qry.url = xstrdup(value); 182 ctx.qry.url = xstrdup(value);
160 cgit_parse_url(value); 183 cgit_parse_url(value);
161 } else if (!strcmp(name, "qt")) { 184 } else if (!strcmp(name, "qt")) {
162 ctx.qry.grep = xstrdup(value); 185 ctx.qry.grep = xstrdup(value);
163 } else if (!strcmp(name, "q")) { 186 } else if (!strcmp(name, "q")) {
164 ctx.qry.search = xstrdup(value); 187 ctx.qry.search = xstrdup(value);
165 } else if (!strcmp(name, "h")) { 188 } else if (!strcmp(name, "h")) {
166 ctx.qry.head = xstrdup(value); 189 ctx.qry.head = xstrdup(value);
167 ctx.qry.has_symref = 1; 190 ctx.qry.has_symref = 1;
168 } else if (!strcmp(name, "id")) { 191 } else if (!strcmp(name, "id")) {
169 ctx.qry.sha1 = xstrdup(value); 192 ctx.qry.sha1 = xstrdup(value);
170 ctx.qry.has_sha1 = 1; 193 ctx.qry.has_sha1 = 1;
171 } else if (!strcmp(name, "id2")) { 194 } else if (!strcmp(name, "id2")) {
172 ctx.qry.sha2 = xstrdup(value); 195 ctx.qry.sha2 = xstrdup(value);
173 ctx.qry.has_sha1 = 1; 196 ctx.qry.has_sha1 = 1;
174 } else if (!strcmp(name, "ofs")) { 197 } else if (!strcmp(name, "ofs")) {
175 ctx.qry.ofs = atoi(value); 198 ctx.qry.ofs = atoi(value);
176 } else if (!strcmp(name, "path")) { 199 } else if (!strcmp(name, "path")) {
177 ctx.qry.path = trim_end(value, '/'); 200 ctx.qry.path = trim_end(value, '/');
178 } else if (!strcmp(name, "name")) { 201 } else if (!strcmp(name, "name")) {
179 ctx.qry.name = xstrdup(value); 202 ctx.qry.name = xstrdup(value);
180 } else if (!strcmp(name, "mimetype")) { 203 } else if (!strcmp(name, "mimetype")) {
181 ctx.qry.mimetype = xstrdup(value); 204 ctx.qry.mimetype = xstrdup(value);
182 } else if (!strcmp(name, "s")){ 205 } else if (!strcmp(name, "s")){
183 ctx.qry.sort = xstrdup(value); 206 ctx.qry.sort = xstrdup(value);
184 } else if (!strcmp(name, "showmsg")) { 207 } else if (!strcmp(name, "showmsg")) {
185 ctx.qry.showmsg = atoi(value); 208 ctx.qry.showmsg = atoi(value);
186 } else if (!strcmp(name, "period")) { 209 } else if (!strcmp(name, "period")) {
187 ctx.qry.period = xstrdup(value); 210 ctx.qry.period = xstrdup(value);
188 } 211 }
189} 212}
190 213
191static void prepare_context(struct cgit_context *ctx) 214static void prepare_context(struct cgit_context *ctx)
192{ 215{
193 memset(ctx, 0, sizeof(ctx)); 216 memset(ctx, 0, sizeof(ctx));
194 ctx->cfg.agefile = "info/web/last-modified"; 217 ctx->cfg.agefile = "info/web/last-modified";
195 ctx->cfg.nocache = 0; 218 ctx->cfg.nocache = 0;
196 ctx->cfg.cache_size = 0; 219 ctx->cfg.cache_size = 0;
197 ctx->cfg.cache_dynamic_ttl = 5; 220 ctx->cfg.cache_dynamic_ttl = 5;
198 ctx->cfg.cache_max_create_time = 5; 221 ctx->cfg.cache_max_create_time = 5;
199 ctx->cfg.cache_repo_ttl = 5; 222 ctx->cfg.cache_repo_ttl = 5;
200 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 223 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
201 ctx->cfg.cache_root_ttl = 5; 224 ctx->cfg.cache_root_ttl = 5;
202 ctx->cfg.cache_static_ttl = -1; 225 ctx->cfg.cache_static_ttl = -1;
203 ctx->cfg.css = "/cgit.css"; 226 ctx->cfg.css = "/cgit.css";
204 ctx->cfg.logo = "/git-logo.png"; 227 ctx->cfg.logo = "/git-logo.png";
205 ctx->cfg.local_time = 0; 228 ctx->cfg.local_time = 0;
206 ctx->cfg.max_repo_count = 50; 229 ctx->cfg.max_repo_count = 50;
207 ctx->cfg.max_commit_count = 50; 230 ctx->cfg.max_commit_count = 50;
208 ctx->cfg.max_lock_attempts = 5; 231 ctx->cfg.max_lock_attempts = 5;
209 ctx->cfg.max_msg_len = 80; 232 ctx->cfg.max_msg_len = 80;
210 ctx->cfg.max_repodesc_len = 80; 233 ctx->cfg.max_repodesc_len = 80;
211 ctx->cfg.max_stats = 0; 234 ctx->cfg.max_stats = 0;
212 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 235 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
213 ctx->cfg.renamelimit = -1; 236 ctx->cfg.renamelimit = -1;
214 ctx->cfg.robots = "index, nofollow"; 237 ctx->cfg.robots = "index, nofollow";
215 ctx->cfg.root_title = "Git repository browser"; 238 ctx->cfg.root_title = "Git repository browser";
216 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 239 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
217 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 240 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
218 ctx->cfg.summary_branches = 10; 241 ctx->cfg.summary_branches = 10;
219 ctx->cfg.summary_log = 10; 242 ctx->cfg.summary_log = 10;
220 ctx->cfg.summary_tags = 10; 243 ctx->cfg.summary_tags = 10;
221 ctx->page.mimetype = "text/html"; 244 ctx->page.mimetype = "text/html";
222 ctx->page.charset = PAGE_ENCODING; 245 ctx->page.charset = PAGE_ENCODING;
223 ctx->page.filename = NULL; 246 ctx->page.filename = NULL;
224 ctx->page.size = 0; 247 ctx->page.size = 0;
225 ctx->page.modified = time(NULL); 248 ctx->page.modified = time(NULL);
226 ctx->page.expires = ctx->page.modified; 249 ctx->page.expires = ctx->page.modified;
227 ctx->page.etag = NULL; 250 ctx->page.etag = NULL;
228 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 251 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
229} 252}
230 253
231struct refmatch { 254struct refmatch {
232 char *req_ref; 255 char *req_ref;
233 char *first_ref; 256 char *first_ref;
234 int match; 257 int match;
235}; 258};
236 259
237int find_current_ref(const char *refname, const unsigned char *sha1, 260int find_current_ref(const char *refname, const unsigned char *sha1,
238 int flags, void *cb_data) 261 int flags, void *cb_data)
239{ 262{
240 struct refmatch *info; 263 struct refmatch *info;
241 264
242 info = (struct refmatch *)cb_data; 265 info = (struct refmatch *)cb_data;
243 if (!strcmp(refname, info->req_ref)) 266 if (!strcmp(refname, info->req_ref))
244 info->match = 1; 267 info->match = 1;
245 if (!info->first_ref) 268 if (!info->first_ref)
246 info->first_ref = xstrdup(refname); 269 info->first_ref = xstrdup(refname);
247 return info->match; 270 return info->match;
248} 271}
249 272
250char *find_default_branch(struct cgit_repo *repo) 273char *find_default_branch(struct cgit_repo *repo)
251{ 274{
252 struct refmatch info; 275 struct refmatch info;
253 char *ref; 276 char *ref;
254 277
255 info.req_ref = repo->defbranch; 278 info.req_ref = repo->defbranch;
256 info.first_ref = NULL; 279 info.first_ref = NULL;
257 info.match = 0; 280 info.match = 0;
258 for_each_branch_ref(find_current_ref, &info); 281 for_each_branch_ref(find_current_ref, &info);
259 if (info.match) 282 if (info.match)
260 ref = info.req_ref; 283 ref = info.req_ref;
261 else 284 else
262 ref = info.first_ref; 285 ref = info.first_ref;
263 if (ref) 286 if (ref)
264 ref = xstrdup(ref); 287 ref = xstrdup(ref);
265 return ref; 288 return ref;
266} 289}
267 290
268static int prepare_repo_cmd(struct cgit_context *ctx) 291static int prepare_repo_cmd(struct cgit_context *ctx)
269{ 292{
270 char *tmp; 293 char *tmp;
271 unsigned char sha1[20]; 294 unsigned char sha1[20];
272 int nongit = 0; 295 int nongit = 0;
273 296
274 setenv("GIT_DIR", ctx->repo->path, 1); 297 setenv("GIT_DIR", ctx->repo->path, 1);
275 setup_git_directory_gently(&nongit); 298 setup_git_directory_gently(&nongit);
276 if (nongit) { 299 if (nongit) {
277 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 300 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
278 "config error"); 301 "config error");
279 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 302 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
280 ctx->repo = NULL; 303 ctx->repo = NULL;
281 cgit_print_http_headers(ctx); 304 cgit_print_http_headers(ctx);
282 cgit_print_docstart(ctx); 305 cgit_print_docstart(ctx);
283 cgit_print_pageheader(ctx); 306 cgit_print_pageheader(ctx);
284 cgit_print_error(tmp); 307 cgit_print_error(tmp);
285 cgit_print_docend(); 308 cgit_print_docend();
286 return 1; 309 return 1;
287 } 310 }
288 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 311 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
289 312
290 if (!ctx->qry.head) { 313 if (!ctx->qry.head) {
291 ctx->qry.nohead = 1; 314 ctx->qry.nohead = 1;
292 ctx->qry.head = find_default_branch(ctx->repo); 315 ctx->qry.head = find_default_branch(ctx->repo);
293 ctx->repo->defbranch = ctx->qry.head; 316 ctx->repo->defbranch = ctx->qry.head;
294 } 317 }
295 318
296 if (!ctx->qry.head) { 319 if (!ctx->qry.head) {
297 cgit_print_http_headers(ctx); 320 cgit_print_http_headers(ctx);
298 cgit_print_docstart(ctx); 321 cgit_print_docstart(ctx);
299 cgit_print_pageheader(ctx); 322 cgit_print_pageheader(ctx);
300 cgit_print_error("Repository seems to be empty"); 323 cgit_print_error("Repository seems to be empty");
301 cgit_print_docend(); 324 cgit_print_docend();
302 return 1; 325 return 1;
303 } 326 }
304 327
305 if (get_sha1(ctx->qry.head, sha1)) { 328 if (get_sha1(ctx->qry.head, sha1)) {
306 tmp = xstrdup(ctx->qry.head); 329 tmp = xstrdup(ctx->qry.head);
307 ctx->qry.head = ctx->repo->defbranch; 330 ctx->qry.head = ctx->repo->defbranch;
308 ctx->page.status = 404; 331 ctx->page.status = 404;
309 ctx->page.statusmsg = "not found"; 332 ctx->page.statusmsg = "not found";
310 cgit_print_http_headers(ctx); 333 cgit_print_http_headers(ctx);
311 cgit_print_docstart(ctx); 334 cgit_print_docstart(ctx);
312 cgit_print_pageheader(ctx); 335 cgit_print_pageheader(ctx);
313 cgit_print_error(fmt("Invalid branch: %s", tmp)); 336 cgit_print_error(fmt("Invalid branch: %s", tmp));
314 cgit_print_docend(); 337 cgit_print_docend();
315 return 1; 338 return 1;
316 } 339 }
317 return 0; 340 return 0;
318} 341}
319 342
320static void process_request(void *cbdata) 343static void process_request(void *cbdata)
321{ 344{
322 struct cgit_context *ctx = cbdata; 345 struct cgit_context *ctx = cbdata;
323 struct cgit_cmd *cmd; 346 struct cgit_cmd *cmd;
324 347
325 cmd = cgit_get_cmd(ctx); 348 cmd = cgit_get_cmd(ctx);
326 if (!cmd) { 349 if (!cmd) {
327 ctx->page.title = "cgit error"; 350 ctx->page.title = "cgit error";
328 cgit_print_http_headers(ctx); 351 cgit_print_http_headers(ctx);
329 cgit_print_docstart(ctx); 352 cgit_print_docstart(ctx);
330 cgit_print_pageheader(ctx); 353 cgit_print_pageheader(ctx);
331 cgit_print_error("Invalid request"); 354 cgit_print_error("Invalid request");
332 cgit_print_docend(); 355 cgit_print_docend();
333 return; 356 return;
diff --git a/cgit.h b/cgit.h
index 1194eb0..b8557ac 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,255 +1,271 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22 22
23 23
24/* 24/*
25 * Dateformats used on misc. pages 25 * Dateformats used on misc. pages
26 */ 26 */
27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
28#define FMT_SHORTDATE "%Y-%m-%d" 28#define FMT_SHORTDATE "%Y-%m-%d"
29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
30 30
31 31
32/* 32/*
33 * Limits used for relative dates 33 * Limits used for relative dates
34 */ 34 */
35#define TM_MIN 60 35#define TM_MIN 60
36#define TM_HOUR (TM_MIN * 60) 36#define TM_HOUR (TM_MIN * 60)
37#define TM_DAY (TM_HOUR * 24) 37#define TM_DAY (TM_HOUR * 24)
38#define TM_WEEK (TM_DAY * 7) 38#define TM_WEEK (TM_DAY * 7)
39#define TM_YEAR (TM_DAY * 365) 39#define TM_YEAR (TM_DAY * 365)
40#define TM_MONTH (TM_YEAR / 12.0) 40#define TM_MONTH (TM_YEAR / 12.0)
41 41
42 42
43/* 43/*
44 * Default encoding 44 * Default encoding
45 */ 45 */
46#define PAGE_ENCODING "UTF-8" 46#define PAGE_ENCODING "UTF-8"
47 47
48typedef void (*configfn)(const char *name, const char *value); 48typedef void (*configfn)(const char *name, const char *value);
49typedef void (*filepair_fn)(struct diff_filepair *pair); 49typedef void (*filepair_fn)(struct diff_filepair *pair);
50typedef void (*linediff_fn)(char *line, int len); 50typedef void (*linediff_fn)(char *line, int len);
51 51
52struct cgit_filter {
53 char *cmd;
54 char **argv;
55 int old_stdout;
56 int pipe_fh[2];
57 int pid;
58 int exitstatus;
59};
60
52struct cgit_repo { 61struct cgit_repo {
53 char *url; 62 char *url;
54 char *name; 63 char *name;
55 char *path; 64 char *path;
56 char *desc; 65 char *desc;
57 char *owner; 66 char *owner;
58 char *defbranch; 67 char *defbranch;
59 char *group; 68 char *group;
60 char *module_link; 69 char *module_link;
61 char *readme; 70 char *readme;
62 char *clone_url; 71 char *clone_url;
63 int snapshots; 72 int snapshots;
64 int enable_log_filecount; 73 int enable_log_filecount;
65 int enable_log_linecount; 74 int enable_log_linecount;
66 int max_stats; 75 int max_stats;
67 time_t mtime; 76 time_t mtime;
77 struct cgit_filter *commit_filter;
78 struct cgit_filter *source_filter;
68}; 79};
69 80
70struct cgit_repolist { 81struct cgit_repolist {
71 int length; 82 int length;
72 int count; 83 int count;
73 struct cgit_repo *repos; 84 struct cgit_repo *repos;
74}; 85};
75 86
76struct commitinfo { 87struct commitinfo {
77 struct commit *commit; 88 struct commit *commit;
78 char *author; 89 char *author;
79 char *author_email; 90 char *author_email;
80 unsigned long author_date; 91 unsigned long author_date;
81 char *committer; 92 char *committer;
82 char *committer_email; 93 char *committer_email;
83 unsigned long committer_date; 94 unsigned long committer_date;
84 char *subject; 95 char *subject;
85 char *msg; 96 char *msg;
86 char *msg_encoding; 97 char *msg_encoding;
87}; 98};
88 99
89struct taginfo { 100struct taginfo {
90 char *tagger; 101 char *tagger;
91 char *tagger_email; 102 char *tagger_email;
92 unsigned long tagger_date; 103 unsigned long tagger_date;
93 char *msg; 104 char *msg;
94}; 105};
95 106
96struct refinfo { 107struct refinfo {
97 const char *refname; 108 const char *refname;
98 struct object *object; 109 struct object *object;
99 union { 110 union {
100 struct taginfo *tag; 111 struct taginfo *tag;
101 struct commitinfo *commit; 112 struct commitinfo *commit;
102 }; 113 };
103}; 114};
104 115
105struct reflist { 116struct reflist {
106 struct refinfo **refs; 117 struct refinfo **refs;
107 int alloc; 118 int alloc;
108 int count; 119 int count;
109}; 120};
110 121
111struct cgit_query { 122struct cgit_query {
112 int has_symref; 123 int has_symref;
113 int has_sha1; 124 int has_sha1;
114 char *raw; 125 char *raw;
115 char *repo; 126 char *repo;
116 char *page; 127 char *page;
117 char *search; 128 char *search;
118 char *grep; 129 char *grep;
119 char *head; 130 char *head;
120 char *sha1; 131 char *sha1;
121 char *sha2; 132 char *sha2;
122 char *path; 133 char *path;
123 char *name; 134 char *name;
124 char *mimetype; 135 char *mimetype;
125 char *url; 136 char *url;
126 char *period; 137 char *period;
127 int ofs; 138 int ofs;
128 int nohead; 139 int nohead;
129 char *sort; 140 char *sort;
130 int showmsg; 141 int showmsg;
131}; 142};
132 143
133struct cgit_config { 144struct cgit_config {
134 char *agefile; 145 char *agefile;
135 char *cache_root; 146 char *cache_root;
136 char *clone_prefix; 147 char *clone_prefix;
137 char *css; 148 char *css;
138 char *favicon; 149 char *favicon;
139 char *footer; 150 char *footer;
140 char *head_include; 151 char *head_include;
141 char *header; 152 char *header;
142 char *index_header; 153 char *index_header;
143 char *index_info; 154 char *index_info;
144 char *logo; 155 char *logo;
145 char *logo_link; 156 char *logo_link;
146 char *module_link; 157 char *module_link;
147 char *repo_group; 158 char *repo_group;
148 char *robots; 159 char *robots;
149 char *root_title; 160 char *root_title;
150 char *root_desc; 161 char *root_desc;
151 char *root_readme; 162 char *root_readme;
152 char *script_name; 163 char *script_name;
153 char *virtual_root; 164 char *virtual_root;
154 int cache_size; 165 int cache_size;
155 int cache_dynamic_ttl; 166 int cache_dynamic_ttl;
156 int cache_max_create_time; 167 int cache_max_create_time;
157 int cache_repo_ttl; 168 int cache_repo_ttl;
158 int cache_root_ttl; 169 int cache_root_ttl;
159 int cache_static_ttl; 170 int cache_static_ttl;
160 int embedded; 171 int embedded;
161 int enable_index_links; 172 int enable_index_links;
162 int enable_log_filecount; 173 int enable_log_filecount;
163 int enable_log_linecount; 174 int enable_log_linecount;
164 int local_time; 175 int local_time;
165 int max_repo_count; 176 int max_repo_count;
166 int max_commit_count; 177 int max_commit_count;
167 int max_lock_attempts; 178 int max_lock_attempts;
168 int max_msg_len; 179 int max_msg_len;
169 int max_repodesc_len; 180 int max_repodesc_len;
170 int max_stats; 181 int max_stats;
171 int nocache; 182 int nocache;
172 int noplainemail; 183 int noplainemail;
173 int noheader; 184 int noheader;
174 int renamelimit; 185 int renamelimit;
175 int snapshots; 186 int snapshots;
176 int summary_branches; 187 int summary_branches;
177 int summary_log; 188 int summary_log;
178 int summary_tags; 189 int summary_tags;
179 struct string_list mimetypes; 190 struct string_list mimetypes;
191 struct cgit_filter *commit_filter;
192 struct cgit_filter *source_filter;
180}; 193};
181 194
182struct cgit_page { 195struct cgit_page {
183 time_t modified; 196 time_t modified;
184 time_t expires; 197 time_t expires;
185 size_t size; 198 size_t size;
186 char *mimetype; 199 char *mimetype;
187 char *charset; 200 char *charset;
188 char *filename; 201 char *filename;
189 char *etag; 202 char *etag;
190 char *title; 203 char *title;
191 int status; 204 int status;
192 char *statusmsg; 205 char *statusmsg;
193}; 206};
194 207
195struct cgit_context { 208struct cgit_context {
196 struct cgit_query qry; 209 struct cgit_query qry;
197 struct cgit_config cfg; 210 struct cgit_config cfg;
198 struct cgit_repo *repo; 211 struct cgit_repo *repo;
199 struct cgit_page page; 212 struct cgit_page page;
200}; 213};
201 214
202struct cgit_snapshot_format { 215struct cgit_snapshot_format {
203 const char *suffix; 216 const char *suffix;
204 const char *mimetype; 217 const char *mimetype;
205 write_archive_fn_t write_func; 218 write_archive_fn_t write_func;
206 int bit; 219 int bit;
207}; 220};
208 221
209extern const char *cgit_version; 222extern const char *cgit_version;
210 223
211extern struct cgit_repolist cgit_repolist; 224extern struct cgit_repolist cgit_repolist;
212extern struct cgit_context ctx; 225extern struct cgit_context ctx;
213extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 226extern const struct cgit_snapshot_format cgit_snapshot_formats[];
214 227
215extern struct cgit_repo *cgit_add_repo(const char *url); 228extern struct cgit_repo *cgit_add_repo(const char *url);
216extern struct cgit_repo *cgit_get_repoinfo(const char *url); 229extern struct cgit_repo *cgit_get_repoinfo(const char *url);
217extern void cgit_repo_config_cb(const char *name, const char *value); 230extern void cgit_repo_config_cb(const char *name, const char *value);
218 231
219extern int chk_zero(int result, char *msg); 232extern int chk_zero(int result, char *msg);
220extern int chk_positive(int result, char *msg); 233extern int chk_positive(int result, char *msg);
221extern int chk_non_negative(int result, char *msg); 234extern int chk_non_negative(int result, char *msg);
222 235
223extern char *trim_end(const char *str, char c); 236extern char *trim_end(const char *str, char c);
224extern char *strlpart(char *txt, int maxlen); 237extern char *strlpart(char *txt, int maxlen);
225extern char *strrpart(char *txt, int maxlen); 238extern char *strrpart(char *txt, int maxlen);
226 239
227extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 240extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
228extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 241extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
229 int flags, void *cb_data); 242 int flags, void *cb_data);
230 243
231extern void *cgit_free_commitinfo(struct commitinfo *info); 244extern void *cgit_free_commitinfo(struct commitinfo *info);
232 245
233extern int cgit_diff_files(const unsigned char *old_sha1, 246extern int cgit_diff_files(const unsigned char *old_sha1,
234 const unsigned char *new_sha1, 247 const unsigned char *new_sha1,
235 unsigned long *old_size, unsigned long *new_size, 248 unsigned long *old_size, unsigned long *new_size,
236 int *binary, linediff_fn fn); 249 int *binary, linediff_fn fn);
237 250
238extern void cgit_diff_tree(const unsigned char *old_sha1, 251extern void cgit_diff_tree(const unsigned char *old_sha1,
239 const unsigned char *new_sha1, 252 const unsigned char *new_sha1,
240 filepair_fn fn, const char *prefix); 253 filepair_fn fn, const char *prefix);
241 254
242extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 255extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
243 256
244extern char *fmt(const char *format,...); 257extern char *fmt(const char *format,...);
245 258
246extern struct commitinfo *cgit_parse_commit(struct commit *commit); 259extern struct commitinfo *cgit_parse_commit(struct commit *commit);
247extern struct taginfo *cgit_parse_tag(struct tag *tag); 260extern struct taginfo *cgit_parse_tag(struct tag *tag);
248extern void cgit_parse_url(const char *url); 261extern void cgit_parse_url(const char *url);
249 262
250extern const char *cgit_repobasename(const char *reponame); 263extern const char *cgit_repobasename(const char *reponame);
251 264
252extern int cgit_parse_snapshots_mask(const char *str); 265extern int cgit_parse_snapshots_mask(const char *str);
253 266
267extern int cgit_open_filter(struct cgit_filter *filter);
268extern int cgit_close_filter(struct cgit_filter *filter);
269
254 270
255#endif /* CGIT_H */ 271#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 0412f64..dc63637 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,424 +1,444 @@
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::
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
61 command will be included verbatim as the commit message, i.e. this can
62 be used to implement bugtracker integration. Default value: none.
63
58css:: 64css::
59 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.
60 Default value: "/cgit.css". 66 Default value: "/cgit.css".
61 67
62embedded:: 68embedded::
63 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
64 suitable for embedding in other html pages. Default value: none. See 70 suitable for embedding in other html pages. Default value: none. See
65 also: "noheader". 71 also: "noheader".
66 72
67enable-index-links:: 73enable-index-links::
68 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
69 each repo in the repository index (specifically, to the "summary", 75 each repo in the repository index (specifically, to the "summary",
70 "commit" and "tree" pages). Default value: "0". 76 "commit" and "tree" pages). Default value: "0".
71 77
72enable-log-filecount:: 78enable-log-filecount::
73 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
74 modified files for each commit on the repository log page. Default 80 modified files for each commit on the repository log page. Default
75 value: "0". 81 value: "0".
76 82
77enable-log-linecount:: 83enable-log-linecount::
78 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
79 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
80 value: "0". 86 value: "0".
81 87
82favicon:: 88favicon::
83 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
84 suggested to use the value "/favicon.ico" since certain browsers will 90 suggested to use the value "/favicon.ico" since certain browsers will
85 ignore other values. Default value: none. 91 ignore other values. Default value: none.
86 92
87footer:: 93footer::
88 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
89 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
90 "generated by..." message. Default value: none. 96 "generated by..." message. Default value: none.
91 97
92head-include:: 98head-include::
93 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
94 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.
95 101
96header:: 102header::
97 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
98 verbatim at the top of all pages. Default value: none. 104 verbatim at the top of all pages. Default value: none.
99 105
100include:: 106include::
101 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-
102 file is parsed. Default value: none. 108 file is parsed. Default value: none.
103 109
104index-header:: 110index-header::
105 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
106 verbatim above the repository index. This setting is deprecated, and 112 verbatim above the repository index. This setting is deprecated, and
107 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
108 value: none. 114 value: none.
109 115
110index-info:: 116index-info::
111 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
112 verbatim below the heading on the repository index page. This setting 118 verbatim below the heading on the repository index page. This setting
113 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
114 instead). Default value: none. 120 instead). Default value: none.
115 121
116local-time:: 122local-time::
117 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
118 servers timezone. Default value: "0". 124 servers timezone. Default value: "0".
119 125
120logo:: 126logo::
121 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
122 on all cgit pages. 128 on all cgit pages.
123 129
124logo-link:: 130logo-link::
125 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
126 calculated url of the repository index page will be used. Default 132 calculated url of the repository index page will be used. Default
127 value: none. 133 value: none.
128 134
129max-commit-count:: 135max-commit-count::
130 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
131 value: "50". 137 value: "50".
132 138
133max-message-length:: 139max-message-length::
134 Specifies the maximum number of commit message characters to display in 140 Specifies the maximum number of commit message characters to display in
135 "log" view. Default value: "80". 141 "log" view. Default value: "80".
136 142
137max-repo-count:: 143max-repo-count::
138 Specifies the number of entries to list per page on therepository 144 Specifies the number of entries to list per page on therepository
139 index page. Default value: "50". 145 index page. Default value: "50".
140 146
141max-repodesc-length:: 147max-repodesc-length::
142 Specifies the maximum number of repo description characters to display 148 Specifies the maximum number of repo description characters to display
143 on the repository index page. Default value: "80". 149 on the repository index page. Default value: "80".
144 150
145max-stats:: 151max-stats::
146 Set the default maximum statistics period. Valid values are "week", 152 Set the default maximum statistics period. Valid values are "week",
147 "month", "quarter" and "year". If unspecified, statistics are 153 "month", "quarter" and "year". If unspecified, statistics are
148 disabled. Default value: none. See also: "repo.max-stats". 154 disabled. Default value: none. See also: "repo.max-stats".
149 155
150mimetype.<ext>:: 156mimetype.<ext>::
151 Set the mimetype for the specified filename extension. This is used 157 Set the mimetype for the specified filename extension. This is used
152 by the `plain` command when returning blob content. 158 by the `plain` command when returning blob content.
153 159
154module-link:: 160module-link::
155 Text which will be used as the formatstring for a hyperlink when a 161 Text which will be used as the formatstring for a hyperlink when a
156 submodule is printed in a directory listing. The arguments for the 162 submodule is printed in a directory listing. The arguments for the
157 formatstring are the path and SHA1 of the submodule commit. Default 163 formatstring are the path and SHA1 of the submodule commit. Default
158 value: "./?repo=%s&page=commit&id=%s" 164 value: "./?repo=%s&page=commit&id=%s"
159 165
160nocache:: 166nocache::
161 If set to the value "1" caching will be disabled. This settings is 167 If set to the value "1" caching will be disabled. This settings is
162 deprecated, and will not be honored starting with cgit-1.0. Default 168 deprecated, and will not be honored starting with cgit-1.0. Default
163 value: "0". 169 value: "0".
164 170
165noplainemail:: 171noplainemail::
166 If set to "1" showing full author email adresses will be disabled. 172 If set to "1" showing full author email adresses will be disabled.
167 Default value: "0". 173 Default value: "0".
168 174
169noheader:: 175noheader::
170 Flag which, when set to "1", will make cgit omit the standard header 176 Flag which, when set to "1", will make cgit omit the standard header
171 on all pages. Default value: none. See also: "embedded". 177 on all pages. Default value: none. See also: "embedded".
172 178
173renamelimit:: 179renamelimit::
174 Maximum number of files to consider when detecting renames. The value 180 Maximum number of files to consider when detecting renames. The value
175 "-1" uses the compiletime value in git (for further info, look at 181 "-1" uses the compiletime value in git (for further info, look at
176 `man git-diff`). Default value: "-1". 182 `man git-diff`). Default value: "-1".
177 183
178repo.group:: 184repo.group::
179 A value for the current repository group, which all repositories 185 A value for the current repository group, which all repositories
180 specified after this setting will inherit. Default value: none. 186 specified after this setting will inherit. Default value: none.
181 187
182robots:: 188robots::
183 Text used as content for the "robots" meta-tag. Default value: 189 Text used as content for the "robots" meta-tag. Default value:
184 "index, nofollow". 190 "index, nofollow".
185 191
186root-desc:: 192root-desc::
187 Text printed below the heading on the repository index page. Default 193 Text printed below the heading on the repository index page. Default
188 value: "a fast webinterface for the git dscm". 194 value: "a fast webinterface for the git dscm".
189 195
190root-readme:: 196root-readme::
191 The content of the file specified with this option will be included 197 The content of the file specified with this option will be included
192 verbatim below the "about" link on the repository index page. Default 198 verbatim below the "about" link on the repository index page. Default
193 value: none. 199 value: none.
194 200
195root-title:: 201root-title::
196 Text printed as heading on the repository index page. Default value: 202 Text printed as heading on the repository index page. Default value:
197 "Git Repository Browser". 203 "Git Repository Browser".
198 204
199snapshots:: 205snapshots::
200 Text which specifies the default (and allowed) set of snapshot formats 206 Text which specifies the default (and allowed) set of snapshot formats
201 supported by cgit. The value is a space-separated list of zero or more 207 supported by cgit. The value is a space-separated list of zero or more
202 of the following values: 208 of the following values:
203 "tar" uncompressed tar-file 209 "tar" uncompressed tar-file
204 "tar.gz"gzip-compressed tar-file 210 "tar.gz"gzip-compressed tar-file
205 "tar.bz2"bzip-compressed tar-file 211 "tar.bz2"bzip-compressed tar-file
206 "zip" zip-file 212 "zip" zip-file
207 Default value: none. 213 Default value: none.
208 214
215source-filter::
216 Specifies a command which will be invoked to format plaintext blobs
217 in the tree view. The command will get the blob content on its STDIN
218 and the name of the blob as its only command line argument. The STDOUT
219 from the command will be included verbatim as the blob contents, i.e.
220 this can be used to implement e.g. syntax highlighting. Default value:
221 none.
222
209summary-branches:: 223summary-branches::
210 Specifies the number of branches to display in the repository "summary" 224 Specifies the number of branches to display in the repository "summary"
211 view. Default value: "10". 225 view. Default value: "10".
212 226
213summary-log:: 227summary-log::
214 Specifies the number of log entries to display in the repository 228 Specifies the number of log entries to display in the repository
215 "summary" view. Default value: "10". 229 "summary" view. Default value: "10".
216 230
217summary-tags:: 231summary-tags::
218 Specifies the number of tags to display in the repository "summary" 232 Specifies the number of tags to display in the repository "summary"
219 view. Default value: "10". 233 view. Default value: "10".
220 234
221virtual-root:: 235virtual-root::
222 Url which, if specified, will be used as root for all cgit links. It 236 Url which, if specified, will be used as root for all cgit links. It
223 will also cause cgit to generate 'virtual urls', i.e. urls like 237 will also cause cgit to generate 'virtual urls', i.e. urls like
224 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 238 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
225 value: none. 239 value: none.
226 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 240 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
227 same kind of virtual urls, so this option will probably be deprecated. 241 same kind of virtual urls, so this option will probably be deprecated.
228 242
229REPOSITORY SETTINGS 243REPOSITORY SETTINGS
230------------------- 244-------------------
231repo.clone-url:: 245repo.clone-url::
232 A list of space-separated urls which can be used to clone this repo. 246 A list of space-separated urls which can be used to clone this repo.
233 Default value: none. 247 Default value: none.
234 248
249repo.commit-filter::
250 Override the default commit-filter. Default value: <commit-filter>.
251
235repo.defbranch:: 252repo.defbranch::
236 The name of the default branch for this repository. If no such branch 253 The name of the default branch for this repository. If no such branch
237 exists in the repository, the first branch name (when sorted) is used 254 exists in the repository, the first branch name (when sorted) is used
238 as default instead. Default value: "master". 255 as default instead. Default value: "master".
239 256
240repo.desc:: 257repo.desc::
241 The value to show as repository description. Default value: none. 258 The value to show as repository description. Default value: none.
242 259
243repo.enable-log-filecount:: 260repo.enable-log-filecount::
244 A flag which can be used to disable the global setting 261 A flag which can be used to disable the global setting
245 `enable-log-filecount'. Default value: none. 262 `enable-log-filecount'. Default value: none.
246 263
247repo.enable-log-linecount:: 264repo.enable-log-linecount::
248 A flag which can be used to disable the global setting 265 A flag which can be used to disable the global setting
249 `enable-log-linecount'. Default value: none. 266 `enable-log-linecount'. Default value: none.
250 267
251repo.max-stats:: 268repo.max-stats::
252 Override the default maximum statistics period. Valid values are equal 269 Override the default maximum statistics period. Valid values are equal
253 to the values specified for the global "max-stats" setting. Default 270 to the values specified for the global "max-stats" setting. Default
254 value: none. 271 value: none.
255 272
256repo.name:: 273repo.name::
257 The value to show as repository name. Default value: <repo.url>. 274 The value to show as repository name. Default value: <repo.url>.
258 275
259repo.owner:: 276repo.owner::
260 A value used to identify the owner of the repository. Default value: 277 A value used to identify the owner of the repository. Default value:
261 none. 278 none.
262 279
263repo.path:: 280repo.path::
264 An absolute path to the repository directory. For non-bare repositories 281 An absolute path to the repository directory. For non-bare repositories
265 this is the .git-directory. Default value: none. 282 this is the .git-directory. Default value: none.
266 283
267repo.readme:: 284repo.readme::
268 A path (relative to <repo.path>) which specifies a file to include 285 A path (relative to <repo.path>) which specifies a file to include
269 verbatim as the "About" page for this repo. Default value: none. 286 verbatim as the "About" page for this repo. Default value: none.
270 287
271repo.snapshots:: 288repo.snapshots::
272 A mask of allowed snapshot-formats for this repo, restricted by the 289 A mask of allowed snapshot-formats for this repo, restricted by the
273 "snapshots" global setting. Default value: <snapshots>. 290 "snapshots" global setting. Default value: <snapshots>.
274 291
292repo.source-filter::
293 Override the default source-filter. Default value: <source-filter>.
294
275repo.url:: 295repo.url::
276 The relative url used to access the repository. This must be the first 296 The relative url used to access the repository. This must be the first
277 setting specified for each repo. Default value: none. 297 setting specified for each repo. Default value: none.
278 298
279 299
280EXAMPLE CGITRC FILE 300EXAMPLE CGITRC FILE
281------------------- 301-------------------
282 302
283.... 303....
284# Enable caching of up to 1000 output entriess 304# Enable caching of up to 1000 output entriess
285cache-size=1000 305cache-size=1000
286 306
287 307
288# Specify some default clone prefixes 308# Specify some default clone prefixes
289clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 309clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
290 310
291# Specify the css url 311# Specify the css url
292css=/css/cgit.css 312css=/css/cgit.css
293 313
294 314
295# Show extra links for each repository on the index page 315# Show extra links for each repository on the index page
296enable-index-links=1 316enable-index-links=1
297 317
298 318
299# Show number of affected files per commit on the log pages 319# Show number of affected files per commit on the log pages
300enable-log-filecount=1 320enable-log-filecount=1
301 321
302 322
303# Show number of added/removed lines per commit on the log pages 323# Show number of added/removed lines per commit on the log pages
304enable-log-linecount=1 324enable-log-linecount=1
305 325
306 326
307# Add a cgit favicon 327# Add a cgit favicon
308favicon=/favicon.ico 328favicon=/favicon.ico
309 329
310 330
311# Use a custom logo 331# Use a custom logo
312logo=/img/mylogo.png 332logo=/img/mylogo.png
313 333
314 334
315# Enable statistics per week, month and quarter 335# Enable statistics per week, month and quarter
316max-stats=quarter 336max-stats=quarter
317 337
318 338
319# Set the title and heading of the repository index page 339# Set the title and heading of the repository index page
320root-title=foobar.com git repositories 340root-title=foobar.com git repositories
321 341
322 342
323# Set a subheading for the repository index page 343# Set a subheading for the repository index page
324root-desc=tracking the foobar development 344root-desc=tracking the foobar development
325 345
326 346
327# Include some more info about foobar.com on the index page 347# Include some more info about foobar.com on the index page
328root-readme=/var/www/htdocs/about.html 348root-readme=/var/www/htdocs/about.html
329 349
330 350
331# Allow download of tar.gz, tar.bz2 and zip-files 351# Allow download of tar.gz, tar.bz2 and zip-files
332snapshots=tar.gz tar.bz2 zip 352snapshots=tar.gz tar.bz2 zip
333 353
334 354
335## 355##
336## List of common mimetypes 356## List of common mimetypes
337## 357##
338 358
339mimetype.git=image/git 359mimetype.git=image/git
340mimetype.html=text/html 360mimetype.html=text/html
341mimetype.jpg=image/jpeg 361mimetype.jpg=image/jpeg
342mimetype.jpeg=image/jpeg 362mimetype.jpeg=image/jpeg
343mimetype.pdf=application/pdf 363mimetype.pdf=application/pdf
344mimetype.png=image/png 364mimetype.png=image/png
345mimetype.svg=image/svg+xml 365mimetype.svg=image/svg+xml
346 366
347 367
348## 368##
349## List of repositories. 369## List of repositories.
350## PS: Any repositories listed when repo.group is unset will not be 370## PS: Any repositories listed when repo.group is unset will not be
351## displayed under a group heading 371## displayed under a group heading
352## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 372## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
353## and included like this: 373## and included like this:
354## include=/etc/cgitrepos 374## include=/etc/cgitrepos
355## 375##
356 376
357 377
358repo.url=foo 378repo.url=foo
359repo.path=/pub/git/foo.git 379repo.path=/pub/git/foo.git
360repo.desc=the master foo repository 380repo.desc=the master foo repository
361repo.owner=fooman@foobar.com 381repo.owner=fooman@foobar.com
362repo.readme=info/web/about.html 382repo.readme=info/web/about.html
363 383
364 384
365repo.url=bar 385repo.url=bar
366repo.path=/pub/git/bar.git 386repo.path=/pub/git/bar.git
367repo.desc=the bars for your foo 387repo.desc=the bars for your foo
368repo.owner=barman@foobar.com 388repo.owner=barman@foobar.com
369repo.readme=info/web/about.html 389repo.readme=info/web/about.html
370 390
371 391
372# The next repositories will be displayed under the 'extras' heading 392# The next repositories will be displayed under the 'extras' heading
373repo.group=extras 393repo.group=extras
374 394
375 395
376repo.url=baz 396repo.url=baz
377repo.path=/pub/git/baz.git 397repo.path=/pub/git/baz.git
378repo.desc=a set of extensions for bar users 398repo.desc=a set of extensions for bar users
379 399
380repo.url=wiz 400repo.url=wiz
381repo.path=/pub/git/wiz.git 401repo.path=/pub/git/wiz.git
382repo.desc=the wizard of foo 402repo.desc=the wizard of foo
383 403
384 404
385# Add some mirrored repositories 405# Add some mirrored repositories
386repo.group=mirrors 406repo.group=mirrors
387 407
388 408
389repo.url=git 409repo.url=git
390repo.path=/pub/git/git.git 410repo.path=/pub/git/git.git
391repo.desc=the dscm 411repo.desc=the dscm
392 412
393 413
394repo.url=linux 414repo.url=linux
395repo.path=/pub/git/linux.git 415repo.path=/pub/git/linux.git
396repo.desc=the kernel 416repo.desc=the kernel
397 417
398# Disable adhoc downloads of this repo 418# Disable adhoc downloads of this repo
399repo.snapshots=0 419repo.snapshots=0
400 420
401# Disable line-counts for this repo 421# Disable line-counts for this repo
402repo.enable-log-linecount=0 422repo.enable-log-linecount=0
403 423
404# Restrict the max statistics period for this repo 424# Restrict the max statistics period for this repo
405repo.max-stats=month 425repo.max-stats=month
406.... 426....
407 427
408 428
409BUGS 429BUGS
410---- 430----
411Comments currently cannot appear on the same line as a setting; the comment 431Comments currently cannot appear on the same line as a setting; the comment
412will be included as part of the value. E.g. this line: 432will be included as part of the value. E.g. this line:
413 433
414 robots=index # allow indexing 434 robots=index # allow indexing
415 435
416will generate the following html element: 436will generate the following html element:
417 437
418 <meta name='robots' content='index # allow indexing'/> 438 <meta name='robots' content='index # allow indexing'/>
419 439
420 440
421 441
422AUTHOR 442AUTHOR
423------ 443------
424Lars Hjemli <hjemli@gmail.com> 444Lars Hjemli <hjemli@gmail.com>
diff --git a/shared.c b/shared.c
index cce0af4..783604b 100644
--- a/shared.c
+++ b/shared.c
@@ -1,357 +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}
360
361int cgit_open_filter(struct cgit_filter *filter)
362{
363
364 filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
365 "Unable to duplicate STDOUT");
366 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess");
367 filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
368 if (filter->pid == 0) {
369 close(filter->pipe_fh[1]);
370 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO),
371 "Unable to use pipe as STDIN");
372 execvp(filter->cmd, filter->argv);
373 die("Unable to exec subprocess %s: %s (%d)", filter->cmd,
374 strerror(errno), errno);
375 }
376 close(filter->pipe_fh[0]);
377 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO),
378 "Unable to use pipe as STDOUT");
379 close(filter->pipe_fh[1]);
380 return 0;
381}
382
383int cgit_close_filter(struct cgit_filter *filter)
384{
385 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
386 "Unable to restore STDOUT");
387 close(filter->old_stdout);
388 if (filter->pid < 0)
389 return 0;
390 waitpid(filter->pid, &filter->exitstatus, 0);
391 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus))
392 return 0;
393 die("Subprocess %s exited abnormally", filter->cmd);
394}
diff --git a/ui-commit.c b/ui-commit.c
index 9fdb8ee..d6b73ee 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -1,110 +1,118 @@
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 if (!ctx.cfg.noplainemail) { 43 if (!ctx.cfg.noplainemail) {
44 html(" "); 44 html(" ");
45 html_txt(info->author_email); 45 html_txt(info->author_email);
46 } 46 }
47 html("</td><td class='right'>"); 47 html("</td><td class='right'>");
48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time); 48 cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time);
49 html("</td></tr>\n"); 49 html("</td></tr>\n");
50 html("<tr><th>committer</th><td>"); 50 html("<tr><th>committer</th><td>");
51 html_txt(info->committer); 51 html_txt(info->committer);
52 if (!ctx.cfg.noplainemail) { 52 if (!ctx.cfg.noplainemail) {
53 html(" "); 53 html(" ");
54 html_txt(info->committer_email); 54 html_txt(info->committer_email);
55 } 55 }
56 html("</td><td class='right'>"); 56 html("</td><td class='right'>");
57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); 57 cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time);
58 html("</td></tr>\n"); 58 html("</td></tr>\n");
59 html("<tr><th>commit</th><td colspan='2' class='sha1'>"); 59 html("<tr><th>commit</th><td colspan='2' class='sha1'>");
60 tmp = sha1_to_hex(commit->object.sha1); 60 tmp = sha1_to_hex(commit->object.sha1);
61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); 61 cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp);
62 html(" ("); 62 html(" (");
63 cgit_patch_link("patch", NULL, NULL, NULL, tmp); 63 cgit_patch_link("patch", NULL, NULL, NULL, tmp);
64 html(")</td></tr>\n"); 64 html(")</td></tr>\n");
65 html("<tr><th>tree</th><td colspan='2' class='sha1'>"); 65 html("<tr><th>tree</th><td colspan='2' class='sha1'>");
66 tmp = xstrdup(hex); 66 tmp = xstrdup(hex);
67 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, 67 cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL,
68 ctx.qry.head, tmp, NULL); 68 ctx.qry.head, tmp, NULL);
69 html("</td></tr>\n"); 69 html("</td></tr>\n");
70 for (p = commit->parents; p ; p = p->next) { 70 for (p = commit->parents; p ; p = p->next) {
71 parent = lookup_commit_reference(p->item->object.sha1); 71 parent = lookup_commit_reference(p->item->object.sha1);
72 if (!parent) { 72 if (!parent) {
73 html("<tr><td colspan='3'>"); 73 html("<tr><td colspan='3'>");
74 cgit_print_error("Error reading parent commit"); 74 cgit_print_error("Error reading parent commit");
75 html("</td></tr>"); 75 html("</td></tr>");
76 continue; 76 continue;
77 } 77 }
78 html("<tr><th>parent</th>" 78 html("<tr><th>parent</th>"
79 "<td colspan='2' class='sha1'>"); 79 "<td colspan='2' class='sha1'>");
80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, 80 cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL,
81 ctx.qry.head, sha1_to_hex(p->item->object.sha1)); 81 ctx.qry.head, sha1_to_hex(p->item->object.sha1));
82 html(" ("); 82 html(" (");
83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, 83 cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
84 sha1_to_hex(p->item->object.sha1), NULL); 84 sha1_to_hex(p->item->object.sha1), NULL);
85 html(")</td></tr>"); 85 html(")</td></tr>");
86 parents++; 86 parents++;
87 } 87 }
88 if (ctx.repo->snapshots) { 88 if (ctx.repo->snapshots) {
89 html("<tr><th>download</th><td colspan='2' class='sha1'>"); 89 html("<tr><th>download</th><td colspan='2' class='sha1'>");
90 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, 90 cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head,
91 hex, ctx.repo->snapshots); 91 hex, ctx.repo->snapshots);
92 html("</td></tr>"); 92 html("</td></tr>");
93 } 93 }
94 html("</table>\n"); 94 html("</table>\n");
95 html("<div class='commit-subject'>"); 95 html("<div class='commit-subject'>");
96 if (ctx.repo->commit_filter)
97 cgit_open_filter(ctx.repo->commit_filter);
96 html_txt(info->subject); 98 html_txt(info->subject);
99 if (ctx.repo->commit_filter)
100 cgit_close_filter(ctx.repo->commit_filter);
97 show_commit_decorations(commit); 101 show_commit_decorations(commit);
98 html("</div>"); 102 html("</div>");
99 html("<div class='commit-msg'>"); 103 html("<div class='commit-msg'>");
104 if (ctx.repo->commit_filter)
105 cgit_open_filter(ctx.repo->commit_filter);
100 html_txt(info->msg); 106 html_txt(info->msg);
107 if (ctx.repo->commit_filter)
108 cgit_close_filter(ctx.repo->commit_filter);
101 html("</div>"); 109 html("</div>");
102 if (parents < 3) { 110 if (parents < 3) {
103 if (parents) 111 if (parents)
104 tmp = sha1_to_hex(commit->parents->item->object.sha1); 112 tmp = sha1_to_hex(commit->parents->item->object.sha1);
105 else 113 else
106 tmp = NULL; 114 tmp = NULL;
107 cgit_print_diff(ctx.qry.sha1, tmp, NULL); 115 cgit_print_diff(ctx.qry.sha1, tmp, NULL);
108 } 116 }
109 cgit_free_commitinfo(info); 117 cgit_free_commitinfo(info);
110} 118}
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 5372f5d..4136b3e 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -1,205 +1,184 @@
1/* ui-snapshot.c: generate snapshot of a commit 1/* ui-snapshot.c: generate snapshot of a commit
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 12
13static int write_compressed_tar_archive(struct archiver_args *args,const char *filter) 13static int write_compressed_tar_archive(struct archiver_args *args,const char *filter)
14{ 14{
15 int rw[2];
16 pid_t gzpid;
17 int stdout2;
18 int status;
19 int rv; 15 int rv;
16 struct cgit_filter f;
20 17
21 stdout2 = chk_non_negative(dup(STDIN_FILENO), "Preserving STDOUT before compressing"); 18 f.cmd = xstrdup(filter);
22 chk_zero(pipe(rw), "Opening pipe from compressor subprocess"); 19 f.argv = malloc(2 * sizeof(char *));
23 gzpid = chk_non_negative(fork(), "Forking compressor subprocess"); 20 f.argv[0] = f.cmd;
24 if(gzpid==0) { 21 f.argv[1] = NULL;
25 /* child */ 22 cgit_open_filter(&f);
26 chk_zero(close(rw[1]), "Closing write end of pipe in child");
27 chk_zero(close(STDIN_FILENO), "Closing STDIN");
28 chk_non_negative(dup2(rw[0],STDIN_FILENO), "Redirecting compressor input to stdin");
29 execlp(filter,filter,NULL);
30 _exit(-1);
31 }
32 /* parent */
33 chk_zero(close(rw[0]), "Closing read end of pipe");
34 chk_non_negative(dup2(rw[1],STDOUT_FILENO), "Redirecting output to compressor");
35
36 rv = write_tar_archive(args); 23 rv = write_tar_archive(args);
37 24 cgit_close_filter(&f);
38 chk_zero(close(STDOUT_FILENO), "Closing STDOUT redirected to compressor");
39 chk_non_negative(dup2(stdout2,STDOUT_FILENO), "Restoring uncompressed STDOUT");
40 chk_zero(close(stdout2), "Closing uncompressed STDOUT");
41 chk_zero(close(rw[1]), "Closing write end of pipe in parent");
42 chk_positive(waitpid(gzpid,&status,0), "Waiting on compressor process");
43 if(! ( WIFEXITED(status) && WEXITSTATUS(status)==0 ) )
44 cgit_print_error("Failed to compress archive");
45
46 return rv; 25 return rv;
47} 26}
48 27
49static int write_tar_gzip_archive(struct archiver_args *args) 28static int write_tar_gzip_archive(struct archiver_args *args)
50{ 29{
51 return write_compressed_tar_archive(args,"gzip"); 30 return write_compressed_tar_archive(args,"gzip");
52} 31}
53 32
54static int write_tar_bzip2_archive(struct archiver_args *args) 33static int write_tar_bzip2_archive(struct archiver_args *args)
55{ 34{
56 return write_compressed_tar_archive(args,"bzip2"); 35 return write_compressed_tar_archive(args,"bzip2");
57} 36}
58 37
59const struct cgit_snapshot_format cgit_snapshot_formats[] = { 38const struct cgit_snapshot_format cgit_snapshot_formats[] = {
60 { ".zip", "application/x-zip", write_zip_archive, 0x1 }, 39 { ".zip", "application/x-zip", write_zip_archive, 0x1 },
61 { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x2 }, 40 { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x2 },
62 { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x4 }, 41 { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x4 },
63 { ".tar", "application/x-tar", write_tar_archive, 0x8 }, 42 { ".tar", "application/x-tar", write_tar_archive, 0x8 },
64 {} 43 {}
65}; 44};
66 45
67static const struct cgit_snapshot_format *get_format(const char *filename) 46static const struct cgit_snapshot_format *get_format(const char *filename)
68{ 47{
69 const struct cgit_snapshot_format *fmt; 48 const struct cgit_snapshot_format *fmt;
70 int fl, sl; 49 int fl, sl;
71 50
72 fl = strlen(filename); 51 fl = strlen(filename);
73 for(fmt = cgit_snapshot_formats; fmt->suffix; fmt++) { 52 for(fmt = cgit_snapshot_formats; fmt->suffix; fmt++) {
74 sl = strlen(fmt->suffix); 53 sl = strlen(fmt->suffix);
75 if (sl >= fl) 54 if (sl >= fl)
76 continue; 55 continue;
77 if (!strcmp(fmt->suffix, filename + fl - sl)) 56 if (!strcmp(fmt->suffix, filename + fl - sl))
78 return fmt; 57 return fmt;
79 } 58 }
80 return NULL; 59 return NULL;
81} 60}
82 61
83static int make_snapshot(const struct cgit_snapshot_format *format, 62static int make_snapshot(const struct cgit_snapshot_format *format,
84 const char *hex, const char *prefix, 63 const char *hex, const char *prefix,
85 const char *filename) 64 const char *filename)
86{ 65{
87 struct archiver_args args; 66 struct archiver_args args;
88 struct commit *commit; 67 struct commit *commit;
89 unsigned char sha1[20]; 68 unsigned char sha1[20];
90 69
91 if(get_sha1(hex, sha1)) { 70 if(get_sha1(hex, sha1)) {
92 cgit_print_error(fmt("Bad object id: %s", hex)); 71 cgit_print_error(fmt("Bad object id: %s", hex));
93 return 1; 72 return 1;
94 } 73 }
95 commit = lookup_commit_reference(sha1); 74 commit = lookup_commit_reference(sha1);
96 if(!commit) { 75 if(!commit) {
97 cgit_print_error(fmt("Not a commit reference: %s", hex)); 76 cgit_print_error(fmt("Not a commit reference: %s", hex));
98 return 1; 77 return 1;
99 } 78 }
100 memset(&args, 0, sizeof(args)); 79 memset(&args, 0, sizeof(args));
101 if (prefix) { 80 if (prefix) {
102 args.base = fmt("%s/", prefix); 81 args.base = fmt("%s/", prefix);
103 args.baselen = strlen(prefix) + 1; 82 args.baselen = strlen(prefix) + 1;
104 } else { 83 } else {
105 args.base = ""; 84 args.base = "";
106 args.baselen = 0; 85 args.baselen = 0;
107 } 86 }
108 args.tree = commit->tree; 87 args.tree = commit->tree;
109 args.time = commit->date; 88 args.time = commit->date;
110 ctx.page.mimetype = xstrdup(format->mimetype); 89 ctx.page.mimetype = xstrdup(format->mimetype);
111 ctx.page.filename = xstrdup(filename); 90 ctx.page.filename = xstrdup(filename);
112 cgit_print_http_headers(&ctx); 91 cgit_print_http_headers(&ctx);
113 format->write_func(&args); 92 format->write_func(&args);
114 return 0; 93 return 0;
115} 94}
116 95
117/* Try to guess the requested revision from the requested snapshot name. 96/* Try to guess the requested revision from the requested snapshot name.
118 * First the format extension is stripped, e.g. "cgit-0.7.2.tar.gz" become 97 * First the format extension is stripped, e.g. "cgit-0.7.2.tar.gz" become
119 * "cgit-0.7.2". If this is a valid commit object name we've got a winner. 98 * "cgit-0.7.2". If this is a valid commit object name we've got a winner.
120 * Otherwise, if the snapshot name has a prefix matching the result from 99 * Otherwise, if the snapshot name has a prefix matching the result from
121 * repo_basename(), we strip the basename and any following '-' and '_' 100 * repo_basename(), we strip the basename and any following '-' and '_'
122 * characters ("cgit-0.7.2" -> "0.7.2") and check the resulting name once 101 * characters ("cgit-0.7.2" -> "0.7.2") and check the resulting name once
123 * more. If this still isn't a valid commit object name, we check if pre- 102 * more. If this still isn't a valid commit object name, we check if pre-
124 * pending a 'v' to the remaining snapshot name ("0.7.2" -> "v0.7.2") gives 103 * pending a 'v' to the remaining snapshot name ("0.7.2" -> "v0.7.2") gives
125 * us something valid. 104 * us something valid.
126 */ 105 */
127static const char *get_ref_from_filename(const char *url, const char *filename, 106static const char *get_ref_from_filename(const char *url, const char *filename,
128 const struct cgit_snapshot_format *format) 107 const struct cgit_snapshot_format *format)
129{ 108{
130 const char *reponame; 109 const char *reponame;
131 unsigned char sha1[20]; 110 unsigned char sha1[20];
132 char *snapshot; 111 char *snapshot;
133 112
134 snapshot = xstrdup(filename); 113 snapshot = xstrdup(filename);
135 snapshot[strlen(snapshot) - strlen(format->suffix)] = '\0'; 114 snapshot[strlen(snapshot) - strlen(format->suffix)] = '\0';
136 fprintf(stderr, "snapshot=%s\n", snapshot); 115 fprintf(stderr, "snapshot=%s\n", snapshot);
137 116
138 if (get_sha1(snapshot, sha1) == 0) 117 if (get_sha1(snapshot, sha1) == 0)
139 return snapshot; 118 return snapshot;
140 119
141 reponame = cgit_repobasename(url); 120 reponame = cgit_repobasename(url);
142 fprintf(stderr, "reponame=%s\n", reponame); 121 fprintf(stderr, "reponame=%s\n", reponame);
143 if (prefixcmp(snapshot, reponame) == 0) { 122 if (prefixcmp(snapshot, reponame) == 0) {
144 snapshot += strlen(reponame); 123 snapshot += strlen(reponame);
145 while (snapshot && (*snapshot == '-' || *snapshot == '_')) 124 while (snapshot && (*snapshot == '-' || *snapshot == '_'))
146 snapshot++; 125 snapshot++;
147 } 126 }
148 127
149 if (get_sha1(snapshot, sha1) == 0) 128 if (get_sha1(snapshot, sha1) == 0)
150 return snapshot; 129 return snapshot;
151 130
152 snapshot = fmt("v%s", snapshot); 131 snapshot = fmt("v%s", snapshot);
153 if (get_sha1(snapshot, sha1) == 0) 132 if (get_sha1(snapshot, sha1) == 0)
154 return snapshot; 133 return snapshot;
155 134
156 return NULL; 135 return NULL;
157} 136}
158 137
159void show_error(char *msg) 138void show_error(char *msg)
160{ 139{
161 ctx.page.mimetype = "text/html"; 140 ctx.page.mimetype = "text/html";
162 cgit_print_http_headers(&ctx); 141 cgit_print_http_headers(&ctx);
163 cgit_print_docstart(&ctx); 142 cgit_print_docstart(&ctx);
164 cgit_print_pageheader(&ctx); 143 cgit_print_pageheader(&ctx);
165 cgit_print_error(msg); 144 cgit_print_error(msg);
166 cgit_print_docend(); 145 cgit_print_docend();
167} 146}
168 147
169void cgit_print_snapshot(const char *head, const char *hex, 148void cgit_print_snapshot(const char *head, const char *hex,
170 const char *filename, int snapshots, int dwim) 149 const char *filename, int snapshots, int dwim)
171{ 150{
172 const struct cgit_snapshot_format* f; 151 const struct cgit_snapshot_format* f;
173 char *prefix = NULL; 152 char *prefix = NULL;
174 153
175 if (!filename) { 154 if (!filename) {
176 show_error("No snapshot name specified"); 155 show_error("No snapshot name specified");
177 return; 156 return;
178 } 157 }
179 158
180 f = get_format(filename); 159 f = get_format(filename);
181 if (!f) { 160 if (!f) {
182 show_error(xstrdup(fmt("Unsupported snapshot format: %s", 161 show_error(xstrdup(fmt("Unsupported snapshot format: %s",
183 filename))); 162 filename)));
184 return; 163 return;
185 } 164 }
186 165
187 if (!hex && dwim) { 166 if (!hex && dwim) {
188 hex = get_ref_from_filename(ctx.repo->url, filename, f); 167 hex = get_ref_from_filename(ctx.repo->url, filename, f);
189 if (hex == NULL) { 168 if (hex == NULL) {
190 html_status(404, "Not found", 0); 169 html_status(404, "Not found", 0);
191 return; 170 return;
192 } 171 }
193 prefix = xstrdup(filename); 172 prefix = xstrdup(filename);
194 prefix[strlen(filename) - strlen(f->suffix)] = '\0'; 173 prefix[strlen(filename) - strlen(f->suffix)] = '\0';
195 } 174 }
196 175
197 if (!hex) 176 if (!hex)
198 hex = head; 177 hex = head;
199 178
200 if (!prefix) 179 if (!prefix)
201 prefix = xstrdup(cgit_repobasename(ctx.repo->url)); 180 prefix = xstrdup(cgit_repobasename(ctx.repo->url));
202 181
203 make_snapshot(f, hex, prefix, filename); 182 make_snapshot(f, hex, prefix, filename);
204 free(prefix); 183 free(prefix);
205} 184}
diff --git a/ui-tree.c b/ui-tree.c
index 61fcf5a..c608754 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -1,268 +1,278 @@
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(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.repo->source_filter) {
26 html("<tr><td class='lines'><pre><code>");
27 ctx.repo->source_filter->argv[1] = xstrdup(name);
28 cgit_open_filter(ctx.repo->source_filter);
29 write(STDOUT_FILENO, buf, size);
30 cgit_close_filter(ctx.repo->source_filter);
31 html("</code></pre></td></tr></table>\n");
32 return;
33 }
34
25 html("<tr><td class='linenumbers'><pre>"); 35 html("<tr><td class='linenumbers'><pre>");
26 idx = 0; 36 idx = 0;
27 lineno = 0; 37 lineno = 0;
28 38
29 if (size) { 39 if (size) {
30 htmlf(numberfmt, ++lineno); 40 htmlf(numberfmt, ++lineno);
31 while(idx < size - 1) { // skip absolute last newline 41 while(idx < size - 1) { // skip absolute last newline
32 if (buf[idx] == '\n') 42 if (buf[idx] == '\n')
33 htmlf(numberfmt, ++lineno); 43 htmlf(numberfmt, ++lineno);
34 idx++; 44 idx++;
35 } 45 }
36 } 46 }
37 html("</pre></td>\n"); 47 html("</pre></td>\n");
38 html("<td class='lines'><pre><code>"); 48 html("<td class='lines'><pre><code>");
39 html_txt(buf); 49 html_txt(buf);
40 html("</code></pre></td></tr></table>\n"); 50 html("</code></pre></td></tr></table>\n");
41} 51}
42 52
43#define ROWLEN 32 53#define ROWLEN 32
44 54
45static void print_binary_buffer(char *buf, unsigned long size) 55static void print_binary_buffer(char *buf, unsigned long size)
46{ 56{
47 unsigned long ofs, idx; 57 unsigned long ofs, idx;
48 static char ascii[ROWLEN + 1]; 58 static char ascii[ROWLEN + 1];
49 59
50 html("<table summary='blob content' class='bin-blob'>\n"); 60 html("<table summary='blob content' class='bin-blob'>\n");
51 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>");
52 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { 62 for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) {
53 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs); 63 htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs);
54 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 64 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
55 htmlf("%*s%02x", 65 htmlf("%*s%02x",
56 idx == 16 ? 4 : 1, "", 66 idx == 16 ? 4 : 1, "",
57 buf[idx] & 0xff); 67 buf[idx] & 0xff);
58 html(" </td><td class='hex'>"); 68 html(" </td><td class='hex'>");
59 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) 69 for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++)
60 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; 70 ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.';
61 ascii[idx] = '\0'; 71 ascii[idx] = '\0';
62 html_txt(ascii); 72 html_txt(ascii);
63 html("</td></tr>\n"); 73 html("</td></tr>\n");
64 } 74 }
65 html("</table>\n"); 75 html("</table>\n");
66} 76}
67 77
68static void print_object(const unsigned char *sha1, char *path) 78static void print_object(const unsigned char *sha1, char *path, const char *basename)
69{ 79{
70 enum object_type type; 80 enum object_type type;
71 char *buf; 81 char *buf;
72 unsigned long size; 82 unsigned long size;
73 83
74 type = sha1_object_info(sha1, &size); 84 type = sha1_object_info(sha1, &size);
75 if (type == OBJ_BAD) { 85 if (type == OBJ_BAD) {
76 cgit_print_error(fmt("Bad object name: %s", 86 cgit_print_error(fmt("Bad object name: %s",
77 sha1_to_hex(sha1))); 87 sha1_to_hex(sha1)));
78 return; 88 return;
79 } 89 }
80 90
81 buf = read_sha1_file(sha1, &type, &size); 91 buf = read_sha1_file(sha1, &type, &size);
82 if (!buf) { 92 if (!buf) {
83 cgit_print_error(fmt("Error reading object %s", 93 cgit_print_error(fmt("Error reading object %s",
84 sha1_to_hex(sha1))); 94 sha1_to_hex(sha1)));
85 return; 95 return;
86 } 96 }
87 97
88 html(" ("); 98 html(" (");
89 cgit_plain_link("plain", NULL, NULL, ctx.qry.head, 99 cgit_plain_link("plain", NULL, NULL, ctx.qry.head,
90 curr_rev, path); 100 curr_rev, path);
91 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); 101 htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1));
92 102
93 if (buffer_is_binary(buf, size)) 103 if (buffer_is_binary(buf, size))
94 print_binary_buffer(buf, size); 104 print_binary_buffer(buf, size);
95 else 105 else
96 print_text_buffer(buf, size); 106 print_text_buffer(basename, buf, size);
97} 107}
98 108
99 109
100static 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,
101 const char *pathname, unsigned int mode, int stage, 111 const char *pathname, unsigned int mode, int stage,
102 void *cbdata) 112 void *cbdata)
103{ 113{
104 char *name; 114 char *name;
105 char *fullpath; 115 char *fullpath;
106 char *class; 116 char *class;
107 enum object_type type; 117 enum object_type type;
108 unsigned long size = 0; 118 unsigned long size = 0;
109 119
110 name = xstrdup(pathname); 120 name = xstrdup(pathname);
111 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", 121 fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "",
112 ctx.qry.path ? "/" : "", name); 122 ctx.qry.path ? "/" : "", name);
113 123
114 if (!S_ISGITLINK(mode)) { 124 if (!S_ISGITLINK(mode)) {
115 type = sha1_object_info(sha1, &size); 125 type = sha1_object_info(sha1, &size);
116 if (type == OBJ_BAD) { 126 if (type == OBJ_BAD) {
117 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", 127 htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>",
118 name, 128 name,
119 sha1_to_hex(sha1)); 129 sha1_to_hex(sha1));
120 return 0; 130 return 0;
121 } 131 }
122 } 132 }
123 133
124 html("<tr><td class='ls-mode'>"); 134 html("<tr><td class='ls-mode'>");
125 cgit_print_filemode(mode); 135 cgit_print_filemode(mode);
126 html("</td><td>"); 136 html("</td><td>");
127 if (S_ISGITLINK(mode)) { 137 if (S_ISGITLINK(mode)) {
128 htmlf("<a class='ls-mod' href='"); 138 htmlf("<a class='ls-mod' href='");
129 html_attr(fmt(ctx.repo->module_link, 139 html_attr(fmt(ctx.repo->module_link,
130 name, 140 name,
131 sha1_to_hex(sha1))); 141 sha1_to_hex(sha1)));
132 html("'>"); 142 html("'>");
133 html_txt(name); 143 html_txt(name);
134 html("</a>"); 144 html("</a>");
135 } else if (S_ISDIR(mode)) { 145 } else if (S_ISDIR(mode)) {
136 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 146 cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head,
137 curr_rev, fullpath); 147 curr_rev, fullpath);
138 } else { 148 } else {
139 class = strrchr(name, '.'); 149 class = strrchr(name, '.');
140 if (class != NULL) { 150 if (class != NULL) {
141 class = fmt("ls-blob %s", class + 1); 151 class = fmt("ls-blob %s", class + 1);
142 } else 152 } else
143 class = "ls-blob"; 153 class = "ls-blob";
144 cgit_tree_link(name, NULL, class, ctx.qry.head, 154 cgit_tree_link(name, NULL, class, ctx.qry.head,
145 curr_rev, fullpath); 155 curr_rev, fullpath);
146 } 156 }
147 htmlf("</td><td class='ls-size'>%li</td>", size); 157 htmlf("</td><td class='ls-size'>%li</td>", size);
148 158
149 html("<td>"); 159 html("<td>");
150 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, 160 cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev,
151 fullpath, 0, NULL, NULL, ctx.qry.showmsg); 161 fullpath, 0, NULL, NULL, ctx.qry.showmsg);
152 if (ctx.repo->max_stats) 162 if (ctx.repo->max_stats)
153 cgit_stats_link("stats", NULL, "button", ctx.qry.head, 163 cgit_stats_link("stats", NULL, "button", ctx.qry.head,
154 fullpath); 164 fullpath);
155 html("</td></tr>\n"); 165 html("</td></tr>\n");
156 free(name); 166 free(name);
157 return 0; 167 return 0;
158} 168}
159 169
160static void ls_head() 170static void ls_head()
161{ 171{
162 html("<table summary='tree listing' class='list'>\n"); 172 html("<table summary='tree listing' class='list'>\n");
163 html("<tr class='nohover'>"); 173 html("<tr class='nohover'>");
164 html("<th class='left'>Mode</th>"); 174 html("<th class='left'>Mode</th>");
165 html("<th class='left'>Name</th>"); 175 html("<th class='left'>Name</th>");
166 html("<th class='right'>Size</th>"); 176 html("<th class='right'>Size</th>");
167 html("<th/>"); 177 html("<th/>");
168 html("</tr>\n"); 178 html("</tr>\n");
169 header = 1; 179 header = 1;
170} 180}
171 181
172static void ls_tail() 182static void ls_tail()
173{ 183{
174 if (!header) 184 if (!header)
175 return; 185 return;
176 html("</table>\n"); 186 html("</table>\n");
177 header = 0; 187 header = 0;
178} 188}
179 189
180static void ls_tree(const unsigned char *sha1, char *path) 190static void ls_tree(const unsigned char *sha1, char *path)
181{ 191{
182 struct tree *tree; 192 struct tree *tree;
183 193
184 tree = parse_tree_indirect(sha1); 194 tree = parse_tree_indirect(sha1);
185 if (!tree) { 195 if (!tree) {
186 cgit_print_error(fmt("Not a tree object: %s", 196 cgit_print_error(fmt("Not a tree object: %s",
187 sha1_to_hex(sha1))); 197 sha1_to_hex(sha1)));
188 return; 198 return;
189 } 199 }
190 200
191 ls_head(); 201 ls_head();
192 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); 202 read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL);
193 ls_tail(); 203 ls_tail();
194} 204}
195 205
196 206
197static int walk_tree(const unsigned char *sha1, const char *base, int baselen, 207static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
198 const char *pathname, unsigned mode, int stage, 208 const char *pathname, unsigned mode, int stage,
199 void *cbdata) 209 void *cbdata)
200{ 210{
201 static int state; 211 static int state;
202 static char buffer[PATH_MAX]; 212 static char buffer[PATH_MAX];
203 char *url; 213 char *url;
204 214
205 if (state == 0) { 215 if (state == 0) {
206 memcpy(buffer, base, baselen); 216 memcpy(buffer, base, baselen);
207 strcpy(buffer+baselen, pathname); 217 strcpy(buffer+baselen, pathname);
208 url = cgit_pageurl(ctx.qry.repo, "tree", 218 url = cgit_pageurl(ctx.qry.repo, "tree",
209 fmt("h=%s&amp;path=%s", curr_rev, buffer)); 219 fmt("h=%s&amp;path=%s", curr_rev, buffer));
210 html("/"); 220 html("/");
211 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head, 221 cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head,
212 curr_rev, buffer); 222 curr_rev, buffer);
213 223
214 if (strcmp(match_path, buffer)) 224 if (strcmp(match_path, buffer))
215 return READ_TREE_RECURSIVE; 225 return READ_TREE_RECURSIVE;
216 226
217 if (S_ISDIR(mode)) { 227 if (S_ISDIR(mode)) {
218 state = 1; 228 state = 1;
219 ls_head(); 229 ls_head();
220 return READ_TREE_RECURSIVE; 230 return READ_TREE_RECURSIVE;
221 } else { 231 } else {
222 print_object(sha1, buffer); 232 print_object(sha1, buffer, pathname);
223 return 0; 233 return 0;
224 } 234 }
225 } 235 }
226 ls_item(sha1, base, baselen, pathname, mode, stage, NULL); 236 ls_item(sha1, base, baselen, pathname, mode, stage, NULL);
227 return 0; 237 return 0;
228} 238}
229 239
230 240
231/* 241/*
232 * Show a tree or a blob 242 * Show a tree or a blob
233 * rev: the commit pointing at the root tree object 243 * rev: the commit pointing at the root tree object
234 * path: path to tree or blob 244 * path: path to tree or blob
235 */ 245 */
236void cgit_print_tree(const char *rev, char *path) 246void cgit_print_tree(const char *rev, char *path)
237{ 247{
238 unsigned char sha1[20]; 248 unsigned char sha1[20];
239 struct commit *commit; 249 struct commit *commit;
240 const char *paths[] = {path, NULL}; 250 const char *paths[] = {path, NULL};
241 251
242 if (!rev) 252 if (!rev)
243 rev = ctx.qry.head; 253 rev = ctx.qry.head;
244 254
245 curr_rev = xstrdup(rev); 255 curr_rev = xstrdup(rev);
246 if (get_sha1(rev, sha1)) { 256 if (get_sha1(rev, sha1)) {
247 cgit_print_error(fmt("Invalid revision name: %s", rev)); 257 cgit_print_error(fmt("Invalid revision name: %s", rev));
248 return; 258 return;
249 } 259 }
250 commit = lookup_commit_reference(sha1); 260 commit = lookup_commit_reference(sha1);
251 if (!commit || parse_commit(commit)) { 261 if (!commit || parse_commit(commit)) {
252 cgit_print_error(fmt("Invalid commit reference: %s", rev)); 262 cgit_print_error(fmt("Invalid commit reference: %s", rev));
253 return; 263 return;
254 } 264 }
255 265
256 html("path: <a href='"); 266 html("path: <a href='");
257 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev))); 267 html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev)));
258 html("'>root</a>"); 268 html("'>root</a>");
259 269
260 if (path == NULL) { 270 if (path == NULL) {
261 ls_tree(commit->tree->object.sha1, NULL); 271 ls_tree(commit->tree->object.sha1, NULL);
262 return; 272 return;
263 } 273 }
264 274
265 match_path = path; 275 match_path = path;
266 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL); 276 read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL);
267 ls_tail(); 277 ls_tail();
268} 278}