summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c4
-rw-r--r--cgit.h2
-rw-r--r--cgitrc.5.txt9
-rw-r--r--shared.c1
-rw-r--r--ui-refs.c2
5 files changed, 18 insertions, 0 deletions
diff --git a/cgit.c b/cgit.c
index a17f40d..29813cd 100644
--- a/cgit.c
+++ b/cgit.c
@@ -1,720 +1,724 @@
1/* cgit.c: cgi for the git scm 1/* cgit.c: cgi for the git scm
2 * 2 *
3 * Copyright (C) 2006 Lars Hjemli 3 * Copyright (C) 2006 Lars Hjemli
4 * 4 *
5 * Licensed under GNU General Public License v2 5 * Licensed under GNU General Public License v2
6 * (see COPYING for full license text) 6 * (see COPYING for full license text)
7 */ 7 */
8 8
9#include "cgit.h" 9#include "cgit.h"
10#include "cache.h" 10#include "cache.h"
11#include "cmd.h" 11#include "cmd.h"
12#include "configfile.h" 12#include "configfile.h"
13#include "html.h" 13#include "html.h"
14#include "ui-shared.h" 14#include "ui-shared.h"
15#include "ui-stats.h" 15#include "ui-stats.h"
16#include "scan-tree.h" 16#include "scan-tree.h"
17 17
18const char *cgit_version = CGIT_VERSION; 18const char *cgit_version = CGIT_VERSION;
19 19
20void add_mimetype(const char *name, const char *value) 20void add_mimetype(const char *name, const char *value)
21{ 21{
22 struct string_list_item *item; 22 struct string_list_item *item;
23 23
24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); 24 item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
25 item->util = xstrdup(value); 25 item->util = xstrdup(value);
26} 26}
27 27
28struct cgit_filter *new_filter(const char *cmd, int extra_args) 28struct cgit_filter *new_filter(const char *cmd, int extra_args)
29{ 29{
30 struct cgit_filter *f; 30 struct cgit_filter *f;
31 31
32 if (!cmd || !cmd[0]) 32 if (!cmd || !cmd[0])
33 return NULL; 33 return NULL;
34 34
35 f = xmalloc(sizeof(struct cgit_filter)); 35 f = xmalloc(sizeof(struct cgit_filter));
36 f->cmd = xstrdup(cmd); 36 f->cmd = xstrdup(cmd);
37 f->argv = xmalloc((2 + extra_args) * sizeof(char *)); 37 f->argv = xmalloc((2 + extra_args) * sizeof(char *));
38 f->argv[0] = f->cmd; 38 f->argv[0] = f->cmd;
39 f->argv[1] = NULL; 39 f->argv[1] = NULL;
40 return f; 40 return f;
41} 41}
42 42
43static void process_cached_repolist(const char *path); 43static void process_cached_repolist(const char *path);
44 44
45void repo_config(struct cgit_repo *repo, const char *name, const char *value) 45void repo_config(struct cgit_repo *repo, const char *name, const char *value)
46{ 46{
47 if (!strcmp(name, "name")) 47 if (!strcmp(name, "name"))
48 repo->name = xstrdup(value); 48 repo->name = xstrdup(value);
49 else if (!strcmp(name, "clone-url")) 49 else if (!strcmp(name, "clone-url"))
50 repo->clone_url = xstrdup(value); 50 repo->clone_url = xstrdup(value);
51 else if (!strcmp(name, "desc")) 51 else if (!strcmp(name, "desc"))
52 repo->desc = xstrdup(value); 52 repo->desc = xstrdup(value);
53 else if (!strcmp(name, "owner")) 53 else if (!strcmp(name, "owner"))
54 repo->owner = xstrdup(value); 54 repo->owner = xstrdup(value);
55 else if (!strcmp(name, "defbranch")) 55 else if (!strcmp(name, "defbranch"))
56 repo->defbranch = xstrdup(value); 56 repo->defbranch = xstrdup(value);
57 else if (!strcmp(name, "snapshots")) 57 else if (!strcmp(name, "snapshots"))
58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); 58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
59 else if (!strcmp(name, "enable-log-filecount")) 59 else if (!strcmp(name, "enable-log-filecount"))
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); 60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount")) 61 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); 62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
63 else if (!strcmp(name, "enable-remote-branches"))
64 repo->enable_remote_branches = atoi(value);
63 else if (!strcmp(name, "max-stats")) 65 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL); 66 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link")) 67 else if (!strcmp(name, "module-link"))
66 repo->module_link= xstrdup(value); 68 repo->module_link= xstrdup(value);
67 else if (!strcmp(name, "section")) 69 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value); 70 repo->section = xstrdup(value);
69 else if (!strcmp(name, "readme") && value != NULL) { 71 else if (!strcmp(name, "readme") && value != NULL) {
70 if (*value == '/') 72 if (*value == '/')
71 ctx.repo->readme = xstrdup(value); 73 ctx.repo->readme = xstrdup(value);
72 else 74 else
73 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 75 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
74 } else if (ctx.cfg.enable_filter_overrides) { 76 } else if (ctx.cfg.enable_filter_overrides) {
75 if (!strcmp(name, "about-filter")) 77 if (!strcmp(name, "about-filter"))
76 repo->about_filter = new_filter(value, 0); 78 repo->about_filter = new_filter(value, 0);
77 else if (!strcmp(name, "commit-filter")) 79 else if (!strcmp(name, "commit-filter"))
78 repo->commit_filter = new_filter(value, 0); 80 repo->commit_filter = new_filter(value, 0);
79 else if (!strcmp(name, "source-filter")) 81 else if (!strcmp(name, "source-filter"))
80 repo->source_filter = new_filter(value, 1); 82 repo->source_filter = new_filter(value, 1);
81 } 83 }
82} 84}
83 85
84void config_cb(const char *name, const char *value) 86void config_cb(const char *name, const char *value)
85{ 87{
86 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 88 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
87 ctx.cfg.section = xstrdup(value); 89 ctx.cfg.section = xstrdup(value);
88 else if (!strcmp(name, "repo.url")) 90 else if (!strcmp(name, "repo.url"))
89 ctx.repo = cgit_add_repo(value); 91 ctx.repo = cgit_add_repo(value);
90 else if (ctx.repo && !strcmp(name, "repo.path")) 92 else if (ctx.repo && !strcmp(name, "repo.path"))
91 ctx.repo->path = trim_end(value, '/'); 93 ctx.repo->path = trim_end(value, '/');
92 else if (ctx.repo && !prefixcmp(name, "repo.")) 94 else if (ctx.repo && !prefixcmp(name, "repo."))
93 repo_config(ctx.repo, name + 5, value); 95 repo_config(ctx.repo, name + 5, value);
94 else if (!strcmp(name, "root-title")) 96 else if (!strcmp(name, "root-title"))
95 ctx.cfg.root_title = xstrdup(value); 97 ctx.cfg.root_title = xstrdup(value);
96 else if (!strcmp(name, "root-desc")) 98 else if (!strcmp(name, "root-desc"))
97 ctx.cfg.root_desc = xstrdup(value); 99 ctx.cfg.root_desc = xstrdup(value);
98 else if (!strcmp(name, "root-readme")) 100 else if (!strcmp(name, "root-readme"))
99 ctx.cfg.root_readme = xstrdup(value); 101 ctx.cfg.root_readme = xstrdup(value);
100 else if (!strcmp(name, "css")) 102 else if (!strcmp(name, "css"))
101 ctx.cfg.css = xstrdup(value); 103 ctx.cfg.css = xstrdup(value);
102 else if (!strcmp(name, "favicon")) 104 else if (!strcmp(name, "favicon"))
103 ctx.cfg.favicon = xstrdup(value); 105 ctx.cfg.favicon = xstrdup(value);
104 else if (!strcmp(name, "footer")) 106 else if (!strcmp(name, "footer"))
105 ctx.cfg.footer = xstrdup(value); 107 ctx.cfg.footer = xstrdup(value);
106 else if (!strcmp(name, "head-include")) 108 else if (!strcmp(name, "head-include"))
107 ctx.cfg.head_include = xstrdup(value); 109 ctx.cfg.head_include = xstrdup(value);
108 else if (!strcmp(name, "header")) 110 else if (!strcmp(name, "header"))
109 ctx.cfg.header = xstrdup(value); 111 ctx.cfg.header = xstrdup(value);
110 else if (!strcmp(name, "logo")) 112 else if (!strcmp(name, "logo"))
111 ctx.cfg.logo = xstrdup(value); 113 ctx.cfg.logo = xstrdup(value);
112 else if (!strcmp(name, "index-header")) 114 else if (!strcmp(name, "index-header"))
113 ctx.cfg.index_header = xstrdup(value); 115 ctx.cfg.index_header = xstrdup(value);
114 else if (!strcmp(name, "index-info")) 116 else if (!strcmp(name, "index-info"))
115 ctx.cfg.index_info = xstrdup(value); 117 ctx.cfg.index_info = xstrdup(value);
116 else if (!strcmp(name, "logo-link")) 118 else if (!strcmp(name, "logo-link"))
117 ctx.cfg.logo_link = xstrdup(value); 119 ctx.cfg.logo_link = xstrdup(value);
118 else if (!strcmp(name, "module-link")) 120 else if (!strcmp(name, "module-link"))
119 ctx.cfg.module_link = xstrdup(value); 121 ctx.cfg.module_link = xstrdup(value);
120 else if (!strcmp(name, "virtual-root")) { 122 else if (!strcmp(name, "virtual-root")) {
121 ctx.cfg.virtual_root = trim_end(value, '/'); 123 ctx.cfg.virtual_root = trim_end(value, '/');
122 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 124 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
123 ctx.cfg.virtual_root = ""; 125 ctx.cfg.virtual_root = "";
124 } else if (!strcmp(name, "nocache")) 126 } else if (!strcmp(name, "nocache"))
125 ctx.cfg.nocache = atoi(value); 127 ctx.cfg.nocache = atoi(value);
126 else if (!strcmp(name, "noplainemail")) 128 else if (!strcmp(name, "noplainemail"))
127 ctx.cfg.noplainemail = atoi(value); 129 ctx.cfg.noplainemail = atoi(value);
128 else if (!strcmp(name, "noheader")) 130 else if (!strcmp(name, "noheader"))
129 ctx.cfg.noheader = atoi(value); 131 ctx.cfg.noheader = atoi(value);
130 else if (!strcmp(name, "snapshots")) 132 else if (!strcmp(name, "snapshots"))
131 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 133 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
132 else if (!strcmp(name, "enable-filter-overrides")) 134 else if (!strcmp(name, "enable-filter-overrides"))
133 ctx.cfg.enable_filter_overrides = atoi(value); 135 ctx.cfg.enable_filter_overrides = atoi(value);
134 else if (!strcmp(name, "enable-index-links")) 136 else if (!strcmp(name, "enable-index-links"))
135 ctx.cfg.enable_index_links = atoi(value); 137 ctx.cfg.enable_index_links = atoi(value);
136 else if (!strcmp(name, "enable-log-filecount")) 138 else if (!strcmp(name, "enable-log-filecount"))
137 ctx.cfg.enable_log_filecount = atoi(value); 139 ctx.cfg.enable_log_filecount = atoi(value);
138 else if (!strcmp(name, "enable-log-linecount")) 140 else if (!strcmp(name, "enable-log-linecount"))
139 ctx.cfg.enable_log_linecount = atoi(value); 141 ctx.cfg.enable_log_linecount = atoi(value);
142 else if (!strcmp(name, "enable-remote-branches"))
143 ctx.cfg.enable_remote_branches = atoi(value);
140 else if (!strcmp(name, "enable-tree-linenumbers")) 144 else if (!strcmp(name, "enable-tree-linenumbers"))
141 ctx.cfg.enable_tree_linenumbers = atoi(value); 145 ctx.cfg.enable_tree_linenumbers = atoi(value);
142 else if (!strcmp(name, "max-stats")) 146 else if (!strcmp(name, "max-stats"))
143 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 147 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
144 else if (!strcmp(name, "cache-size")) 148 else if (!strcmp(name, "cache-size"))
145 ctx.cfg.cache_size = atoi(value); 149 ctx.cfg.cache_size = atoi(value);
146 else if (!strcmp(name, "cache-root")) 150 else if (!strcmp(name, "cache-root"))
147 ctx.cfg.cache_root = xstrdup(value); 151 ctx.cfg.cache_root = xstrdup(value);
148 else if (!strcmp(name, "cache-root-ttl")) 152 else if (!strcmp(name, "cache-root-ttl"))
149 ctx.cfg.cache_root_ttl = atoi(value); 153 ctx.cfg.cache_root_ttl = atoi(value);
150 else if (!strcmp(name, "cache-repo-ttl")) 154 else if (!strcmp(name, "cache-repo-ttl"))
151 ctx.cfg.cache_repo_ttl = atoi(value); 155 ctx.cfg.cache_repo_ttl = atoi(value);
152 else if (!strcmp(name, "cache-scanrc-ttl")) 156 else if (!strcmp(name, "cache-scanrc-ttl"))
153 ctx.cfg.cache_scanrc_ttl = atoi(value); 157 ctx.cfg.cache_scanrc_ttl = atoi(value);
154 else if (!strcmp(name, "cache-static-ttl")) 158 else if (!strcmp(name, "cache-static-ttl"))
155 ctx.cfg.cache_static_ttl = atoi(value); 159 ctx.cfg.cache_static_ttl = atoi(value);
156 else if (!strcmp(name, "cache-dynamic-ttl")) 160 else if (!strcmp(name, "cache-dynamic-ttl"))
157 ctx.cfg.cache_dynamic_ttl = atoi(value); 161 ctx.cfg.cache_dynamic_ttl = atoi(value);
158 else if (!strcmp(name, "about-filter")) 162 else if (!strcmp(name, "about-filter"))
159 ctx.cfg.about_filter = new_filter(value, 0); 163 ctx.cfg.about_filter = new_filter(value, 0);
160 else if (!strcmp(name, "commit-filter")) 164 else if (!strcmp(name, "commit-filter"))
161 ctx.cfg.commit_filter = new_filter(value, 0); 165 ctx.cfg.commit_filter = new_filter(value, 0);
162 else if (!strcmp(name, "embedded")) 166 else if (!strcmp(name, "embedded"))
163 ctx.cfg.embedded = atoi(value); 167 ctx.cfg.embedded = atoi(value);
164 else if (!strcmp(name, "max-message-length")) 168 else if (!strcmp(name, "max-message-length"))
165 ctx.cfg.max_msg_len = atoi(value); 169 ctx.cfg.max_msg_len = atoi(value);
166 else if (!strcmp(name, "max-repodesc-length")) 170 else if (!strcmp(name, "max-repodesc-length"))
167 ctx.cfg.max_repodesc_len = atoi(value); 171 ctx.cfg.max_repodesc_len = atoi(value);
168 else if (!strcmp(name, "max-repo-count")) 172 else if (!strcmp(name, "max-repo-count"))
169 ctx.cfg.max_repo_count = atoi(value); 173 ctx.cfg.max_repo_count = atoi(value);
170 else if (!strcmp(name, "max-commit-count")) 174 else if (!strcmp(name, "max-commit-count"))
171 ctx.cfg.max_commit_count = atoi(value); 175 ctx.cfg.max_commit_count = atoi(value);
172 else if (!strcmp(name, "scan-path")) 176 else if (!strcmp(name, "scan-path"))
173 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 177 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
174 process_cached_repolist(value); 178 process_cached_repolist(value);
175 else 179 else
176 scan_tree(value, repo_config); 180 scan_tree(value, repo_config);
177 else if (!strcmp(name, "source-filter")) 181 else if (!strcmp(name, "source-filter"))
178 ctx.cfg.source_filter = new_filter(value, 1); 182 ctx.cfg.source_filter = new_filter(value, 1);
179 else if (!strcmp(name, "summary-log")) 183 else if (!strcmp(name, "summary-log"))
180 ctx.cfg.summary_log = atoi(value); 184 ctx.cfg.summary_log = atoi(value);
181 else if (!strcmp(name, "summary-branches")) 185 else if (!strcmp(name, "summary-branches"))
182 ctx.cfg.summary_branches = atoi(value); 186 ctx.cfg.summary_branches = atoi(value);
183 else if (!strcmp(name, "summary-tags")) 187 else if (!strcmp(name, "summary-tags"))
184 ctx.cfg.summary_tags = atoi(value); 188 ctx.cfg.summary_tags = atoi(value);
185 else if (!strcmp(name, "agefile")) 189 else if (!strcmp(name, "agefile"))
186 ctx.cfg.agefile = xstrdup(value); 190 ctx.cfg.agefile = xstrdup(value);
187 else if (!strcmp(name, "renamelimit")) 191 else if (!strcmp(name, "renamelimit"))
188 ctx.cfg.renamelimit = atoi(value); 192 ctx.cfg.renamelimit = atoi(value);
189 else if (!strcmp(name, "robots")) 193 else if (!strcmp(name, "robots"))
190 ctx.cfg.robots = xstrdup(value); 194 ctx.cfg.robots = xstrdup(value);
191 else if (!strcmp(name, "clone-prefix")) 195 else if (!strcmp(name, "clone-prefix"))
192 ctx.cfg.clone_prefix = xstrdup(value); 196 ctx.cfg.clone_prefix = xstrdup(value);
193 else if (!strcmp(name, "local-time")) 197 else if (!strcmp(name, "local-time"))
194 ctx.cfg.local_time = atoi(value); 198 ctx.cfg.local_time = atoi(value);
195 else if (!prefixcmp(name, "mimetype.")) 199 else if (!prefixcmp(name, "mimetype."))
196 add_mimetype(name + 9, value); 200 add_mimetype(name + 9, value);
197 else if (!strcmp(name, "include")) 201 else if (!strcmp(name, "include"))
198 parse_configfile(value, config_cb); 202 parse_configfile(value, config_cb);
199} 203}
200 204
201static void querystring_cb(const char *name, const char *value) 205static void querystring_cb(const char *name, const char *value)
202{ 206{
203 if (!value) 207 if (!value)
204 value = ""; 208 value = "";
205 209
206 if (!strcmp(name,"r")) { 210 if (!strcmp(name,"r")) {
207 ctx.qry.repo = xstrdup(value); 211 ctx.qry.repo = xstrdup(value);
208 ctx.repo = cgit_get_repoinfo(value); 212 ctx.repo = cgit_get_repoinfo(value);
209 } else if (!strcmp(name, "p")) { 213 } else if (!strcmp(name, "p")) {
210 ctx.qry.page = xstrdup(value); 214 ctx.qry.page = xstrdup(value);
211 } else if (!strcmp(name, "url")) { 215 } else if (!strcmp(name, "url")) {
212 if (*value == '/') 216 if (*value == '/')
213 value++; 217 value++;
214 ctx.qry.url = xstrdup(value); 218 ctx.qry.url = xstrdup(value);
215 cgit_parse_url(value); 219 cgit_parse_url(value);
216 } else if (!strcmp(name, "qt")) { 220 } else if (!strcmp(name, "qt")) {
217 ctx.qry.grep = xstrdup(value); 221 ctx.qry.grep = xstrdup(value);
218 } else if (!strcmp(name, "q")) { 222 } else if (!strcmp(name, "q")) {
219 ctx.qry.search = xstrdup(value); 223 ctx.qry.search = xstrdup(value);
220 } else if (!strcmp(name, "h")) { 224 } else if (!strcmp(name, "h")) {
221 ctx.qry.head = xstrdup(value); 225 ctx.qry.head = xstrdup(value);
222 ctx.qry.has_symref = 1; 226 ctx.qry.has_symref = 1;
223 } else if (!strcmp(name, "id")) { 227 } else if (!strcmp(name, "id")) {
224 ctx.qry.sha1 = xstrdup(value); 228 ctx.qry.sha1 = xstrdup(value);
225 ctx.qry.has_sha1 = 1; 229 ctx.qry.has_sha1 = 1;
226 } else if (!strcmp(name, "id2")) { 230 } else if (!strcmp(name, "id2")) {
227 ctx.qry.sha2 = xstrdup(value); 231 ctx.qry.sha2 = xstrdup(value);
228 ctx.qry.has_sha1 = 1; 232 ctx.qry.has_sha1 = 1;
229 } else if (!strcmp(name, "ofs")) { 233 } else if (!strcmp(name, "ofs")) {
230 ctx.qry.ofs = atoi(value); 234 ctx.qry.ofs = atoi(value);
231 } else if (!strcmp(name, "path")) { 235 } else if (!strcmp(name, "path")) {
232 ctx.qry.path = trim_end(value, '/'); 236 ctx.qry.path = trim_end(value, '/');
233 } else if (!strcmp(name, "name")) { 237 } else if (!strcmp(name, "name")) {
234 ctx.qry.name = xstrdup(value); 238 ctx.qry.name = xstrdup(value);
235 } else if (!strcmp(name, "mimetype")) { 239 } else if (!strcmp(name, "mimetype")) {
236 ctx.qry.mimetype = xstrdup(value); 240 ctx.qry.mimetype = xstrdup(value);
237 } else if (!strcmp(name, "s")){ 241 } else if (!strcmp(name, "s")){
238 ctx.qry.sort = xstrdup(value); 242 ctx.qry.sort = xstrdup(value);
239 } else if (!strcmp(name, "showmsg")) { 243 } else if (!strcmp(name, "showmsg")) {
240 ctx.qry.showmsg = atoi(value); 244 ctx.qry.showmsg = atoi(value);
241 } else if (!strcmp(name, "period")) { 245 } else if (!strcmp(name, "period")) {
242 ctx.qry.period = xstrdup(value); 246 ctx.qry.period = xstrdup(value);
243 } 247 }
244} 248}
245 249
246char *xstrdupn(const char *str) 250char *xstrdupn(const char *str)
247{ 251{
248 return (str ? xstrdup(str) : NULL); 252 return (str ? xstrdup(str) : NULL);
249} 253}
250 254
251static void prepare_context(struct cgit_context *ctx) 255static void prepare_context(struct cgit_context *ctx)
252{ 256{
253 memset(ctx, 0, sizeof(ctx)); 257 memset(ctx, 0, sizeof(ctx));
254 ctx->cfg.agefile = "info/web/last-modified"; 258 ctx->cfg.agefile = "info/web/last-modified";
255 ctx->cfg.nocache = 0; 259 ctx->cfg.nocache = 0;
256 ctx->cfg.cache_size = 0; 260 ctx->cfg.cache_size = 0;
257 ctx->cfg.cache_dynamic_ttl = 5; 261 ctx->cfg.cache_dynamic_ttl = 5;
258 ctx->cfg.cache_max_create_time = 5; 262 ctx->cfg.cache_max_create_time = 5;
259 ctx->cfg.cache_repo_ttl = 5; 263 ctx->cfg.cache_repo_ttl = 5;
260 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 264 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
261 ctx->cfg.cache_root_ttl = 5; 265 ctx->cfg.cache_root_ttl = 5;
262 ctx->cfg.cache_scanrc_ttl = 15; 266 ctx->cfg.cache_scanrc_ttl = 15;
263 ctx->cfg.cache_static_ttl = -1; 267 ctx->cfg.cache_static_ttl = -1;
264 ctx->cfg.css = "/cgit.css"; 268 ctx->cfg.css = "/cgit.css";
265 ctx->cfg.logo = "/cgit.png"; 269 ctx->cfg.logo = "/cgit.png";
266 ctx->cfg.local_time = 0; 270 ctx->cfg.local_time = 0;
267 ctx->cfg.enable_tree_linenumbers = 1; 271 ctx->cfg.enable_tree_linenumbers = 1;
268 ctx->cfg.max_repo_count = 50; 272 ctx->cfg.max_repo_count = 50;
269 ctx->cfg.max_commit_count = 50; 273 ctx->cfg.max_commit_count = 50;
270 ctx->cfg.max_lock_attempts = 5; 274 ctx->cfg.max_lock_attempts = 5;
271 ctx->cfg.max_msg_len = 80; 275 ctx->cfg.max_msg_len = 80;
272 ctx->cfg.max_repodesc_len = 80; 276 ctx->cfg.max_repodesc_len = 80;
273 ctx->cfg.max_stats = 0; 277 ctx->cfg.max_stats = 0;
274 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 278 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
275 ctx->cfg.renamelimit = -1; 279 ctx->cfg.renamelimit = -1;
276 ctx->cfg.robots = "index, nofollow"; 280 ctx->cfg.robots = "index, nofollow";
277 ctx->cfg.root_title = "Git repository browser"; 281 ctx->cfg.root_title = "Git repository browser";
278 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 282 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
279 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 283 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
280 ctx->cfg.section = ""; 284 ctx->cfg.section = "";
281 ctx->cfg.summary_branches = 10; 285 ctx->cfg.summary_branches = 10;
282 ctx->cfg.summary_log = 10; 286 ctx->cfg.summary_log = 10;
283 ctx->cfg.summary_tags = 10; 287 ctx->cfg.summary_tags = 10;
284 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 288 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
285 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 289 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
286 ctx->env.https = xstrdupn(getenv("HTTPS")); 290 ctx->env.https = xstrdupn(getenv("HTTPS"));
287 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 291 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
288 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 292 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
289 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 293 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
290 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 294 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
291 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 295 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
292 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 296 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
293 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 297 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
294 ctx->page.mimetype = "text/html"; 298 ctx->page.mimetype = "text/html";
295 ctx->page.charset = PAGE_ENCODING; 299 ctx->page.charset = PAGE_ENCODING;
296 ctx->page.filename = NULL; 300 ctx->page.filename = NULL;
297 ctx->page.size = 0; 301 ctx->page.size = 0;
298 ctx->page.modified = time(NULL); 302 ctx->page.modified = time(NULL);
299 ctx->page.expires = ctx->page.modified; 303 ctx->page.expires = ctx->page.modified;
300 ctx->page.etag = NULL; 304 ctx->page.etag = NULL;
301 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 305 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
302 if (ctx->env.script_name) 306 if (ctx->env.script_name)
303 ctx->cfg.script_name = ctx->env.script_name; 307 ctx->cfg.script_name = ctx->env.script_name;
304 if (ctx->env.query_string) 308 if (ctx->env.query_string)
305 ctx->qry.raw = ctx->env.query_string; 309 ctx->qry.raw = ctx->env.query_string;
306 if (!ctx->env.cgit_config) 310 if (!ctx->env.cgit_config)
307 ctx->env.cgit_config = CGIT_CONFIG; 311 ctx->env.cgit_config = CGIT_CONFIG;
308} 312}
309 313
310struct refmatch { 314struct refmatch {
311 char *req_ref; 315 char *req_ref;
312 char *first_ref; 316 char *first_ref;
313 int match; 317 int match;
314}; 318};
315 319
316int find_current_ref(const char *refname, const unsigned char *sha1, 320int find_current_ref(const char *refname, const unsigned char *sha1,
317 int flags, void *cb_data) 321 int flags, void *cb_data)
318{ 322{
319 struct refmatch *info; 323 struct refmatch *info;
320 324
321 info = (struct refmatch *)cb_data; 325 info = (struct refmatch *)cb_data;
322 if (!strcmp(refname, info->req_ref)) 326 if (!strcmp(refname, info->req_ref))
323 info->match = 1; 327 info->match = 1;
324 if (!info->first_ref) 328 if (!info->first_ref)
325 info->first_ref = xstrdup(refname); 329 info->first_ref = xstrdup(refname);
326 return info->match; 330 return info->match;
327} 331}
328 332
329char *find_default_branch(struct cgit_repo *repo) 333char *find_default_branch(struct cgit_repo *repo)
330{ 334{
331 struct refmatch info; 335 struct refmatch info;
332 char *ref; 336 char *ref;
333 337
334 info.req_ref = repo->defbranch; 338 info.req_ref = repo->defbranch;
335 info.first_ref = NULL; 339 info.first_ref = NULL;
336 info.match = 0; 340 info.match = 0;
337 for_each_branch_ref(find_current_ref, &info); 341 for_each_branch_ref(find_current_ref, &info);
338 if (info.match) 342 if (info.match)
339 ref = info.req_ref; 343 ref = info.req_ref;
340 else 344 else
341 ref = info.first_ref; 345 ref = info.first_ref;
342 if (ref) 346 if (ref)
343 ref = xstrdup(ref); 347 ref = xstrdup(ref);
344 return ref; 348 return ref;
345} 349}
346 350
347static int prepare_repo_cmd(struct cgit_context *ctx) 351static int prepare_repo_cmd(struct cgit_context *ctx)
348{ 352{
349 char *tmp; 353 char *tmp;
350 unsigned char sha1[20]; 354 unsigned char sha1[20];
351 int nongit = 0; 355 int nongit = 0;
352 356
353 setenv("GIT_DIR", ctx->repo->path, 1); 357 setenv("GIT_DIR", ctx->repo->path, 1);
354 setup_git_directory_gently(&nongit); 358 setup_git_directory_gently(&nongit);
355 if (nongit) { 359 if (nongit) {
356 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, 360 ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
357 "config error"); 361 "config error");
358 tmp = fmt("Not a git repository: '%s'", ctx->repo->path); 362 tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
359 ctx->repo = NULL; 363 ctx->repo = NULL;
360 cgit_print_http_headers(ctx); 364 cgit_print_http_headers(ctx);
361 cgit_print_docstart(ctx); 365 cgit_print_docstart(ctx);
362 cgit_print_pageheader(ctx); 366 cgit_print_pageheader(ctx);
363 cgit_print_error(tmp); 367 cgit_print_error(tmp);
364 cgit_print_docend(); 368 cgit_print_docend();
365 return 1; 369 return 1;
366 } 370 }
367 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 371 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
368 372
369 if (!ctx->qry.head) { 373 if (!ctx->qry.head) {
370 ctx->qry.nohead = 1; 374 ctx->qry.nohead = 1;
371 ctx->qry.head = find_default_branch(ctx->repo); 375 ctx->qry.head = find_default_branch(ctx->repo);
372 ctx->repo->defbranch = ctx->qry.head; 376 ctx->repo->defbranch = ctx->qry.head;
373 } 377 }
374 378
375 if (!ctx->qry.head) { 379 if (!ctx->qry.head) {
376 cgit_print_http_headers(ctx); 380 cgit_print_http_headers(ctx);
377 cgit_print_docstart(ctx); 381 cgit_print_docstart(ctx);
378 cgit_print_pageheader(ctx); 382 cgit_print_pageheader(ctx);
379 cgit_print_error("Repository seems to be empty"); 383 cgit_print_error("Repository seems to be empty");
380 cgit_print_docend(); 384 cgit_print_docend();
381 return 1; 385 return 1;
382 } 386 }
383 387
384 if (get_sha1(ctx->qry.head, sha1)) { 388 if (get_sha1(ctx->qry.head, sha1)) {
385 tmp = xstrdup(ctx->qry.head); 389 tmp = xstrdup(ctx->qry.head);
386 ctx->qry.head = ctx->repo->defbranch; 390 ctx->qry.head = ctx->repo->defbranch;
387 ctx->page.status = 404; 391 ctx->page.status = 404;
388 ctx->page.statusmsg = "not found"; 392 ctx->page.statusmsg = "not found";
389 cgit_print_http_headers(ctx); 393 cgit_print_http_headers(ctx);
390 cgit_print_docstart(ctx); 394 cgit_print_docstart(ctx);
391 cgit_print_pageheader(ctx); 395 cgit_print_pageheader(ctx);
392 cgit_print_error(fmt("Invalid branch: %s", tmp)); 396 cgit_print_error(fmt("Invalid branch: %s", tmp));
393 cgit_print_docend(); 397 cgit_print_docend();
394 return 1; 398 return 1;
395 } 399 }
396 return 0; 400 return 0;
397} 401}
398 402
399static void process_request(void *cbdata) 403static void process_request(void *cbdata)
400{ 404{
401 struct cgit_context *ctx = cbdata; 405 struct cgit_context *ctx = cbdata;
402 struct cgit_cmd *cmd; 406 struct cgit_cmd *cmd;
403 407
404 cmd = cgit_get_cmd(ctx); 408 cmd = cgit_get_cmd(ctx);
405 if (!cmd) { 409 if (!cmd) {
406 ctx->page.title = "cgit error"; 410 ctx->page.title = "cgit error";
407 cgit_print_http_headers(ctx); 411 cgit_print_http_headers(ctx);
408 cgit_print_docstart(ctx); 412 cgit_print_docstart(ctx);
409 cgit_print_pageheader(ctx); 413 cgit_print_pageheader(ctx);
410 cgit_print_error("Invalid request"); 414 cgit_print_error("Invalid request");
411 cgit_print_docend(); 415 cgit_print_docend();
412 return; 416 return;
413 } 417 }
414 418
415 if (cmd->want_repo && !ctx->repo) { 419 if (cmd->want_repo && !ctx->repo) {
416 cgit_print_http_headers(ctx); 420 cgit_print_http_headers(ctx);
417 cgit_print_docstart(ctx); 421 cgit_print_docstart(ctx);
418 cgit_print_pageheader(ctx); 422 cgit_print_pageheader(ctx);
419 cgit_print_error(fmt("No repository selected")); 423 cgit_print_error(fmt("No repository selected"));
420 cgit_print_docend(); 424 cgit_print_docend();
421 return; 425 return;
422 } 426 }
423 427
424 if (ctx->repo && prepare_repo_cmd(ctx)) 428 if (ctx->repo && prepare_repo_cmd(ctx))
425 return; 429 return;
426 430
427 if (cmd->want_layout) { 431 if (cmd->want_layout) {
428 cgit_print_http_headers(ctx); 432 cgit_print_http_headers(ctx);
429 cgit_print_docstart(ctx); 433 cgit_print_docstart(ctx);
430 cgit_print_pageheader(ctx); 434 cgit_print_pageheader(ctx);
431 } 435 }
432 436
433 cmd->fn(ctx); 437 cmd->fn(ctx);
434 438
435 if (cmd->want_layout) 439 if (cmd->want_layout)
436 cgit_print_docend(); 440 cgit_print_docend();
437} 441}
438 442
439int cmp_repos(const void *a, const void *b) 443int cmp_repos(const void *a, const void *b)
440{ 444{
441 const struct cgit_repo *ra = a, *rb = b; 445 const struct cgit_repo *ra = a, *rb = b;
442 return strcmp(ra->url, rb->url); 446 return strcmp(ra->url, rb->url);
443} 447}
444 448
445char *build_snapshot_setting(int bitmap) 449char *build_snapshot_setting(int bitmap)
446{ 450{
447 const struct cgit_snapshot_format *f; 451 const struct cgit_snapshot_format *f;
448 char *result = xstrdup(""); 452 char *result = xstrdup("");
449 char *tmp; 453 char *tmp;
450 int len; 454 int len;
451 455
452 for (f = cgit_snapshot_formats; f->suffix; f++) { 456 for (f = cgit_snapshot_formats; f->suffix; f++) {
453 if (f->bit & bitmap) { 457 if (f->bit & bitmap) {
454 tmp = result; 458 tmp = result;
455 result = xstrdup(fmt("%s%s ", tmp, f->suffix)); 459 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
456 free(tmp); 460 free(tmp);
457 } 461 }
458 } 462 }
459 len = strlen(result); 463 len = strlen(result);
460 if (len) 464 if (len)
461 result[len - 1] = '\0'; 465 result[len - 1] = '\0';
462 return result; 466 return result;
463} 467}
464 468
465char *get_first_line(char *txt) 469char *get_first_line(char *txt)
466{ 470{
467 char *t = xstrdup(txt); 471 char *t = xstrdup(txt);
468 char *p = strchr(t, '\n'); 472 char *p = strchr(t, '\n');
469 if (p) 473 if (p)
470 *p = '\0'; 474 *p = '\0';
471 return t; 475 return t;
472} 476}
473 477
474void print_repo(FILE *f, struct cgit_repo *repo) 478void print_repo(FILE *f, struct cgit_repo *repo)
475{ 479{
476 fprintf(f, "repo.url=%s\n", repo->url); 480 fprintf(f, "repo.url=%s\n", repo->url);
477 fprintf(f, "repo.name=%s\n", repo->name); 481 fprintf(f, "repo.name=%s\n", repo->name);
478 fprintf(f, "repo.path=%s\n", repo->path); 482 fprintf(f, "repo.path=%s\n", repo->path);
479 if (repo->owner) 483 if (repo->owner)
480 fprintf(f, "repo.owner=%s\n", repo->owner); 484 fprintf(f, "repo.owner=%s\n", repo->owner);
481 if (repo->desc) { 485 if (repo->desc) {
482 char *tmp = get_first_line(repo->desc); 486 char *tmp = get_first_line(repo->desc);
483 fprintf(f, "repo.desc=%s\n", tmp); 487 fprintf(f, "repo.desc=%s\n", tmp);
484 free(tmp); 488 free(tmp);
485 } 489 }
486 if (repo->readme) 490 if (repo->readme)
487 fprintf(f, "repo.readme=%s\n", repo->readme); 491 fprintf(f, "repo.readme=%s\n", repo->readme);
488 if (repo->defbranch) 492 if (repo->defbranch)
489 fprintf(f, "repo.defbranch=%s\n", repo->defbranch); 493 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
490 if (repo->module_link) 494 if (repo->module_link)
491 fprintf(f, "repo.module-link=%s\n", repo->module_link); 495 fprintf(f, "repo.module-link=%s\n", repo->module_link);
492 if (repo->section) 496 if (repo->section)
493 fprintf(f, "repo.section=%s\n", repo->section); 497 fprintf(f, "repo.section=%s\n", repo->section);
494 if (repo->clone_url) 498 if (repo->clone_url)
495 fprintf(f, "repo.clone-url=%s\n", repo->clone_url); 499 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
496 fprintf(f, "repo.enable-log-filecount=%d\n", 500 fprintf(f, "repo.enable-log-filecount=%d\n",
497 repo->enable_log_filecount); 501 repo->enable_log_filecount);
498 fprintf(f, "repo.enable-log-linecount=%d\n", 502 fprintf(f, "repo.enable-log-linecount=%d\n",
499 repo->enable_log_linecount); 503 repo->enable_log_linecount);
500 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) 504 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
501 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd); 505 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
502 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) 506 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
503 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd); 507 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
504 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) 508 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
505 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd); 509 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
506 if (repo->snapshots != ctx.cfg.snapshots) { 510 if (repo->snapshots != ctx.cfg.snapshots) {
507 char *tmp = build_snapshot_setting(repo->snapshots); 511 char *tmp = build_snapshot_setting(repo->snapshots);
508 fprintf(f, "repo.snapshots=%s\n", tmp); 512 fprintf(f, "repo.snapshots=%s\n", tmp);
509 free(tmp); 513 free(tmp);
510 } 514 }
511 if (repo->max_stats != ctx.cfg.max_stats) 515 if (repo->max_stats != ctx.cfg.max_stats)
512 fprintf(f, "repo.max-stats=%s\n", 516 fprintf(f, "repo.max-stats=%s\n",
513 cgit_find_stats_periodname(repo->max_stats)); 517 cgit_find_stats_periodname(repo->max_stats));
514 fprintf(f, "\n"); 518 fprintf(f, "\n");
515} 519}
516 520
517void print_repolist(FILE *f, struct cgit_repolist *list, int start) 521void print_repolist(FILE *f, struct cgit_repolist *list, int start)
518{ 522{
519 int i; 523 int i;
520 524
521 for(i = start; i < list->count; i++) 525 for(i = start; i < list->count; i++)
522 print_repo(f, &list->repos[i]); 526 print_repo(f, &list->repos[i]);
523} 527}
524 528
525/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 529/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
526 * and return 0 on success. 530 * and return 0 on success.
527 */ 531 */
528static int generate_cached_repolist(const char *path, const char *cached_rc) 532static int generate_cached_repolist(const char *path, const char *cached_rc)
529{ 533{
530 char *locked_rc; 534 char *locked_rc;
531 int idx; 535 int idx;
532 FILE *f; 536 FILE *f;
533 537
534 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 538 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
535 f = fopen(locked_rc, "wx"); 539 f = fopen(locked_rc, "wx");
536 if (!f) { 540 if (!f) {
537 /* Inform about the error unless the lockfile already existed, 541 /* Inform about the error unless the lockfile already existed,
538 * since that only means we've got concurrent requests. 542 * since that only means we've got concurrent requests.
539 */ 543 */
540 if (errno != EEXIST) 544 if (errno != EEXIST)
541 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 545 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
542 locked_rc, strerror(errno), errno); 546 locked_rc, strerror(errno), errno);
543 return errno; 547 return errno;
544 } 548 }
545 idx = cgit_repolist.count; 549 idx = cgit_repolist.count;
546 scan_tree(path, repo_config); 550 scan_tree(path, repo_config);
547 print_repolist(f, &cgit_repolist, idx); 551 print_repolist(f, &cgit_repolist, idx);
548 if (rename(locked_rc, cached_rc)) 552 if (rename(locked_rc, cached_rc))
549 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 553 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
550 locked_rc, cached_rc, strerror(errno), errno); 554 locked_rc, cached_rc, strerror(errno), errno);
551 fclose(f); 555 fclose(f);
552 return 0; 556 return 0;
553} 557}
554 558
555static void process_cached_repolist(const char *path) 559static void process_cached_repolist(const char *path)
556{ 560{
557 struct stat st; 561 struct stat st;
558 char *cached_rc; 562 char *cached_rc;
559 time_t age; 563 time_t age;
560 564
561 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, 565 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root,
562 hash_str(path))); 566 hash_str(path)));
563 567
564 if (stat(cached_rc, &st)) { 568 if (stat(cached_rc, &st)) {
565 /* Nothing is cached, we need to scan without forking. And 569 /* Nothing is cached, we need to scan without forking. And
566 * if we fail to generate a cached repolist, we need to 570 * if we fail to generate a cached repolist, we need to
567 * invoke scan_tree manually. 571 * invoke scan_tree manually.
568 */ 572 */
569 if (generate_cached_repolist(path, cached_rc)) 573 if (generate_cached_repolist(path, cached_rc))
570 scan_tree(path, repo_config); 574 scan_tree(path, repo_config);
571 return; 575 return;
572 } 576 }
573 577
574 parse_configfile(cached_rc, config_cb); 578 parse_configfile(cached_rc, config_cb);
575 579
576 /* If the cached configfile hasn't expired, lets exit now */ 580 /* If the cached configfile hasn't expired, lets exit now */
577 age = time(NULL) - st.st_mtime; 581 age = time(NULL) - st.st_mtime;
578 if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) 582 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
579 return; 583 return;
580 584
581 /* The cached repolist has been parsed, but it was old. So lets 585 /* The cached repolist has been parsed, but it was old. So lets
582 * rescan the specified path and generate a new cached repolist 586 * rescan the specified path and generate a new cached repolist
583 * in a child-process to avoid latency for the current request. 587 * in a child-process to avoid latency for the current request.
584 */ 588 */
585 if (fork()) 589 if (fork())
586 return; 590 return;
587 591
588 exit(generate_cached_repolist(path, cached_rc)); 592 exit(generate_cached_repolist(path, cached_rc));
589} 593}
590 594
591static void cgit_parse_args(int argc, const char **argv) 595static void cgit_parse_args(int argc, const char **argv)
592{ 596{
593 int i; 597 int i;
594 int scan = 0; 598 int scan = 0;
595 599
596 for (i = 1; i < argc; i++) { 600 for (i = 1; i < argc; i++) {
597 if (!strncmp(argv[i], "--cache=", 8)) { 601 if (!strncmp(argv[i], "--cache=", 8)) {
598 ctx.cfg.cache_root = xstrdup(argv[i]+8); 602 ctx.cfg.cache_root = xstrdup(argv[i]+8);
599 } 603 }
600 if (!strcmp(argv[i], "--nocache")) { 604 if (!strcmp(argv[i], "--nocache")) {
601 ctx.cfg.nocache = 1; 605 ctx.cfg.nocache = 1;
602 } 606 }
603 if (!strcmp(argv[i], "--nohttp")) { 607 if (!strcmp(argv[i], "--nohttp")) {
604 ctx.env.no_http = "1"; 608 ctx.env.no_http = "1";
605 } 609 }
606 if (!strncmp(argv[i], "--query=", 8)) { 610 if (!strncmp(argv[i], "--query=", 8)) {
607 ctx.qry.raw = xstrdup(argv[i]+8); 611 ctx.qry.raw = xstrdup(argv[i]+8);
608 } 612 }
609 if (!strncmp(argv[i], "--repo=", 7)) { 613 if (!strncmp(argv[i], "--repo=", 7)) {
610 ctx.qry.repo = xstrdup(argv[i]+7); 614 ctx.qry.repo = xstrdup(argv[i]+7);
611 } 615 }
612 if (!strncmp(argv[i], "--page=", 7)) { 616 if (!strncmp(argv[i], "--page=", 7)) {
613 ctx.qry.page = xstrdup(argv[i]+7); 617 ctx.qry.page = xstrdup(argv[i]+7);
614 } 618 }
615 if (!strncmp(argv[i], "--head=", 7)) { 619 if (!strncmp(argv[i], "--head=", 7)) {
616 ctx.qry.head = xstrdup(argv[i]+7); 620 ctx.qry.head = xstrdup(argv[i]+7);
617 ctx.qry.has_symref = 1; 621 ctx.qry.has_symref = 1;
618 } 622 }
619 if (!strncmp(argv[i], "--sha1=", 7)) { 623 if (!strncmp(argv[i], "--sha1=", 7)) {
620 ctx.qry.sha1 = xstrdup(argv[i]+7); 624 ctx.qry.sha1 = xstrdup(argv[i]+7);
621 ctx.qry.has_sha1 = 1; 625 ctx.qry.has_sha1 = 1;
622 } 626 }
623 if (!strncmp(argv[i], "--ofs=", 6)) { 627 if (!strncmp(argv[i], "--ofs=", 6)) {
624 ctx.qry.ofs = atoi(argv[i]+6); 628 ctx.qry.ofs = atoi(argv[i]+6);
625 } 629 }
626 if (!strncmp(argv[i], "--scan-tree=", 12) || 630 if (!strncmp(argv[i], "--scan-tree=", 12) ||
627 !strncmp(argv[i], "--scan-path=", 12)) { 631 !strncmp(argv[i], "--scan-path=", 12)) {
628 /* HACK: the global snapshot bitmask defines the 632 /* HACK: the global snapshot bitmask defines the
629 * set of allowed snapshot formats, but the config 633 * set of allowed snapshot formats, but the config
630 * file hasn't been parsed yet so the mask is 634 * file hasn't been parsed yet so the mask is
631 * currently 0. By setting all bits high before 635 * currently 0. By setting all bits high before
632 * scanning we make sure that any in-repo cgitrc 636 * scanning we make sure that any in-repo cgitrc
633 * snapshot setting is respected by scan_tree(). 637 * snapshot setting is respected by scan_tree().
634 * BTW: we assume that there'll never be more than 638 * BTW: we assume that there'll never be more than
635 * 255 different snapshot formats supported by cgit... 639 * 255 different snapshot formats supported by cgit...
636 */ 640 */
637 ctx.cfg.snapshots = 0xFF; 641 ctx.cfg.snapshots = 0xFF;
638 scan++; 642 scan++;
639 scan_tree(argv[i] + 12, repo_config); 643 scan_tree(argv[i] + 12, repo_config);
640 } 644 }
641 } 645 }
642 if (scan) { 646 if (scan) {
643 qsort(cgit_repolist.repos, cgit_repolist.count, 647 qsort(cgit_repolist.repos, cgit_repolist.count,
644 sizeof(struct cgit_repo), cmp_repos); 648 sizeof(struct cgit_repo), cmp_repos);
645 print_repolist(stdout, &cgit_repolist, 0); 649 print_repolist(stdout, &cgit_repolist, 0);
646 exit(0); 650 exit(0);
647 } 651 }
648} 652}
649 653
650static int calc_ttl() 654static int calc_ttl()
651{ 655{
652 if (!ctx.repo) 656 if (!ctx.repo)
653 return ctx.cfg.cache_root_ttl; 657 return ctx.cfg.cache_root_ttl;
654 658
655 if (!ctx.qry.page) 659 if (!ctx.qry.page)
656 return ctx.cfg.cache_repo_ttl; 660 return ctx.cfg.cache_repo_ttl;
657 661
658 if (ctx.qry.has_symref) 662 if (ctx.qry.has_symref)
659 return ctx.cfg.cache_dynamic_ttl; 663 return ctx.cfg.cache_dynamic_ttl;
660 664
661 if (ctx.qry.has_sha1) 665 if (ctx.qry.has_sha1)
662 return ctx.cfg.cache_static_ttl; 666 return ctx.cfg.cache_static_ttl;
663 667
664 return ctx.cfg.cache_repo_ttl; 668 return ctx.cfg.cache_repo_ttl;
665} 669}
666 670
667int main(int argc, const char **argv) 671int main(int argc, const char **argv)
668{ 672{
669 const char *path; 673 const char *path;
670 char *qry; 674 char *qry;
671 int err, ttl; 675 int err, ttl;
672 676
673 prepare_context(&ctx); 677 prepare_context(&ctx);
674 cgit_repolist.length = 0; 678 cgit_repolist.length = 0;
675 cgit_repolist.count = 0; 679 cgit_repolist.count = 0;
676 cgit_repolist.repos = NULL; 680 cgit_repolist.repos = NULL;
677 681
678 cgit_parse_args(argc, argv); 682 cgit_parse_args(argc, argv);
679 parse_configfile(ctx.env.cgit_config, config_cb); 683 parse_configfile(ctx.env.cgit_config, config_cb);
680 ctx.repo = NULL; 684 ctx.repo = NULL;
681 http_parse_querystring(ctx.qry.raw, querystring_cb); 685 http_parse_querystring(ctx.qry.raw, querystring_cb);
682 686
683 /* If virtual-root isn't specified in cgitrc, lets pretend 687 /* If virtual-root isn't specified in cgitrc, lets pretend
684 * that virtual-root equals SCRIPT_NAME. 688 * that virtual-root equals SCRIPT_NAME.
685 */ 689 */
686 if (!ctx.cfg.virtual_root) 690 if (!ctx.cfg.virtual_root)
687 ctx.cfg.virtual_root = ctx.cfg.script_name; 691 ctx.cfg.virtual_root = ctx.cfg.script_name;
688 692
689 /* If no url parameter is specified on the querystring, lets 693 /* If no url parameter is specified on the querystring, lets
690 * use PATH_INFO as url. This allows cgit to work with virtual 694 * use PATH_INFO as url. This allows cgit to work with virtual
691 * urls without the need for rewriterules in the webserver (as 695 * urls without the need for rewriterules in the webserver (as
692 * long as PATH_INFO is included in the cache lookup key). 696 * long as PATH_INFO is included in the cache lookup key).
693 */ 697 */
694 path = ctx.env.path_info; 698 path = ctx.env.path_info;
695 if (!ctx.qry.url && path) { 699 if (!ctx.qry.url && path) {
696 if (path[0] == '/') 700 if (path[0] == '/')
697 path++; 701 path++;
698 ctx.qry.url = xstrdup(path); 702 ctx.qry.url = xstrdup(path);
699 if (ctx.qry.raw) { 703 if (ctx.qry.raw) {
700 qry = ctx.qry.raw; 704 qry = ctx.qry.raw;
701 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 705 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
702 free(qry); 706 free(qry);
703 } else 707 } else
704 ctx.qry.raw = xstrdup(ctx.qry.url); 708 ctx.qry.raw = xstrdup(ctx.qry.url);
705 cgit_parse_url(ctx.qry.url); 709 cgit_parse_url(ctx.qry.url);
706 } 710 }
707 711
708 ttl = calc_ttl(); 712 ttl = calc_ttl();
709 ctx.page.expires += ttl*60; 713 ctx.page.expires += ttl*60;
710 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) 714 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
711 ctx.cfg.nocache = 1; 715 ctx.cfg.nocache = 1;
712 if (ctx.cfg.nocache) 716 if (ctx.cfg.nocache)
713 ctx.cfg.cache_size = 0; 717 ctx.cfg.cache_size = 0;
714 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 718 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
715 ctx.qry.raw, ttl, process_request, &ctx); 719 ctx.qry.raw, ttl, process_request, &ctx);
716 if (err) 720 if (err)
717 cgit_print_error(fmt("Error processing page: %s (%d)", 721 cgit_print_error(fmt("Error processing page: %s (%d)",
718 strerror(err), err)); 722 strerror(err), err));
719 return err; 723 return err;
720} 724}
diff --git a/cgit.h b/cgit.h
index 6c6c460..1de2335 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,294 +1,296 @@
1#ifndef CGIT_H 1#ifndef CGIT_H
2#define CGIT_H 2#define CGIT_H
3 3
4 4
5#include <git-compat-util.h> 5#include <git-compat-util.h>
6#include <cache.h> 6#include <cache.h>
7#include <grep.h> 7#include <grep.h>
8#include <object.h> 8#include <object.h>
9#include <tree.h> 9#include <tree.h>
10#include <commit.h> 10#include <commit.h>
11#include <tag.h> 11#include <tag.h>
12#include <diff.h> 12#include <diff.h>
13#include <diffcore.h> 13#include <diffcore.h>
14#include <refs.h> 14#include <refs.h>
15#include <revision.h> 15#include <revision.h>
16#include <log-tree.h> 16#include <log-tree.h>
17#include <archive.h> 17#include <archive.h>
18#include <string-list.h> 18#include <string-list.h>
19#include <xdiff-interface.h> 19#include <xdiff-interface.h>
20#include <xdiff/xdiff.h> 20#include <xdiff/xdiff.h>
21#include <utf8.h> 21#include <utf8.h>
22 22
23 23
24/* 24/*
25 * Dateformats used on misc. pages 25 * Dateformats used on misc. pages
26 */ 26 */
27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" 27#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
28#define FMT_SHORTDATE "%Y-%m-%d" 28#define FMT_SHORTDATE "%Y-%m-%d"
29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" 29#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
30 30
31 31
32/* 32/*
33 * Limits used for relative dates 33 * Limits used for relative dates
34 */ 34 */
35#define TM_MIN 60 35#define TM_MIN 60
36#define TM_HOUR (TM_MIN * 60) 36#define TM_HOUR (TM_MIN * 60)
37#define TM_DAY (TM_HOUR * 24) 37#define TM_DAY (TM_HOUR * 24)
38#define TM_WEEK (TM_DAY * 7) 38#define TM_WEEK (TM_DAY * 7)
39#define TM_YEAR (TM_DAY * 365) 39#define TM_YEAR (TM_DAY * 365)
40#define TM_MONTH (TM_YEAR / 12.0) 40#define TM_MONTH (TM_YEAR / 12.0)
41 41
42 42
43/* 43/*
44 * Default encoding 44 * Default encoding
45 */ 45 */
46#define PAGE_ENCODING "UTF-8" 46#define PAGE_ENCODING "UTF-8"
47 47
48typedef void (*configfn)(const char *name, const char *value); 48typedef void (*configfn)(const char *name, const char *value);
49typedef void (*filepair_fn)(struct diff_filepair *pair); 49typedef void (*filepair_fn)(struct diff_filepair *pair);
50typedef void (*linediff_fn)(char *line, int len); 50typedef void (*linediff_fn)(char *line, int len);
51 51
52struct cgit_filter { 52struct cgit_filter {
53 char *cmd; 53 char *cmd;
54 char **argv; 54 char **argv;
55 int old_stdout; 55 int old_stdout;
56 int pipe_fh[2]; 56 int pipe_fh[2];
57 int pid; 57 int pid;
58 int exitstatus; 58 int exitstatus;
59}; 59};
60 60
61struct cgit_repo { 61struct cgit_repo {
62 char *url; 62 char *url;
63 char *name; 63 char *name;
64 char *path; 64 char *path;
65 char *desc; 65 char *desc;
66 char *owner; 66 char *owner;
67 char *defbranch; 67 char *defbranch;
68 char *module_link; 68 char *module_link;
69 char *readme; 69 char *readme;
70 char *section; 70 char *section;
71 char *clone_url; 71 char *clone_url;
72 int snapshots; 72 int snapshots;
73 int enable_log_filecount; 73 int enable_log_filecount;
74 int enable_log_linecount; 74 int enable_log_linecount;
75 int enable_remote_branches;
75 int max_stats; 76 int max_stats;
76 time_t mtime; 77 time_t mtime;
77 struct cgit_filter *about_filter; 78 struct cgit_filter *about_filter;
78 struct cgit_filter *commit_filter; 79 struct cgit_filter *commit_filter;
79 struct cgit_filter *source_filter; 80 struct cgit_filter *source_filter;
80}; 81};
81 82
82typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, 83typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
83 const char *value); 84 const char *value);
84 85
85struct cgit_repolist { 86struct cgit_repolist {
86 int length; 87 int length;
87 int count; 88 int count;
88 struct cgit_repo *repos; 89 struct cgit_repo *repos;
89}; 90};
90 91
91struct commitinfo { 92struct commitinfo {
92 struct commit *commit; 93 struct commit *commit;
93 char *author; 94 char *author;
94 char *author_email; 95 char *author_email;
95 unsigned long author_date; 96 unsigned long author_date;
96 char *committer; 97 char *committer;
97 char *committer_email; 98 char *committer_email;
98 unsigned long committer_date; 99 unsigned long committer_date;
99 char *subject; 100 char *subject;
100 char *msg; 101 char *msg;
101 char *msg_encoding; 102 char *msg_encoding;
102}; 103};
103 104
104struct taginfo { 105struct taginfo {
105 char *tagger; 106 char *tagger;
106 char *tagger_email; 107 char *tagger_email;
107 unsigned long tagger_date; 108 unsigned long tagger_date;
108 char *msg; 109 char *msg;
109}; 110};
110 111
111struct refinfo { 112struct refinfo {
112 const char *refname; 113 const char *refname;
113 struct object *object; 114 struct object *object;
114 union { 115 union {
115 struct taginfo *tag; 116 struct taginfo *tag;
116 struct commitinfo *commit; 117 struct commitinfo *commit;
117 }; 118 };
118}; 119};
119 120
120struct reflist { 121struct reflist {
121 struct refinfo **refs; 122 struct refinfo **refs;
122 int alloc; 123 int alloc;
123 int count; 124 int count;
124}; 125};
125 126
126struct cgit_query { 127struct cgit_query {
127 int has_symref; 128 int has_symref;
128 int has_sha1; 129 int has_sha1;
129 char *raw; 130 char *raw;
130 char *repo; 131 char *repo;
131 char *page; 132 char *page;
132 char *search; 133 char *search;
133 char *grep; 134 char *grep;
134 char *head; 135 char *head;
135 char *sha1; 136 char *sha1;
136 char *sha2; 137 char *sha2;
137 char *path; 138 char *path;
138 char *name; 139 char *name;
139 char *mimetype; 140 char *mimetype;
140 char *url; 141 char *url;
141 char *period; 142 char *period;
142 int ofs; 143 int ofs;
143 int nohead; 144 int nohead;
144 char *sort; 145 char *sort;
145 int showmsg; 146 int showmsg;
146}; 147};
147 148
148struct cgit_config { 149struct cgit_config {
149 char *agefile; 150 char *agefile;
150 char *cache_root; 151 char *cache_root;
151 char *clone_prefix; 152 char *clone_prefix;
152 char *css; 153 char *css;
153 char *favicon; 154 char *favicon;
154 char *footer; 155 char *footer;
155 char *head_include; 156 char *head_include;
156 char *header; 157 char *header;
157 char *index_header; 158 char *index_header;
158 char *index_info; 159 char *index_info;
159 char *logo; 160 char *logo;
160 char *logo_link; 161 char *logo_link;
161 char *module_link; 162 char *module_link;
162 char *robots; 163 char *robots;
163 char *root_title; 164 char *root_title;
164 char *root_desc; 165 char *root_desc;
165 char *root_readme; 166 char *root_readme;
166 char *script_name; 167 char *script_name;
167 char *section; 168 char *section;
168 char *virtual_root; 169 char *virtual_root;
169 int cache_size; 170 int cache_size;
170 int cache_dynamic_ttl; 171 int cache_dynamic_ttl;
171 int cache_max_create_time; 172 int cache_max_create_time;
172 int cache_repo_ttl; 173 int cache_repo_ttl;
173 int cache_root_ttl; 174 int cache_root_ttl;
174 int cache_scanrc_ttl; 175 int cache_scanrc_ttl;
175 int cache_static_ttl; 176 int cache_static_ttl;
176 int embedded; 177 int embedded;
177 int enable_filter_overrides; 178 int enable_filter_overrides;
178 int enable_index_links; 179 int enable_index_links;
179 int enable_log_filecount; 180 int enable_log_filecount;
180 int enable_log_linecount; 181 int enable_log_linecount;
182 int enable_remote_branches;
181 int enable_tree_linenumbers; 183 int enable_tree_linenumbers;
182 int local_time; 184 int local_time;
183 int max_repo_count; 185 int max_repo_count;
184 int max_commit_count; 186 int max_commit_count;
185 int max_lock_attempts; 187 int max_lock_attempts;
186 int max_msg_len; 188 int max_msg_len;
187 int max_repodesc_len; 189 int max_repodesc_len;
188 int max_stats; 190 int max_stats;
189 int nocache; 191 int nocache;
190 int noplainemail; 192 int noplainemail;
191 int noheader; 193 int noheader;
192 int renamelimit; 194 int renamelimit;
193 int snapshots; 195 int snapshots;
194 int summary_branches; 196 int summary_branches;
195 int summary_log; 197 int summary_log;
196 int summary_tags; 198 int summary_tags;
197 struct string_list mimetypes; 199 struct string_list mimetypes;
198 struct cgit_filter *about_filter; 200 struct cgit_filter *about_filter;
199 struct cgit_filter *commit_filter; 201 struct cgit_filter *commit_filter;
200 struct cgit_filter *source_filter; 202 struct cgit_filter *source_filter;
201}; 203};
202 204
203struct cgit_page { 205struct cgit_page {
204 time_t modified; 206 time_t modified;
205 time_t expires; 207 time_t expires;
206 size_t size; 208 size_t size;
207 char *mimetype; 209 char *mimetype;
208 char *charset; 210 char *charset;
209 char *filename; 211 char *filename;
210 char *etag; 212 char *etag;
211 char *title; 213 char *title;
212 int status; 214 int status;
213 char *statusmsg; 215 char *statusmsg;
214}; 216};
215 217
216struct cgit_environment { 218struct cgit_environment {
217 char *cgit_config; 219 char *cgit_config;
218 char *http_host; 220 char *http_host;
219 char *https; 221 char *https;
220 char *no_http; 222 char *no_http;
221 char *path_info; 223 char *path_info;
222 char *query_string; 224 char *query_string;
223 char *request_method; 225 char *request_method;
224 char *script_name; 226 char *script_name;
225 char *server_name; 227 char *server_name;
226 char *server_port; 228 char *server_port;
227}; 229};
228 230
229struct cgit_context { 231struct cgit_context {
230 struct cgit_environment env; 232 struct cgit_environment env;
231 struct cgit_query qry; 233 struct cgit_query qry;
232 struct cgit_config cfg; 234 struct cgit_config cfg;
233 struct cgit_repo *repo; 235 struct cgit_repo *repo;
234 struct cgit_page page; 236 struct cgit_page page;
235}; 237};
236 238
237struct cgit_snapshot_format { 239struct cgit_snapshot_format {
238 const char *suffix; 240 const char *suffix;
239 const char *mimetype; 241 const char *mimetype;
240 write_archive_fn_t write_func; 242 write_archive_fn_t write_func;
241 int bit; 243 int bit;
242}; 244};
243 245
244extern const char *cgit_version; 246extern const char *cgit_version;
245 247
246extern struct cgit_repolist cgit_repolist; 248extern struct cgit_repolist cgit_repolist;
247extern struct cgit_context ctx; 249extern struct cgit_context ctx;
248extern const struct cgit_snapshot_format cgit_snapshot_formats[]; 250extern const struct cgit_snapshot_format cgit_snapshot_formats[];
249 251
250extern struct cgit_repo *cgit_add_repo(const char *url); 252extern struct cgit_repo *cgit_add_repo(const char *url);
251extern struct cgit_repo *cgit_get_repoinfo(const char *url); 253extern struct cgit_repo *cgit_get_repoinfo(const char *url);
252extern void cgit_repo_config_cb(const char *name, const char *value); 254extern void cgit_repo_config_cb(const char *name, const char *value);
253 255
254extern int chk_zero(int result, char *msg); 256extern int chk_zero(int result, char *msg);
255extern int chk_positive(int result, char *msg); 257extern int chk_positive(int result, char *msg);
256extern int chk_non_negative(int result, char *msg); 258extern int chk_non_negative(int result, char *msg);
257 259
258extern char *trim_end(const char *str, char c); 260extern char *trim_end(const char *str, char c);
259extern char *strlpart(char *txt, int maxlen); 261extern char *strlpart(char *txt, int maxlen);
260extern char *strrpart(char *txt, int maxlen); 262extern char *strrpart(char *txt, int maxlen);
261 263
262extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); 264extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
263extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, 265extern int cgit_refs_cb(const char *refname, const unsigned char *sha1,
264 int flags, void *cb_data); 266 int flags, void *cb_data);
265 267
266extern void *cgit_free_commitinfo(struct commitinfo *info); 268extern void *cgit_free_commitinfo(struct commitinfo *info);
267 269
268extern int cgit_diff_files(const unsigned char *old_sha1, 270extern int cgit_diff_files(const unsigned char *old_sha1,
269 const unsigned char *new_sha1, 271 const unsigned char *new_sha1,
270 unsigned long *old_size, unsigned long *new_size, 272 unsigned long *old_size, unsigned long *new_size,
271 int *binary, linediff_fn fn); 273 int *binary, linediff_fn fn);
272 274
273extern void cgit_diff_tree(const unsigned char *old_sha1, 275extern void cgit_diff_tree(const unsigned char *old_sha1,
274 const unsigned char *new_sha1, 276 const unsigned char *new_sha1,
275 filepair_fn fn, const char *prefix); 277 filepair_fn fn, const char *prefix);
276 278
277extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); 279extern void cgit_diff_commit(struct commit *commit, filepair_fn fn);
278 280
279extern char *fmt(const char *format,...); 281extern char *fmt(const char *format,...);
280 282
281extern struct commitinfo *cgit_parse_commit(struct commit *commit); 283extern struct commitinfo *cgit_parse_commit(struct commit *commit);
282extern struct taginfo *cgit_parse_tag(struct tag *tag); 284extern struct taginfo *cgit_parse_tag(struct tag *tag);
283extern void cgit_parse_url(const char *url); 285extern void cgit_parse_url(const char *url);
284 286
285extern const char *cgit_repobasename(const char *reponame); 287extern const char *cgit_repobasename(const char *reponame);
286 288
287extern int cgit_parse_snapshots_mask(const char *str); 289extern int cgit_parse_snapshots_mask(const char *str);
288 290
289extern int cgit_open_filter(struct cgit_filter *filter); 291extern int cgit_open_filter(struct cgit_filter *filter);
290extern int cgit_close_filter(struct cgit_filter *filter); 292extern int cgit_close_filter(struct cgit_filter *filter);
291 293
292extern int readfile(const char *path, char **buf, size_t *size); 294extern int readfile(const char *path, char **buf, size_t *size);
293 295
294#endif /* CGIT_H */ 296#endif /* CGIT_H */
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 0c13485..0bb429a 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,501 +1,510 @@
1:man source: cgit 1:man source: cgit
2:man manual: cgit 2:man manual: cgit
3 3
4CGITRC(5) 4CGITRC(5)
5======== 5========
6 6
7 7
8NAME 8NAME
9---- 9----
10cgitrc - runtime configuration for cgit 10cgitrc - runtime configuration for cgit
11 11
12 12
13SYNOPSIS 13SYNOPSIS
14-------- 14--------
15Cgitrc contains all runtime settings for cgit, including the list of git 15Cgitrc contains all runtime settings for cgit, including the list of git
16repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank 16repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank
17lines, and lines starting with '#', are ignored. 17lines, and lines starting with '#', are ignored.
18 18
19 19
20LOCATION 20LOCATION
21-------- 21--------
22The default location of cgitrc, defined at compile time, is /etc/cgitrc. At 22The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
23runtime, cgit will consult the environment variable CGIT_CONFIG and, if 23runtime, cgit will consult the environment variable CGIT_CONFIG and, if
24defined, use its value instead. 24defined, use its value instead.
25 25
26 26
27GLOBAL SETTINGS 27GLOBAL SETTINGS
28--------------- 28---------------
29about-filter:: 29about-filter::
30 Specifies a command which will be invoked to format the content of 30 Specifies a command which will be invoked to format the content of
31 about pages (both top-level and for each repository). The command will 31 about pages (both top-level and for each repository). The command will
32 get the content of the about-file on its STDIN, and the STDOUT from the 32 get the content of the about-file on its STDIN, and the STDOUT from the
33 command will be included verbatim on the about page. Default value: 33 command will be included verbatim on the about page. Default value:
34 none. 34 none.
35 35
36agefile:: 36agefile::
37 Specifies a path, relative to each repository path, which can be used 37 Specifies a path, relative to each repository path, which can be used
38 to specify the date and time of the youngest commit in the repository. 38 to specify the date and time of the youngest commit in the repository.
39 The first line in the file is used as input to the "parse_date" 39 The first line in the file is used as input to the "parse_date"
40 function in libgit. Recommended timestamp-format is "yyyy-mm-dd 40 function in libgit. Recommended timestamp-format is "yyyy-mm-dd
41 hh:mm:ss". Default value: "info/web/last-modified". 41 hh:mm:ss". Default value: "info/web/last-modified".
42 42
43cache-root:: 43cache-root::
44 Path used to store the cgit cache entries. Default value: 44 Path used to store the cgit cache entries. Default value:
45 "/var/cache/cgit". 45 "/var/cache/cgit".
46 46
47cache-dynamic-ttl:: 47cache-dynamic-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 without a fixed SHA1. Default 49 version of repository pages accessed without a fixed SHA1. Default
50 value: "5". 50 value: "5".
51 51
52cache-repo-ttl:: 52cache-repo-ttl::
53 Number which specifies the time-to-live, in minutes, for the cached 53 Number which specifies the time-to-live, in minutes, for the cached
54 version of the repository summary page. Default value: "5". 54 version of the repository summary page. Default value: "5".
55 55
56cache-root-ttl:: 56cache-root-ttl::
57 Number which specifies the time-to-live, in minutes, for the cached 57 Number which specifies the time-to-live, in minutes, for the cached
58 version of the repository index page. Default value: "5". 58 version of the repository index page. Default value: "5".
59 59
60cache-scanrc-ttl:: 60cache-scanrc-ttl::
61 Number which specifies the time-to-live, in minutes, for the result 61 Number which specifies the time-to-live, in minutes, for the result
62 of scanning a path for git repositories. Default value: "15". 62 of scanning a path for git repositories. Default value: "15".
63 63
64cache-size:: 64cache-size::
65 The maximum number of entries in the cgit cache. Default value: "0" 65 The maximum number of entries in the cgit cache. Default value: "0"
66 (i.e. caching is disabled). 66 (i.e. caching is disabled).
67 67
68cache-static-ttl:: 68cache-static-ttl::
69 Number which specifies the time-to-live, in minutes, for the cached 69 Number which specifies the time-to-live, in minutes, for the cached
70 version of repository pages accessed with a fixed SHA1. Default value: 70 version of repository pages accessed with a fixed SHA1. Default value:
71 "5". 71 "5".
72 72
73clone-prefix:: 73clone-prefix::
74 Space-separated list of common prefixes which, when combined with a 74 Space-separated list of common prefixes which, when combined with a
75 repository url, generates valid clone urls for the repository. This 75 repository url, generates valid clone urls for the repository. This
76 setting is only used if `repo.clone-url` is unspecified. Default value: 76 setting is only used if `repo.clone-url` is unspecified. Default value:
77 none. 77 none.
78 78
79commit-filter:: 79commit-filter::
80 Specifies a command which will be invoked to format commit messages. 80 Specifies a command which will be invoked to format commit messages.
81 The command will get the message on its STDIN, and the STDOUT from the 81 The command will get the message on its STDIN, and the STDOUT from the
82 command will be included verbatim as the commit message, i.e. this can 82 command will be included verbatim as the commit message, i.e. this can
83 be used to implement bugtracker integration. Default value: none. 83 be used to implement bugtracker integration. Default value: none.
84 84
85css:: 85css::
86 Url which specifies the css document to include in all cgit pages. 86 Url which specifies the css document to include in all cgit pages.
87 Default value: "/cgit.css". 87 Default value: "/cgit.css".
88 88
89embedded:: 89embedded::
90 Flag which, when set to "1", will make cgit generate a html fragment 90 Flag which, when set to "1", will make cgit generate a html fragment
91 suitable for embedding in other html pages. Default value: none. See 91 suitable for embedding in other html pages. Default value: none. See
92 also: "noheader". 92 also: "noheader".
93 93
94enable-filter-overrides:: 94enable-filter-overrides::
95 Flag which, when set to "1", allows all filter settings to be 95 Flag which, when set to "1", allows all filter settings to be
96 overridden in repository-specific cgitrc files. Default value: none. 96 overridden in repository-specific cgitrc files. Default value: none.
97 97
98enable-index-links:: 98enable-index-links::
99 Flag which, when set to "1", will make cgit generate extra links for 99 Flag which, when set to "1", will make cgit generate extra links for
100 each repo in the repository index (specifically, to the "summary", 100 each repo in the repository index (specifically, to the "summary",
101 "commit" and "tree" pages). Default value: "0". 101 "commit" and "tree" pages). Default value: "0".
102 102
103enable-log-filecount:: 103enable-log-filecount::
104 Flag which, when set to "1", will make cgit print the number of 104 Flag which, when set to "1", will make cgit print the number of
105 modified files for each commit on the repository log page. Default 105 modified files for each commit on the repository log page. Default
106 value: "0". 106 value: "0".
107 107
108enable-log-linecount:: 108enable-log-linecount::
109 Flag which, when set to "1", will make cgit print the number of added 109 Flag which, when set to "1", will make cgit print the number of added
110 and removed lines for each commit on the repository log page. Default 110 and removed lines for each commit on the repository log page. Default
111 value: "0". 111 value: "0".
112 112
113enable-remote-branches::
114 Flag which, when set to "1", will make cgit display remote branches
115 in the summary and refs views. Default value: "0". See also:
116 "repo.enable-remote-branches".
117
113enable-tree-linenumbers:: 118enable-tree-linenumbers::
114 Flag which, when set to "1", will make cgit generate linenumber links 119 Flag which, when set to "1", will make cgit generate linenumber links
115 for plaintext blobs printed in the tree view. Default value: "1". 120 for plaintext blobs printed in the tree view. Default value: "1".
116 121
117favicon:: 122favicon::
118 Url used as link to a shortcut icon for cgit. If specified, it is 123 Url used as link to a shortcut icon for cgit. If specified, it is
119 suggested to use the value "/favicon.ico" since certain browsers will 124 suggested to use the value "/favicon.ico" since certain browsers will
120 ignore other values. Default value: none. 125 ignore other values. Default value: none.
121 126
122footer:: 127footer::
123 The content of the file specified with this option will be included 128 The content of the file specified with this option will be included
124 verbatim at the bottom of all pages (i.e. it replaces the standard 129 verbatim at the bottom of all pages (i.e. it replaces the standard
125 "generated by..." message. Default value: none. 130 "generated by..." message. Default value: none.
126 131
127head-include:: 132head-include::
128 The content of the file specified with this option will be included 133 The content of the file specified with this option will be included
129 verbatim in the html HEAD section on all pages. Default value: none. 134 verbatim in the html HEAD section on all pages. Default value: none.
130 135
131header:: 136header::
132 The content of the file specified with this option will be included 137 The content of the file specified with this option will be included
133 verbatim at the top of all pages. Default value: none. 138 verbatim at the top of all pages. Default value: none.
134 139
135include:: 140include::
136 Name of a configfile to include before the rest of the current config- 141 Name of a configfile to include before the rest of the current config-
137 file is parsed. Default value: none. 142 file is parsed. Default value: none.
138 143
139index-header:: 144index-header::
140 The content of the file specified with this option will be included 145 The content of the file specified with this option will be included
141 verbatim above the repository index. This setting is deprecated, and 146 verbatim above the repository index. This setting is deprecated, and
142 will not be supported by cgit-1.0 (use root-readme instead). Default 147 will not be supported by cgit-1.0 (use root-readme instead). Default
143 value: none. 148 value: none.
144 149
145index-info:: 150index-info::
146 The content of the file specified with this option will be included 151 The content of the file specified with this option will be included
147 verbatim below the heading on the repository index page. This setting 152 verbatim below the heading on the repository index page. This setting
148 is deprecated, and will not be supported by cgit-1.0 (use root-desc 153 is deprecated, and will not be supported by cgit-1.0 (use root-desc
149 instead). Default value: none. 154 instead). Default value: none.
150 155
151local-time:: 156local-time::
152 Flag which, if set to "1", makes cgit print commit and tag times in the 157 Flag which, if set to "1", makes cgit print commit and tag times in the
153 servers timezone. Default value: "0". 158 servers timezone. Default value: "0".
154 159
155logo:: 160logo::
156 Url which specifies the source of an image which will be used as a logo 161 Url which specifies the source of an image which will be used as a logo
157 on all cgit pages. Default value: "/cgit.png". 162 on all cgit pages. Default value: "/cgit.png".
158 163
159logo-link:: 164logo-link::
160 Url loaded when clicking on the cgit logo image. If unspecified the 165 Url loaded when clicking on the cgit logo image. If unspecified the
161 calculated url of the repository index page will be used. Default 166 calculated url of the repository index page will be used. Default
162 value: none. 167 value: none.
163 168
164max-commit-count:: 169max-commit-count::
165 Specifies the number of entries to list per page in "log" view. Default 170 Specifies the number of entries to list per page in "log" view. Default
166 value: "50". 171 value: "50".
167 172
168max-message-length:: 173max-message-length::
169 Specifies the maximum number of commit message characters to display in 174 Specifies the maximum number of commit message characters to display in
170 "log" view. Default value: "80". 175 "log" view. Default value: "80".
171 176
172max-repo-count:: 177max-repo-count::
173 Specifies the number of entries to list per page on therepository 178 Specifies the number of entries to list per page on therepository
174 index page. Default value: "50". 179 index page. Default value: "50".
175 180
176max-repodesc-length:: 181max-repodesc-length::
177 Specifies the maximum number of repo description characters to display 182 Specifies the maximum number of repo description characters to display
178 on the repository index page. Default value: "80". 183 on the repository index page. Default value: "80".
179 184
180max-stats:: 185max-stats::
181 Set the default maximum statistics period. Valid values are "week", 186 Set the default maximum statistics period. Valid values are "week",
182 "month", "quarter" and "year". If unspecified, statistics are 187 "month", "quarter" and "year". If unspecified, statistics are
183 disabled. Default value: none. See also: "repo.max-stats". 188 disabled. Default value: none. See also: "repo.max-stats".
184 189
185mimetype.<ext>:: 190mimetype.<ext>::
186 Set the mimetype for the specified filename extension. This is used 191 Set the mimetype for the specified filename extension. This is used
187 by the `plain` command when returning blob content. 192 by the `plain` command when returning blob content.
188 193
189module-link:: 194module-link::
190 Text which will be used as the formatstring for a hyperlink when a 195 Text which will be used as the formatstring for a hyperlink when a
191 submodule is printed in a directory listing. The arguments for the 196 submodule is printed in a directory listing. The arguments for the
192 formatstring are the path and SHA1 of the submodule commit. Default 197 formatstring are the path and SHA1 of the submodule commit. Default
193 value: "./?repo=%s&page=commit&id=%s" 198 value: "./?repo=%s&page=commit&id=%s"
194 199
195nocache:: 200nocache::
196 If set to the value "1" caching will be disabled. This settings is 201 If set to the value "1" caching will be disabled. This settings is
197 deprecated, and will not be honored starting with cgit-1.0. Default 202 deprecated, and will not be honored starting with cgit-1.0. Default
198 value: "0". 203 value: "0".
199 204
200noplainemail:: 205noplainemail::
201 If set to "1" showing full author email adresses will be disabled. 206 If set to "1" showing full author email adresses will be disabled.
202 Default value: "0". 207 Default value: "0".
203 208
204noheader:: 209noheader::
205 Flag which, when set to "1", will make cgit omit the standard header 210 Flag which, when set to "1", will make cgit omit the standard header
206 on all pages. Default value: none. See also: "embedded". 211 on all pages. Default value: none. See also: "embedded".
207 212
208renamelimit:: 213renamelimit::
209 Maximum number of files to consider when detecting renames. The value 214 Maximum number of files to consider when detecting renames. The value
210 "-1" uses the compiletime value in git (for further info, look at 215 "-1" uses the compiletime value in git (for further info, look at
211 `man git-diff`). Default value: "-1". 216 `man git-diff`). Default value: "-1".
212 217
213repo.group:: 218repo.group::
214 Legacy alias for "section". This option is deprecated and will not be 219 Legacy alias for "section". This option is deprecated and will not be
215 supported in cgit-1.0. 220 supported in cgit-1.0.
216 221
217robots:: 222robots::
218 Text used as content for the "robots" meta-tag. Default value: 223 Text used as content for the "robots" meta-tag. Default value:
219 "index, nofollow". 224 "index, nofollow".
220 225
221root-desc:: 226root-desc::
222 Text printed below the heading on the repository index page. Default 227 Text printed below the heading on the repository index page. Default
223 value: "a fast webinterface for the git dscm". 228 value: "a fast webinterface for the git dscm".
224 229
225root-readme:: 230root-readme::
226 The content of the file specified with this option will be included 231 The content of the file specified with this option will be included
227 verbatim below the "about" link on the repository index page. Default 232 verbatim below the "about" link on the repository index page. Default
228 value: none. 233 value: none.
229 234
230root-title:: 235root-title::
231 Text printed as heading on the repository index page. Default value: 236 Text printed as heading on the repository index page. Default value:
232 "Git Repository Browser". 237 "Git Repository Browser".
233 238
234scan-path:: 239scan-path::
235 A path which will be scanned for repositories. If caching is enabled, 240 A path which will be scanned for repositories. If caching is enabled,
236 the result will be cached as a cgitrc include-file in the cache 241 the result will be cached as a cgitrc include-file in the cache
237 directory. Default value: none. See also: cache-scanrc-ttl. 242 directory. Default value: none. See also: cache-scanrc-ttl.
238 243
239section:: 244section::
240 The name of the current repository section - all repositories defined 245 The name of the current repository section - all repositories defined
241 after this option will inherit the current section name. Default value: 246 after this option will inherit the current section name. Default value:
242 none. 247 none.
243 248
244snapshots:: 249snapshots::
245 Text which specifies the default set of snapshot formats generated by 250 Text which specifies the default set of snapshot formats generated by
246 cgit. The value is a space-separated list of zero or more of the 251 cgit. The value is a space-separated list of zero or more of the
247 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 252 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
248 253
249source-filter:: 254source-filter::
250 Specifies a command which will be invoked to format plaintext blobs 255 Specifies a command which will be invoked to format plaintext blobs
251 in the tree view. The command will get the blob content on its STDIN 256 in the tree view. The command will get the blob content on its STDIN
252 and the name of the blob as its only command line argument. The STDOUT 257 and the name of the blob as its only command line argument. The STDOUT
253 from the command will be included verbatim as the blob contents, i.e. 258 from the command will be included verbatim as the blob contents, i.e.
254 this can be used to implement e.g. syntax highlighting. Default value: 259 this can be used to implement e.g. syntax highlighting. Default value:
255 none. 260 none.
256 261
257summary-branches:: 262summary-branches::
258 Specifies the number of branches to display in the repository "summary" 263 Specifies the number of branches to display in the repository "summary"
259 view. Default value: "10". 264 view. Default value: "10".
260 265
261summary-log:: 266summary-log::
262 Specifies the number of log entries to display in the repository 267 Specifies the number of log entries to display in the repository
263 "summary" view. Default value: "10". 268 "summary" view. Default value: "10".
264 269
265summary-tags:: 270summary-tags::
266 Specifies the number of tags to display in the repository "summary" 271 Specifies the number of tags to display in the repository "summary"
267 view. Default value: "10". 272 view. Default value: "10".
268 273
269virtual-root:: 274virtual-root::
270 Url which, if specified, will be used as root for all cgit links. It 275 Url which, if specified, will be used as root for all cgit links. It
271 will also cause cgit to generate 'virtual urls', i.e. urls like 276 will also cause cgit to generate 'virtual urls', i.e. urls like
272 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 277 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
273 value: none. 278 value: none.
274 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 279 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
275 same kind of virtual urls, so this option will probably be deprecated. 280 same kind of virtual urls, so this option will probably be deprecated.
276 281
277REPOSITORY SETTINGS 282REPOSITORY SETTINGS
278------------------- 283-------------------
279repo.about-filter:: 284repo.about-filter::
280 Override the default about-filter. Default value: none. See also: 285 Override the default about-filter. Default value: none. See also:
281 "enable-filter-overrides". 286 "enable-filter-overrides".
282 287
283repo.clone-url:: 288repo.clone-url::
284 A list of space-separated urls which can be used to clone this repo. 289 A list of space-separated urls which can be used to clone this repo.
285 Default value: none. 290 Default value: none.
286 291
287repo.commit-filter:: 292repo.commit-filter::
288 Override the default commit-filter. Default value: none. See also: 293 Override the default commit-filter. Default value: none. See also:
289 "enable-filter-overrides". 294 "enable-filter-overrides".
290 295
291repo.defbranch:: 296repo.defbranch::
292 The name of the default branch for this repository. If no such branch 297 The name of the default branch for this repository. If no such branch
293 exists in the repository, the first branch name (when sorted) is used 298 exists in the repository, the first branch name (when sorted) is used
294 as default instead. Default value: "master". 299 as default instead. Default value: "master".
295 300
296repo.desc:: 301repo.desc::
297 The value to show as repository description. Default value: none. 302 The value to show as repository description. Default value: none.
298 303
299repo.enable-log-filecount:: 304repo.enable-log-filecount::
300 A flag which can be used to disable the global setting 305 A flag which can be used to disable the global setting
301 `enable-log-filecount'. Default value: none. 306 `enable-log-filecount'. Default value: none.
302 307
303repo.enable-log-linecount:: 308repo.enable-log-linecount::
304 A flag which can be used to disable the global setting 309 A flag which can be used to disable the global setting
305 `enable-log-linecount'. Default value: none. 310 `enable-log-linecount'. Default value: none.
306 311
312repo.enable-remote-branches::
313 Flag which, when set to "1", will make cgit display remote branches
314 in the summary and refs views. Default value: <enable-remote-branches>.
315
307repo.max-stats:: 316repo.max-stats::
308 Override the default maximum statistics period. Valid values are equal 317 Override the default maximum statistics period. Valid values are equal
309 to the values specified for the global "max-stats" setting. Default 318 to the values specified for the global "max-stats" setting. Default
310 value: none. 319 value: none.
311 320
312repo.name:: 321repo.name::
313 The value to show as repository name. Default value: <repo.url>. 322 The value to show as repository name. Default value: <repo.url>.
314 323
315repo.owner:: 324repo.owner::
316 A value used to identify the owner of the repository. Default value: 325 A value used to identify the owner of the repository. Default value:
317 none. 326 none.
318 327
319repo.path:: 328repo.path::
320 An absolute path to the repository directory. For non-bare repositories 329 An absolute path to the repository directory. For non-bare repositories
321 this is the .git-directory. Default value: none. 330 this is the .git-directory. Default value: none.
322 331
323repo.readme:: 332repo.readme::
324 A path (relative to <repo.path>) which specifies a file to include 333 A path (relative to <repo.path>) which specifies a file to include
325 verbatim as the "About" page for this repo. Default value: none. 334 verbatim as the "About" page for this repo. Default value: none.
326 335
327repo.snapshots:: 336repo.snapshots::
328 A mask of allowed snapshot-formats for this repo, restricted by the 337 A mask of allowed snapshot-formats for this repo, restricted by the
329 "snapshots" global setting. Default value: <snapshots>. 338 "snapshots" global setting. Default value: <snapshots>.
330 339
331repo.section:: 340repo.section::
332 Override the current section name for this repository. Default value: 341 Override the current section name for this repository. Default value:
333 none. 342 none.
334 343
335repo.source-filter:: 344repo.source-filter::
336 Override the default source-filter. Default value: none. See also: 345 Override the default source-filter. Default value: none. See also:
337 "enable-filter-overrides". 346 "enable-filter-overrides".
338 347
339repo.url:: 348repo.url::
340 The relative url used to access the repository. This must be the first 349 The relative url used to access the repository. This must be the first
341 setting specified for each repo. Default value: none. 350 setting specified for each repo. Default value: none.
342 351
343 352
344REPOSITORY-SPECIFIC CGITRC FILE 353REPOSITORY-SPECIFIC CGITRC FILE
345------------------------------- 354-------------------------------
346When the option "scan-path" is used to auto-discover git repositories, cgit 355When the option "scan-path" is used to auto-discover git repositories, cgit
347will try to parse the file "cgitrc" within any found repository. Such a 356will try to parse the file "cgitrc" within any found repository. Such a
348repo-specific config file may contain any of the repo-specific options 357repo-specific config file may contain any of the repo-specific options
349described above, except "repo.url" and "repo.path". Additionally, the "filter" 358described above, except "repo.url" and "repo.path". Additionally, the "filter"
350options are only acknowledged in repo-specific config files when 359options are only acknowledged in repo-specific config files when
351"enable-filter-overrides" is set to "1". 360"enable-filter-overrides" is set to "1".
352 361
353Note: the "repo." prefix is dropped from the option names in repo-specific 362Note: the "repo." prefix is dropped from the option names in repo-specific
354config files, e.g. "repo.desc" becomes "desc". 363config files, e.g. "repo.desc" becomes "desc".
355 364
356 365
357EXAMPLE CGITRC FILE 366EXAMPLE CGITRC FILE
358------------------- 367-------------------
359 368
360.... 369....
361# Enable caching of up to 1000 output entriess 370# Enable caching of up to 1000 output entriess
362cache-size=1000 371cache-size=1000
363 372
364 373
365# Specify some default clone prefixes 374# Specify some default clone prefixes
366clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 375clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
367 376
368# Specify the css url 377# Specify the css url
369css=/css/cgit.css 378css=/css/cgit.css
370 379
371 380
372# Show extra links for each repository on the index page 381# Show extra links for each repository on the index page
373enable-index-links=1 382enable-index-links=1
374 383
375 384
376# Show number of affected files per commit on the log pages 385# Show number of affected files per commit on the log pages
377enable-log-filecount=1 386enable-log-filecount=1
378 387
379 388
380# Show number of added/removed lines per commit on the log pages 389# Show number of added/removed lines per commit on the log pages
381enable-log-linecount=1 390enable-log-linecount=1
382 391
383 392
384# Add a cgit favicon 393# Add a cgit favicon
385favicon=/favicon.ico 394favicon=/favicon.ico
386 395
387 396
388# Use a custom logo 397# Use a custom logo
389logo=/img/mylogo.png 398logo=/img/mylogo.png
390 399
391 400
392# Enable statistics per week, month and quarter 401# Enable statistics per week, month and quarter
393max-stats=quarter 402max-stats=quarter
394 403
395 404
396# Set the title and heading of the repository index page 405# Set the title and heading of the repository index page
397root-title=foobar.com git repositories 406root-title=foobar.com git repositories
398 407
399 408
400# Set a subheading for the repository index page 409# Set a subheading for the repository index page
401root-desc=tracking the foobar development 410root-desc=tracking the foobar development
402 411
403 412
404# Include some more info about foobar.com on the index page 413# Include some more info about foobar.com on the index page
405root-readme=/var/www/htdocs/about.html 414root-readme=/var/www/htdocs/about.html
406 415
407 416
408# Allow download of tar.gz, tar.bz2 and zip-files 417# Allow download of tar.gz, tar.bz2 and zip-files
409snapshots=tar.gz tar.bz2 zip 418snapshots=tar.gz tar.bz2 zip
410 419
411 420
412## 421##
413## List of common mimetypes 422## List of common mimetypes
414## 423##
415 424
416mimetype.git=image/git 425mimetype.git=image/git
417mimetype.html=text/html 426mimetype.html=text/html
418mimetype.jpg=image/jpeg 427mimetype.jpg=image/jpeg
419mimetype.jpeg=image/jpeg 428mimetype.jpeg=image/jpeg
420mimetype.pdf=application/pdf 429mimetype.pdf=application/pdf
421mimetype.png=image/png 430mimetype.png=image/png
422mimetype.svg=image/svg+xml 431mimetype.svg=image/svg+xml
423 432
424 433
425## 434##
426## List of repositories. 435## List of repositories.
427## PS: Any repositories listed when section is unset will not be 436## PS: Any repositories listed when section is unset will not be
428## displayed under a section heading 437## displayed under a section heading
429## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 438## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
430## and included like this: 439## and included like this:
431## include=/etc/cgitrepos 440## include=/etc/cgitrepos
432## 441##
433 442
434 443
435repo.url=foo 444repo.url=foo
436repo.path=/pub/git/foo.git 445repo.path=/pub/git/foo.git
437repo.desc=the master foo repository 446repo.desc=the master foo repository
438repo.owner=fooman@foobar.com 447repo.owner=fooman@foobar.com
439repo.readme=info/web/about.html 448repo.readme=info/web/about.html
440 449
441 450
442repo.url=bar 451repo.url=bar
443repo.path=/pub/git/bar.git 452repo.path=/pub/git/bar.git
444repo.desc=the bars for your foo 453repo.desc=the bars for your foo
445repo.owner=barman@foobar.com 454repo.owner=barman@foobar.com
446repo.readme=info/web/about.html 455repo.readme=info/web/about.html
447 456
448 457
449# The next repositories will be displayed under the 'extras' heading 458# The next repositories will be displayed under the 'extras' heading
450section=extras 459section=extras
451 460
452 461
453repo.url=baz 462repo.url=baz
454repo.path=/pub/git/baz.git 463repo.path=/pub/git/baz.git
455repo.desc=a set of extensions for bar users 464repo.desc=a set of extensions for bar users
456 465
457repo.url=wiz 466repo.url=wiz
458repo.path=/pub/git/wiz.git 467repo.path=/pub/git/wiz.git
459repo.desc=the wizard of foo 468repo.desc=the wizard of foo
460 469
461 470
462# Add some mirrored repositories 471# Add some mirrored repositories
463section=mirrors 472section=mirrors
464 473
465 474
466repo.url=git 475repo.url=git
467repo.path=/pub/git/git.git 476repo.path=/pub/git/git.git
468repo.desc=the dscm 477repo.desc=the dscm
469 478
470 479
471repo.url=linux 480repo.url=linux
472repo.path=/pub/git/linux.git 481repo.path=/pub/git/linux.git
473repo.desc=the kernel 482repo.desc=the kernel
474 483
475# Disable adhoc downloads of this repo 484# Disable adhoc downloads of this repo
476repo.snapshots=0 485repo.snapshots=0
477 486
478# Disable line-counts for this repo 487# Disable line-counts for this repo
479repo.enable-log-linecount=0 488repo.enable-log-linecount=0
480 489
481# Restrict the max statistics period for this repo 490# Restrict the max statistics period for this repo
482repo.max-stats=month 491repo.max-stats=month
483.... 492....
484 493
485 494
486BUGS 495BUGS
487---- 496----
488Comments currently cannot appear on the same line as a setting; the comment 497Comments currently cannot appear on the same line as a setting; the comment
489will be included as part of the value. E.g. this line: 498will be included as part of the value. E.g. this line:
490 499
491 robots=index # allow indexing 500 robots=index # allow indexing
492 501
493will generate the following html element: 502will generate the following html element:
494 503
495 <meta name='robots' content='index # allow indexing'/> 504 <meta name='robots' content='index # allow indexing'/>
496 505
497 506
498 507
499AUTHOR 508AUTHOR
500------ 509------
501Lars Hjemli <hjemli@gmail.com> 510Lars Hjemli <hjemli@gmail.com>
diff --git a/shared.c b/shared.c
index 9362d21..5f46793 100644
--- a/shared.c
+++ b/shared.c
@@ -1,424 +1,425 @@
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 memset(ret, 0, sizeof(struct cgit_repo)); 51 memset(ret, 0, sizeof(struct cgit_repo));
52 ret->url = trim_end(url, '/'); 52 ret->url = trim_end(url, '/');
53 ret->name = ret->url; 53 ret->name = ret->url;
54 ret->path = NULL; 54 ret->path = NULL;
55 ret->desc = "[no description]"; 55 ret->desc = "[no description]";
56 ret->owner = NULL; 56 ret->owner = NULL;
57 ret->section = ctx.cfg.section; 57 ret->section = ctx.cfg.section;
58 ret->defbranch = "master"; 58 ret->defbranch = "master";
59 ret->snapshots = ctx.cfg.snapshots; 59 ret->snapshots = ctx.cfg.snapshots;
60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount; 60 ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount; 61 ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
62 ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
62 ret->max_stats = ctx.cfg.max_stats; 63 ret->max_stats = ctx.cfg.max_stats;
63 ret->module_link = ctx.cfg.module_link; 64 ret->module_link = ctx.cfg.module_link;
64 ret->readme = NULL; 65 ret->readme = NULL;
65 ret->mtime = -1; 66 ret->mtime = -1;
66 ret->about_filter = ctx.cfg.about_filter; 67 ret->about_filter = ctx.cfg.about_filter;
67 ret->commit_filter = ctx.cfg.commit_filter; 68 ret->commit_filter = ctx.cfg.commit_filter;
68 ret->source_filter = ctx.cfg.source_filter; 69 ret->source_filter = ctx.cfg.source_filter;
69 return ret; 70 return ret;
70} 71}
71 72
72struct cgit_repo *cgit_get_repoinfo(const char *url) 73struct cgit_repo *cgit_get_repoinfo(const char *url)
73{ 74{
74 int i; 75 int i;
75 struct cgit_repo *repo; 76 struct cgit_repo *repo;
76 77
77 for (i=0; i<cgit_repolist.count; i++) { 78 for (i=0; i<cgit_repolist.count; i++) {
78 repo = &cgit_repolist.repos[i]; 79 repo = &cgit_repolist.repos[i];
79 if (!strcmp(repo->url, url)) 80 if (!strcmp(repo->url, url))
80 return repo; 81 return repo;
81 } 82 }
82 return NULL; 83 return NULL;
83} 84}
84 85
85void *cgit_free_commitinfo(struct commitinfo *info) 86void *cgit_free_commitinfo(struct commitinfo *info)
86{ 87{
87 free(info->author); 88 free(info->author);
88 free(info->author_email); 89 free(info->author_email);
89 free(info->committer); 90 free(info->committer);
90 free(info->committer_email); 91 free(info->committer_email);
91 free(info->subject); 92 free(info->subject);
92 free(info->msg); 93 free(info->msg);
93 free(info->msg_encoding); 94 free(info->msg_encoding);
94 free(info); 95 free(info);
95 return NULL; 96 return NULL;
96} 97}
97 98
98char *trim_end(const char *str, char c) 99char *trim_end(const char *str, char c)
99{ 100{
100 int len; 101 int len;
101 char *s, *t; 102 char *s, *t;
102 103
103 if (str == NULL) 104 if (str == NULL)
104 return NULL; 105 return NULL;
105 t = (char *)str; 106 t = (char *)str;
106 len = strlen(t); 107 len = strlen(t);
107 while(len > 0 && t[len - 1] == c) 108 while(len > 0 && t[len - 1] == c)
108 len--; 109 len--;
109 110
110 if (len == 0) 111 if (len == 0)
111 return NULL; 112 return NULL;
112 113
113 c = t[len]; 114 c = t[len];
114 t[len] = '\0'; 115 t[len] = '\0';
115 s = xstrdup(t); 116 s = xstrdup(t);
116 t[len] = c; 117 t[len] = c;
117 return s; 118 return s;
118} 119}
119 120
120char *strlpart(char *txt, int maxlen) 121char *strlpart(char *txt, int maxlen)
121{ 122{
122 char *result; 123 char *result;
123 124
124 if (!txt) 125 if (!txt)
125 return txt; 126 return txt;
126 127
127 if (strlen(txt) <= maxlen) 128 if (strlen(txt) <= maxlen)
128 return txt; 129 return txt;
129 result = xmalloc(maxlen + 1); 130 result = xmalloc(maxlen + 1);
130 memcpy(result, txt, maxlen - 3); 131 memcpy(result, txt, maxlen - 3);
131 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.'; 132 result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.';
132 result[maxlen] = '\0'; 133 result[maxlen] = '\0';
133 return result; 134 return result;
134} 135}
135 136
136char *strrpart(char *txt, int maxlen) 137char *strrpart(char *txt, int maxlen)
137{ 138{
138 char *result; 139 char *result;
139 140
140 if (!txt) 141 if (!txt)
141 return txt; 142 return txt;
142 143
143 if (strlen(txt) <= maxlen) 144 if (strlen(txt) <= maxlen)
144 return txt; 145 return txt;
145 result = xmalloc(maxlen + 1); 146 result = xmalloc(maxlen + 1);
146 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3); 147 memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3);
147 result[0] = result[1] = result[2] = '.'; 148 result[0] = result[1] = result[2] = '.';
148 return result; 149 return result;
149} 150}
150 151
151void cgit_add_ref(struct reflist *list, struct refinfo *ref) 152void cgit_add_ref(struct reflist *list, struct refinfo *ref)
152{ 153{
153 size_t size; 154 size_t size;
154 155
155 if (list->count >= list->alloc) { 156 if (list->count >= list->alloc) {
156 list->alloc += (list->alloc ? list->alloc : 4); 157 list->alloc += (list->alloc ? list->alloc : 4);
157 size = list->alloc * sizeof(struct refinfo *); 158 size = list->alloc * sizeof(struct refinfo *);
158 list->refs = xrealloc(list->refs, size); 159 list->refs = xrealloc(list->refs, size);
159 } 160 }
160 list->refs[list->count++] = ref; 161 list->refs[list->count++] = ref;
161} 162}
162 163
163struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1) 164struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1)
164{ 165{
165 struct refinfo *ref; 166 struct refinfo *ref;
166 167
167 ref = xmalloc(sizeof (struct refinfo)); 168 ref = xmalloc(sizeof (struct refinfo));
168 ref->refname = xstrdup(refname); 169 ref->refname = xstrdup(refname);
169 ref->object = parse_object(sha1); 170 ref->object = parse_object(sha1);
170 switch (ref->object->type) { 171 switch (ref->object->type) {
171 case OBJ_TAG: 172 case OBJ_TAG:
172 ref->tag = cgit_parse_tag((struct tag *)ref->object); 173 ref->tag = cgit_parse_tag((struct tag *)ref->object);
173 break; 174 break;
174 case OBJ_COMMIT: 175 case OBJ_COMMIT:
175 ref->commit = cgit_parse_commit((struct commit *)ref->object); 176 ref->commit = cgit_parse_commit((struct commit *)ref->object);
176 break; 177 break;
177 } 178 }
178 return ref; 179 return ref;
179} 180}
180 181
181int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags, 182int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags,
182 void *cb_data) 183 void *cb_data)
183{ 184{
184 struct reflist *list = (struct reflist *)cb_data; 185 struct reflist *list = (struct reflist *)cb_data;
185 struct refinfo *info = cgit_mk_refinfo(refname, sha1); 186 struct refinfo *info = cgit_mk_refinfo(refname, sha1);
186 187
187 if (info) 188 if (info)
188 cgit_add_ref(list, info); 189 cgit_add_ref(list, info);
189 return 0; 190 return 0;
190} 191}
191 192
192void cgit_diff_tree_cb(struct diff_queue_struct *q, 193void cgit_diff_tree_cb(struct diff_queue_struct *q,
193 struct diff_options *options, void *data) 194 struct diff_options *options, void *data)
194{ 195{
195 int i; 196 int i;
196 197
197 for (i = 0; i < q->nr; i++) { 198 for (i = 0; i < q->nr; i++) {
198 if (q->queue[i]->status == 'U') 199 if (q->queue[i]->status == 'U')
199 continue; 200 continue;
200 ((filepair_fn)data)(q->queue[i]); 201 ((filepair_fn)data)(q->queue[i]);
201 } 202 }
202} 203}
203 204
204static int load_mmfile(mmfile_t *file, const unsigned char *sha1) 205static int load_mmfile(mmfile_t *file, const unsigned char *sha1)
205{ 206{
206 enum object_type type; 207 enum object_type type;
207 208
208 if (is_null_sha1(sha1)) { 209 if (is_null_sha1(sha1)) {
209 file->ptr = (char *)""; 210 file->ptr = (char *)"";
210 file->size = 0; 211 file->size = 0;
211 } else { 212 } else {
212 file->ptr = read_sha1_file(sha1, &type, 213 file->ptr = read_sha1_file(sha1, &type,
213 (unsigned long *)&file->size); 214 (unsigned long *)&file->size);
214 } 215 }
215 return 1; 216 return 1;
216} 217}
217 218
218/* 219/*
219 * Receive diff-buffers from xdiff and concatenate them as 220 * Receive diff-buffers from xdiff and concatenate them as
220 * needed across multiple callbacks. 221 * needed across multiple callbacks.
221 * 222 *
222 * This is basically a copy of xdiff-interface.c/xdiff_outf(), 223 * This is basically a copy of xdiff-interface.c/xdiff_outf(),
223 * ripped from git and modified to use globals instead of 224 * ripped from git and modified to use globals instead of
224 * a special callback-struct. 225 * a special callback-struct.
225 */ 226 */
226char *diffbuf = NULL; 227char *diffbuf = NULL;
227int buflen = 0; 228int buflen = 0;
228 229
229int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) 230int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
230{ 231{
231 int i; 232 int i;
232 233
233 for (i = 0; i < nbuf; i++) { 234 for (i = 0; i < nbuf; i++) {
234 if (mb[i].ptr[mb[i].size-1] != '\n') { 235 if (mb[i].ptr[mb[i].size-1] != '\n') {
235 /* Incomplete line */ 236 /* Incomplete line */
236 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 237 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
237 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 238 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
238 buflen += mb[i].size; 239 buflen += mb[i].size;
239 continue; 240 continue;
240 } 241 }
241 242
242 /* we have a complete line */ 243 /* we have a complete line */
243 if (!diffbuf) { 244 if (!diffbuf) {
244 ((linediff_fn)priv)(mb[i].ptr, mb[i].size); 245 ((linediff_fn)priv)(mb[i].ptr, mb[i].size);
245 continue; 246 continue;
246 } 247 }
247 diffbuf = xrealloc(diffbuf, buflen + mb[i].size); 248 diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
248 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); 249 memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
249 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); 250 ((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
250 free(diffbuf); 251 free(diffbuf);
251 diffbuf = NULL; 252 diffbuf = NULL;
252 buflen = 0; 253 buflen = 0;
253 } 254 }
254 if (diffbuf) { 255 if (diffbuf) {
255 ((linediff_fn)priv)(diffbuf, buflen); 256 ((linediff_fn)priv)(diffbuf, buflen);
256 free(diffbuf); 257 free(diffbuf);
257 diffbuf = NULL; 258 diffbuf = NULL;
258 buflen = 0; 259 buflen = 0;
259 } 260 }
260 return 0; 261 return 0;
261} 262}
262 263
263int cgit_diff_files(const unsigned char *old_sha1, 264int cgit_diff_files(const unsigned char *old_sha1,
264 const unsigned char *new_sha1, unsigned long *old_size, 265 const unsigned char *new_sha1, unsigned long *old_size,
265 unsigned long *new_size, int *binary, linediff_fn fn) 266 unsigned long *new_size, int *binary, linediff_fn fn)
266{ 267{
267 mmfile_t file1, file2; 268 mmfile_t file1, file2;
268 xpparam_t diff_params; 269 xpparam_t diff_params;
269 xdemitconf_t emit_params; 270 xdemitconf_t emit_params;
270 xdemitcb_t emit_cb; 271 xdemitcb_t emit_cb;
271 272
272 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) 273 if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1))
273 return 1; 274 return 1;
274 275
275 *old_size = file1.size; 276 *old_size = file1.size;
276 *new_size = file2.size; 277 *new_size = file2.size;
277 278
278 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) || 279 if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) ||
279 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) { 280 (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) {
280 *binary = 1; 281 *binary = 1;
281 return 0; 282 return 0;
282 } 283 }
283 284
284 memset(&diff_params, 0, sizeof(diff_params)); 285 memset(&diff_params, 0, sizeof(diff_params));
285 memset(&emit_params, 0, sizeof(emit_params)); 286 memset(&emit_params, 0, sizeof(emit_params));
286 memset(&emit_cb, 0, sizeof(emit_cb)); 287 memset(&emit_cb, 0, sizeof(emit_cb));
287 diff_params.flags = XDF_NEED_MINIMAL; 288 diff_params.flags = XDF_NEED_MINIMAL;
288 emit_params.ctxlen = 3; 289 emit_params.ctxlen = 3;
289 emit_params.flags = XDL_EMIT_FUNCNAMES; 290 emit_params.flags = XDL_EMIT_FUNCNAMES;
290 emit_cb.outf = filediff_cb; 291 emit_cb.outf = filediff_cb;
291 emit_cb.priv = fn; 292 emit_cb.priv = fn;
292 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); 293 xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
293 return 0; 294 return 0;
294} 295}
295 296
296void cgit_diff_tree(const unsigned char *old_sha1, 297void cgit_diff_tree(const unsigned char *old_sha1,
297 const unsigned char *new_sha1, 298 const unsigned char *new_sha1,
298 filepair_fn fn, const char *prefix) 299 filepair_fn fn, const char *prefix)
299{ 300{
300 struct diff_options opt; 301 struct diff_options opt;
301 int ret; 302 int ret;
302 int prefixlen; 303 int prefixlen;
303 304
304 diff_setup(&opt); 305 diff_setup(&opt);
305 opt.output_format = DIFF_FORMAT_CALLBACK; 306 opt.output_format = DIFF_FORMAT_CALLBACK;
306 opt.detect_rename = 1; 307 opt.detect_rename = 1;
307 opt.rename_limit = ctx.cfg.renamelimit; 308 opt.rename_limit = ctx.cfg.renamelimit;
308 DIFF_OPT_SET(&opt, RECURSIVE); 309 DIFF_OPT_SET(&opt, RECURSIVE);
309 opt.format_callback = cgit_diff_tree_cb; 310 opt.format_callback = cgit_diff_tree_cb;
310 opt.format_callback_data = fn; 311 opt.format_callback_data = fn;
311 if (prefix) { 312 if (prefix) {
312 opt.nr_paths = 1; 313 opt.nr_paths = 1;
313 opt.paths = &prefix; 314 opt.paths = &prefix;
314 prefixlen = strlen(prefix); 315 prefixlen = strlen(prefix);
315 opt.pathlens = &prefixlen; 316 opt.pathlens = &prefixlen;
316 } 317 }
317 diff_setup_done(&opt); 318 diff_setup_done(&opt);
318 319
319 if (old_sha1 && !is_null_sha1(old_sha1)) 320 if (old_sha1 && !is_null_sha1(old_sha1))
320 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt); 321 ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt);
321 else 322 else
322 ret = diff_root_tree_sha1(new_sha1, "", &opt); 323 ret = diff_root_tree_sha1(new_sha1, "", &opt);
323 diffcore_std(&opt); 324 diffcore_std(&opt);
324 diff_flush(&opt); 325 diff_flush(&opt);
325} 326}
326 327
327void cgit_diff_commit(struct commit *commit, filepair_fn fn) 328void cgit_diff_commit(struct commit *commit, filepair_fn fn)
328{ 329{
329 unsigned char *old_sha1 = NULL; 330 unsigned char *old_sha1 = NULL;
330 331
331 if (commit->parents) 332 if (commit->parents)
332 old_sha1 = commit->parents->item->object.sha1; 333 old_sha1 = commit->parents->item->object.sha1;
333 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL); 334 cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL);
334} 335}
335 336
336int cgit_parse_snapshots_mask(const char *str) 337int cgit_parse_snapshots_mask(const char *str)
337{ 338{
338 const struct cgit_snapshot_format *f; 339 const struct cgit_snapshot_format *f;
339 static const char *delim = " \t,:/|;"; 340 static const char *delim = " \t,:/|;";
340 int tl, sl, rv = 0; 341 int tl, sl, rv = 0;
341 342
342 /* favor legacy setting */ 343 /* favor legacy setting */
343 if(atoi(str)) 344 if(atoi(str))
344 return 1; 345 return 1;
345 for(;;) { 346 for(;;) {
346 str += strspn(str,delim); 347 str += strspn(str,delim);
347 tl = strcspn(str,delim); 348 tl = strcspn(str,delim);
348 if (!tl) 349 if (!tl)
349 break; 350 break;
350 for (f = cgit_snapshot_formats; f->suffix; f++) { 351 for (f = cgit_snapshot_formats; f->suffix; f++) {
351 sl = strlen(f->suffix); 352 sl = strlen(f->suffix);
352 if((tl == sl && !strncmp(f->suffix, str, tl)) || 353 if((tl == sl && !strncmp(f->suffix, str, tl)) ||
353 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) { 354 (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) {
354 rv |= f->bit; 355 rv |= f->bit;
355 break; 356 break;
356 } 357 }
357 } 358 }
358 str += tl; 359 str += tl;
359 } 360 }
360 return rv; 361 return rv;
361} 362}
362 363
363int cgit_open_filter(struct cgit_filter *filter) 364int cgit_open_filter(struct cgit_filter *filter)
364{ 365{
365 366
366 filter->old_stdout = chk_positive(dup(STDOUT_FILENO), 367 filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
367 "Unable to duplicate STDOUT"); 368 "Unable to duplicate STDOUT");
368 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess"); 369 chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess");
369 filter->pid = chk_non_negative(fork(), "Unable to create subprocess"); 370 filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
370 if (filter->pid == 0) { 371 if (filter->pid == 0) {
371 close(filter->pipe_fh[1]); 372 close(filter->pipe_fh[1]);
372 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO), 373 chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO),
373 "Unable to use pipe as STDIN"); 374 "Unable to use pipe as STDIN");
374 execvp(filter->cmd, filter->argv); 375 execvp(filter->cmd, filter->argv);
375 die("Unable to exec subprocess %s: %s (%d)", filter->cmd, 376 die("Unable to exec subprocess %s: %s (%d)", filter->cmd,
376 strerror(errno), errno); 377 strerror(errno), errno);
377 } 378 }
378 close(filter->pipe_fh[0]); 379 close(filter->pipe_fh[0]);
379 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO), 380 chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO),
380 "Unable to use pipe as STDOUT"); 381 "Unable to use pipe as STDOUT");
381 close(filter->pipe_fh[1]); 382 close(filter->pipe_fh[1]);
382 return 0; 383 return 0;
383} 384}
384 385
385int cgit_close_filter(struct cgit_filter *filter) 386int cgit_close_filter(struct cgit_filter *filter)
386{ 387{
387 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO), 388 chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
388 "Unable to restore STDOUT"); 389 "Unable to restore STDOUT");
389 close(filter->old_stdout); 390 close(filter->old_stdout);
390 if (filter->pid < 0) 391 if (filter->pid < 0)
391 return 0; 392 return 0;
392 waitpid(filter->pid, &filter->exitstatus, 0); 393 waitpid(filter->pid, &filter->exitstatus, 0);
393 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus)) 394 if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus))
394 return 0; 395 return 0;
395 die("Subprocess %s exited abnormally", filter->cmd); 396 die("Subprocess %s exited abnormally", filter->cmd);
396} 397}
397 398
398/* Read the content of the specified file into a newly allocated buffer, 399/* Read the content of the specified file into a newly allocated buffer,
399 * zeroterminate the buffer and return 0 on success, errno otherwise. 400 * zeroterminate the buffer and return 0 on success, errno otherwise.
400 */ 401 */
401int readfile(const char *path, char **buf, size_t *size) 402int readfile(const char *path, char **buf, size_t *size)
402{ 403{
403 int fd, e; 404 int fd, e;
404 struct stat st; 405 struct stat st;
405 406
406 fd = open(path, O_RDONLY); 407 fd = open(path, O_RDONLY);
407 if (fd == -1) 408 if (fd == -1)
408 return errno; 409 return errno;
409 if (fstat(fd, &st)) { 410 if (fstat(fd, &st)) {
410 e = errno; 411 e = errno;
411 close(fd); 412 close(fd);
412 return e; 413 return e;
413 } 414 }
414 if (!S_ISREG(st.st_mode)) { 415 if (!S_ISREG(st.st_mode)) {
415 close(fd); 416 close(fd);
416 return EISDIR; 417 return EISDIR;
417 } 418 }
418 *buf = xmalloc(st.st_size + 1); 419 *buf = xmalloc(st.st_size + 1);
419 *size = read_in_full(fd, *buf, st.st_size); 420 *size = read_in_full(fd, *buf, st.st_size);
420 e = errno; 421 e = errno;
421 (*buf)[*size] = '\0'; 422 (*buf)[*size] = '\0';
422 close(fd); 423 close(fd);
423 return (*size == st.st_size ? 0 : e); 424 return (*size == st.st_size ? 0 : e);
424} 425}
diff --git a/ui-refs.c b/ui-refs.c
index d3b4f6e..b3489ee 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -1,245 +1,247 @@
1/* ui-refs.c: browse symbolic refs 1/* ui-refs.c: browse symbolic refs
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 header; 13static int header;
14 14
15static int cmp_age(int age1, int age2) 15static int cmp_age(int age1, int age2)
16{ 16{
17 if (age1 != 0 && age2 != 0) 17 if (age1 != 0 && age2 != 0)
18 return age2 - age1; 18 return age2 - age1;
19 19
20 if (age1 == 0 && age2 == 0) 20 if (age1 == 0 && age2 == 0)
21 return 0; 21 return 0;
22 22
23 if (age1 == 0) 23 if (age1 == 0)
24 return +1; 24 return +1;
25 25
26 return -1; 26 return -1;
27} 27}
28 28
29static int cmp_ref_name(const void *a, const void *b) 29static int cmp_ref_name(const void *a, const void *b)
30{ 30{
31 struct refinfo *r1 = *(struct refinfo **)a; 31 struct refinfo *r1 = *(struct refinfo **)a;
32 struct refinfo *r2 = *(struct refinfo **)b; 32 struct refinfo *r2 = *(struct refinfo **)b;
33 33
34 return strcmp(r1->refname, r2->refname); 34 return strcmp(r1->refname, r2->refname);
35} 35}
36 36
37static int cmp_branch_age(const void *a, const void *b) 37static int cmp_branch_age(const void *a, const void *b)
38{ 38{
39 struct refinfo *r1 = *(struct refinfo **)a; 39 struct refinfo *r1 = *(struct refinfo **)a;
40 struct refinfo *r2 = *(struct refinfo **)b; 40 struct refinfo *r2 = *(struct refinfo **)b;
41 41
42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date); 42 return cmp_age(r1->commit->committer_date, r2->commit->committer_date);
43} 43}
44 44
45static int cmp_tag_age(const void *a, const void *b) 45static int cmp_tag_age(const void *a, const void *b)
46{ 46{
47 struct refinfo *r1 = *(struct refinfo **)a; 47 struct refinfo *r1 = *(struct refinfo **)a;
48 struct refinfo *r2 = *(struct refinfo **)b; 48 struct refinfo *r2 = *(struct refinfo **)b;
49 int r1date, r2date; 49 int r1date, r2date;
50 50
51 if (r1->object->type != OBJ_COMMIT) 51 if (r1->object->type != OBJ_COMMIT)
52 r1date = r1->tag->tagger_date; 52 r1date = r1->tag->tagger_date;
53 else 53 else
54 r1date = r1->commit->committer_date; 54 r1date = r1->commit->committer_date;
55 55
56 if (r2->object->type != OBJ_COMMIT) 56 if (r2->object->type != OBJ_COMMIT)
57 r2date = r2->tag->tagger_date; 57 r2date = r2->tag->tagger_date;
58 else 58 else
59 r2date = r2->commit->committer_date; 59 r2date = r2->commit->committer_date;
60 60
61 return cmp_age(r1date, r2date); 61 return cmp_age(r1date, r2date);
62} 62}
63 63
64static int print_branch(struct refinfo *ref) 64static int print_branch(struct refinfo *ref)
65{ 65{
66 struct commitinfo *info = ref->commit; 66 struct commitinfo *info = ref->commit;
67 char *name = (char *)ref->refname; 67 char *name = (char *)ref->refname;
68 68
69 if (!info) 69 if (!info)
70 return 1; 70 return 1;
71 html("<tr><td>"); 71 html("<tr><td>");
72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, 72 cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
73 ctx.qry.showmsg); 73 ctx.qry.showmsg);
74 html("</td><td>"); 74 html("</td><td>");
75 75
76 if (ref->object->type == OBJ_COMMIT) { 76 if (ref->object->type == OBJ_COMMIT) {
77 cgit_commit_link(info->subject, NULL, NULL, name, NULL); 77 cgit_commit_link(info->subject, NULL, NULL, name, NULL);
78 html("</td><td>"); 78 html("</td><td>");
79 html_txt(info->author); 79 html_txt(info->author);
80 html("</td><td colspan='2'>"); 80 html("</td><td colspan='2'>");
81 cgit_print_age(info->commit->date, -1, NULL); 81 cgit_print_age(info->commit->date, -1, NULL);
82 } else { 82 } else {
83 html("</td><td></td><td>"); 83 html("</td><td></td><td>");
84 cgit_object_link(ref->object); 84 cgit_object_link(ref->object);
85 } 85 }
86 html("</td></tr>\n"); 86 html("</td></tr>\n");
87 return 0; 87 return 0;
88} 88}
89 89
90static void print_tag_header() 90static void print_tag_header()
91{ 91{
92 html("<tr class='nohover'><th class='left'>Tag</th>" 92 html("<tr class='nohover'><th class='left'>Tag</th>"
93 "<th class='left'>Download</th>" 93 "<th class='left'>Download</th>"
94 "<th class='left'>Author</th>" 94 "<th class='left'>Author</th>"
95 "<th class='left' colspan='2'>Age</th></tr>\n"); 95 "<th class='left' colspan='2'>Age</th></tr>\n");
96 header = 1; 96 header = 1;
97} 97}
98 98
99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) 99static void print_tag_downloads(const struct cgit_repo *repo, const char *ref)
100{ 100{
101 const struct cgit_snapshot_format* f; 101 const struct cgit_snapshot_format* f;
102 char *filename; 102 char *filename;
103 const char *basename; 103 const char *basename;
104 104
105 if (!ref || strlen(ref) < 2) 105 if (!ref || strlen(ref) < 2)
106 return; 106 return;
107 107
108 basename = cgit_repobasename(repo->url); 108 basename = cgit_repobasename(repo->url);
109 if (prefixcmp(ref, basename) != 0) { 109 if (prefixcmp(ref, basename) != 0) {
110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1])) 110 if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]))
111 ref++; 111 ref++;
112 if (isdigit(ref[0])) 112 if (isdigit(ref[0]))
113 ref = xstrdup(fmt("%s-%s", basename, ref)); 113 ref = xstrdup(fmt("%s-%s", basename, ref));
114 } 114 }
115 115
116 for (f = cgit_snapshot_formats; f->suffix; f++) { 116 for (f = cgit_snapshot_formats; f->suffix; f++) {
117 if (!(repo->snapshots & f->bit)) 117 if (!(repo->snapshots & f->bit))
118 continue; 118 continue;
119 filename = fmt("%s%s", ref, f->suffix); 119 filename = fmt("%s%s", ref, f->suffix);
120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); 120 cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename);
121 html("&nbsp;&nbsp;"); 121 html("&nbsp;&nbsp;");
122 } 122 }
123} 123}
124static int print_tag(struct refinfo *ref) 124static int print_tag(struct refinfo *ref)
125{ 125{
126 struct tag *tag; 126 struct tag *tag;
127 struct taginfo *info; 127 struct taginfo *info;
128 char *name = (char *)ref->refname; 128 char *name = (char *)ref->refname;
129 129
130 if (ref->object->type == OBJ_TAG) { 130 if (ref->object->type == OBJ_TAG) {
131 tag = (struct tag *)ref->object; 131 tag = (struct tag *)ref->object;
132 info = ref->tag; 132 info = ref->tag;
133 if (!tag || !info) 133 if (!tag || !info)
134 return 1; 134 return 1;
135 html("<tr><td>"); 135 html("<tr><td>");
136 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 136 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
137 html("</td><td>"); 137 html("</td><td>");
138 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT)) 138 if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT))
139 print_tag_downloads(ctx.repo, name); 139 print_tag_downloads(ctx.repo, name);
140 else 140 else
141 cgit_object_link(tag->tagged); 141 cgit_object_link(tag->tagged);
142 html("</td><td>"); 142 html("</td><td>");
143 if (info->tagger) 143 if (info->tagger)
144 html(info->tagger); 144 html(info->tagger);
145 html("</td><td colspan='2'>"); 145 html("</td><td colspan='2'>");
146 if (info->tagger_date > 0) 146 if (info->tagger_date > 0)
147 cgit_print_age(info->tagger_date, -1, NULL); 147 cgit_print_age(info->tagger_date, -1, NULL);
148 html("</td></tr>\n"); 148 html("</td></tr>\n");
149 } else { 149 } else {
150 if (!header) 150 if (!header)
151 print_tag_header(); 151 print_tag_header();
152 html("<tr><td>"); 152 html("<tr><td>");
153 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); 153 cgit_tag_link(name, NULL, NULL, ctx.qry.head, name);
154 html("</td><td>"); 154 html("</td><td>");
155 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT)) 155 if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT))
156 print_tag_downloads(ctx.repo, name); 156 print_tag_downloads(ctx.repo, name);
157 else 157 else
158 cgit_object_link(ref->object); 158 cgit_object_link(ref->object);
159 html("</td><td>"); 159 html("</td><td>");
160 if (ref->object->type == OBJ_COMMIT) 160 if (ref->object->type == OBJ_COMMIT)
161 html(ref->commit->author); 161 html(ref->commit->author);
162 html("</td><td colspan='2'>"); 162 html("</td><td colspan='2'>");
163 if (ref->object->type == OBJ_COMMIT) 163 if (ref->object->type == OBJ_COMMIT)
164 cgit_print_age(ref->commit->commit->date, -1, NULL); 164 cgit_print_age(ref->commit->commit->date, -1, NULL);
165 html("</td></tr>\n"); 165 html("</td></tr>\n");
166 } 166 }
167 return 0; 167 return 0;
168} 168}
169 169
170static void print_refs_link(char *path) 170static void print_refs_link(char *path)
171{ 171{
172 html("<tr class='nohover'><td colspan='4'>"); 172 html("<tr class='nohover'><td colspan='4'>");
173 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); 173 cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
174 html("</td></tr>"); 174 html("</td></tr>");
175} 175}
176 176
177void cgit_print_branches(int maxcount) 177void cgit_print_branches(int maxcount)
178{ 178{
179 struct reflist list; 179 struct reflist list;
180 int i; 180 int i;
181 181
182 html("<tr class='nohover'><th class='left'>Branch</th>" 182 html("<tr class='nohover'><th class='left'>Branch</th>"
183 "<th class='left'>Commit message</th>" 183 "<th class='left'>Commit message</th>"
184 "<th class='left'>Author</th>" 184 "<th class='left'>Author</th>"
185 "<th class='left' colspan='2'>Age</th></tr>\n"); 185 "<th class='left' colspan='2'>Age</th></tr>\n");
186 186
187 list.refs = NULL; 187 list.refs = NULL;
188 list.alloc = list.count = 0; 188 list.alloc = list.count = 0;
189 for_each_branch_ref(cgit_refs_cb, &list); 189 for_each_branch_ref(cgit_refs_cb, &list);
190 if (ctx.repo->enable_remote_branches)
191 for_each_remote_ref(cgit_refs_cb, &list);
190 192
191 if (maxcount == 0 || maxcount > list.count) 193 if (maxcount == 0 || maxcount > list.count)
192 maxcount = list.count; 194 maxcount = list.count;
193 195
194 if (maxcount < list.count) { 196 if (maxcount < list.count) {
195 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); 197 qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
196 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); 198 qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name);
197 } 199 }
198 200
199 for(i=0; i<maxcount; i++) 201 for(i=0; i<maxcount; i++)
200 print_branch(list.refs[i]); 202 print_branch(list.refs[i]);
201 203
202 if (maxcount < list.count) 204 if (maxcount < list.count)
203 print_refs_link("heads"); 205 print_refs_link("heads");
204} 206}
205 207
206void cgit_print_tags(int maxcount) 208void cgit_print_tags(int maxcount)
207{ 209{
208 struct reflist list; 210 struct reflist list;
209 int i; 211 int i;
210 212
211 header = 0; 213 header = 0;
212 list.refs = NULL; 214 list.refs = NULL;
213 list.alloc = list.count = 0; 215 list.alloc = list.count = 0;
214 for_each_tag_ref(cgit_refs_cb, &list); 216 for_each_tag_ref(cgit_refs_cb, &list);
215 if (list.count == 0) 217 if (list.count == 0)
216 return; 218 return;
217 qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age); 219 qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age);
218 if (!maxcount) 220 if (!maxcount)
219 maxcount = list.count; 221 maxcount = list.count;
220 else if (maxcount > list.count) 222 else if (maxcount > list.count)
221 maxcount = list.count; 223 maxcount = list.count;
222 print_tag_header(); 224 print_tag_header();
223 for(i=0; i<maxcount; i++) 225 for(i=0; i<maxcount; i++)
224 print_tag(list.refs[i]); 226 print_tag(list.refs[i]);
225 227
226 if (maxcount < list.count) 228 if (maxcount < list.count)
227 print_refs_link("tags"); 229 print_refs_link("tags");
228} 230}
229 231
230void cgit_print_refs() 232void cgit_print_refs()
231{ 233{
232 234
233 html("<table class='list nowrap'>"); 235 html("<table class='list nowrap'>");
234 236
235 if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5)) 237 if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5))
236 cgit_print_branches(0); 238 cgit_print_branches(0);
237 else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4)) 239 else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4))
238 cgit_print_tags(0); 240 cgit_print_tags(0);
239 else { 241 else {
240 cgit_print_branches(0); 242 cgit_print_branches(0);
241 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>"); 243 html("<tr class='nohover'><td colspan='4'>&nbsp;</td></tr>");
242 cgit_print_tags(0); 244 cgit_print_tags(0);
243 } 245 }
244 html("</table>"); 246 html("</table>");
245} 247}