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