summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c8
-rw-r--r--cgit.h3
-rw-r--r--cgitrc.5.txt9
-rw-r--r--scan-tree.c30
-rw-r--r--scan-tree.h2
5 files changed, 39 insertions, 13 deletions
diff --git a/cgit.c b/cgit.c
index 90ae124..e281aa9 100644
--- a/cgit.c
+++ b/cgit.c
@@ -43,257 +43,257 @@ struct cgit_filter *new_filter(const char *cmd, int extra_args)
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, "max-stats")) 63 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL); 64 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link")) 65 else if (!strcmp(name, "module-link"))
66 repo->module_link= xstrdup(value); 66 repo->module_link= xstrdup(value);
67 else if (!strcmp(name, "section")) 67 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value); 68 repo->section = xstrdup(value);
69 else if (!strcmp(name, "about-filter")) 69 else if (!strcmp(name, "about-filter"))
70 repo->about_filter = new_filter(value, 0); 70 repo->about_filter = new_filter(value, 0);
71 else if (!strcmp(name, "commit-filter")) 71 else if (!strcmp(name, "commit-filter"))
72 repo->commit_filter = new_filter(value, 0); 72 repo->commit_filter = new_filter(value, 0);
73 else if (!strcmp(name, "source-filter")) 73 else if (!strcmp(name, "source-filter"))
74 repo->source_filter = new_filter(value, 1); 74 repo->source_filter = new_filter(value, 1);
75 else if (!strcmp(name, "readme") && value != NULL) { 75 else if (!strcmp(name, "readme") && value != NULL) {
76 if (*value == '/') 76 if (*value == '/')
77 ctx.repo->readme = xstrdup(value); 77 ctx.repo->readme = xstrdup(value);
78 else 78 else
79 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); 79 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
80 } 80 }
81} 81}
82 82
83void config_cb(const char *name, const char *value) 83void config_cb(const char *name, const char *value)
84{ 84{
85 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 85 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
86 ctx.cfg.section = xstrdup(value); 86 ctx.cfg.section = xstrdup(value);
87 else if (!strcmp(name, "repo.url")) 87 else if (!strcmp(name, "repo.url"))
88 ctx.repo = cgit_add_repo(value); 88 ctx.repo = cgit_add_repo(value);
89 else if (ctx.repo && !strcmp(name, "repo.path")) 89 else if (ctx.repo && !strcmp(name, "repo.path"))
90 ctx.repo->path = trim_end(value, '/'); 90 ctx.repo->path = trim_end(value, '/');
91 else if (ctx.repo && !prefixcmp(name, "repo.")) 91 else if (ctx.repo && !prefixcmp(name, "repo."))
92 repo_config(ctx.repo, name + 5, value); 92 repo_config(ctx.repo, name + 5, value);
93 else if (!strcmp(name, "root-title")) 93 else if (!strcmp(name, "root-title"))
94 ctx.cfg.root_title = xstrdup(value); 94 ctx.cfg.root_title = xstrdup(value);
95 else if (!strcmp(name, "root-desc")) 95 else if (!strcmp(name, "root-desc"))
96 ctx.cfg.root_desc = xstrdup(value); 96 ctx.cfg.root_desc = xstrdup(value);
97 else if (!strcmp(name, "root-readme")) 97 else if (!strcmp(name, "root-readme"))
98 ctx.cfg.root_readme = xstrdup(value); 98 ctx.cfg.root_readme = xstrdup(value);
99 else if (!strcmp(name, "css")) 99 else if (!strcmp(name, "css"))
100 ctx.cfg.css = xstrdup(value); 100 ctx.cfg.css = xstrdup(value);
101 else if (!strcmp(name, "favicon")) 101 else if (!strcmp(name, "favicon"))
102 ctx.cfg.favicon = xstrdup(value); 102 ctx.cfg.favicon = xstrdup(value);
103 else if (!strcmp(name, "footer")) 103 else if (!strcmp(name, "footer"))
104 ctx.cfg.footer = xstrdup(value); 104 ctx.cfg.footer = xstrdup(value);
105 else if (!strcmp(name, "head-include")) 105 else if (!strcmp(name, "head-include"))
106 ctx.cfg.head_include = xstrdup(value); 106 ctx.cfg.head_include = xstrdup(value);
107 else if (!strcmp(name, "header")) 107 else if (!strcmp(name, "header"))
108 ctx.cfg.header = xstrdup(value); 108 ctx.cfg.header = xstrdup(value);
109 else if (!strcmp(name, "logo")) 109 else if (!strcmp(name, "logo"))
110 ctx.cfg.logo = xstrdup(value); 110 ctx.cfg.logo = xstrdup(value);
111 else if (!strcmp(name, "index-header")) 111 else if (!strcmp(name, "index-header"))
112 ctx.cfg.index_header = xstrdup(value); 112 ctx.cfg.index_header = xstrdup(value);
113 else if (!strcmp(name, "index-info")) 113 else if (!strcmp(name, "index-info"))
114 ctx.cfg.index_info = xstrdup(value); 114 ctx.cfg.index_info = xstrdup(value);
115 else if (!strcmp(name, "logo-link")) 115 else if (!strcmp(name, "logo-link"))
116 ctx.cfg.logo_link = xstrdup(value); 116 ctx.cfg.logo_link = xstrdup(value);
117 else if (!strcmp(name, "module-link")) 117 else if (!strcmp(name, "module-link"))
118 ctx.cfg.module_link = xstrdup(value); 118 ctx.cfg.module_link = xstrdup(value);
119 else if (!strcmp(name, "virtual-root")) { 119 else if (!strcmp(name, "virtual-root")) {
120 ctx.cfg.virtual_root = trim_end(value, '/'); 120 ctx.cfg.virtual_root = trim_end(value, '/');
121 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 121 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
122 ctx.cfg.virtual_root = ""; 122 ctx.cfg.virtual_root = "";
123 } else if (!strcmp(name, "nocache")) 123 } else if (!strcmp(name, "nocache"))
124 ctx.cfg.nocache = atoi(value); 124 ctx.cfg.nocache = atoi(value);
125 else if (!strcmp(name, "noplainemail")) 125 else if (!strcmp(name, "noplainemail"))
126 ctx.cfg.noplainemail = atoi(value); 126 ctx.cfg.noplainemail = atoi(value);
127 else if (!strcmp(name, "noheader")) 127 else if (!strcmp(name, "noheader"))
128 ctx.cfg.noheader = atoi(value); 128 ctx.cfg.noheader = atoi(value);
129 else if (!strcmp(name, "snapshots")) 129 else if (!strcmp(name, "snapshots"))
130 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 130 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
131 else if (!strcmp(name, "enable-index-links")) 131 else if (!strcmp(name, "enable-index-links"))
132 ctx.cfg.enable_index_links = atoi(value); 132 ctx.cfg.enable_index_links = atoi(value);
133 else if (!strcmp(name, "enable-log-filecount")) 133 else if (!strcmp(name, "enable-log-filecount"))
134 ctx.cfg.enable_log_filecount = atoi(value); 134 ctx.cfg.enable_log_filecount = atoi(value);
135 else if (!strcmp(name, "enable-log-linecount")) 135 else if (!strcmp(name, "enable-log-linecount"))
136 ctx.cfg.enable_log_linecount = atoi(value); 136 ctx.cfg.enable_log_linecount = atoi(value);
137 else if (!strcmp(name, "max-stats")) 137 else if (!strcmp(name, "max-stats"))
138 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 138 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
139 else if (!strcmp(name, "cache-size")) 139 else if (!strcmp(name, "cache-size"))
140 ctx.cfg.cache_size = atoi(value); 140 ctx.cfg.cache_size = atoi(value);
141 else if (!strcmp(name, "cache-root")) 141 else if (!strcmp(name, "cache-root"))
142 ctx.cfg.cache_root = xstrdup(value); 142 ctx.cfg.cache_root = xstrdup(value);
143 else if (!strcmp(name, "cache-root-ttl")) 143 else if (!strcmp(name, "cache-root-ttl"))
144 ctx.cfg.cache_root_ttl = atoi(value); 144 ctx.cfg.cache_root_ttl = atoi(value);
145 else if (!strcmp(name, "cache-repo-ttl")) 145 else if (!strcmp(name, "cache-repo-ttl"))
146 ctx.cfg.cache_repo_ttl = atoi(value); 146 ctx.cfg.cache_repo_ttl = atoi(value);
147 else if (!strcmp(name, "cache-scanrc-ttl")) 147 else if (!strcmp(name, "cache-scanrc-ttl"))
148 ctx.cfg.cache_scanrc_ttl = atoi(value); 148 ctx.cfg.cache_scanrc_ttl = atoi(value);
149 else if (!strcmp(name, "cache-static-ttl")) 149 else if (!strcmp(name, "cache-static-ttl"))
150 ctx.cfg.cache_static_ttl = atoi(value); 150 ctx.cfg.cache_static_ttl = atoi(value);
151 else if (!strcmp(name, "cache-dynamic-ttl")) 151 else if (!strcmp(name, "cache-dynamic-ttl"))
152 ctx.cfg.cache_dynamic_ttl = atoi(value); 152 ctx.cfg.cache_dynamic_ttl = atoi(value);
153 else if (!strcmp(name, "about-filter")) 153 else if (!strcmp(name, "about-filter"))
154 ctx.cfg.about_filter = new_filter(value, 0); 154 ctx.cfg.about_filter = new_filter(value, 0);
155 else if (!strcmp(name, "commit-filter")) 155 else if (!strcmp(name, "commit-filter"))
156 ctx.cfg.commit_filter = new_filter(value, 0); 156 ctx.cfg.commit_filter = new_filter(value, 0);
157 else if (!strcmp(name, "embedded")) 157 else if (!strcmp(name, "embedded"))
158 ctx.cfg.embedded = atoi(value); 158 ctx.cfg.embedded = atoi(value);
159 else if (!strcmp(name, "max-message-length")) 159 else if (!strcmp(name, "max-message-length"))
160 ctx.cfg.max_msg_len = atoi(value); 160 ctx.cfg.max_msg_len = atoi(value);
161 else if (!strcmp(name, "max-repodesc-length")) 161 else if (!strcmp(name, "max-repodesc-length"))
162 ctx.cfg.max_repodesc_len = atoi(value); 162 ctx.cfg.max_repodesc_len = atoi(value);
163 else if (!strcmp(name, "max-repo-count")) 163 else if (!strcmp(name, "max-repo-count"))
164 ctx.cfg.max_repo_count = atoi(value); 164 ctx.cfg.max_repo_count = atoi(value);
165 else if (!strcmp(name, "max-commit-count")) 165 else if (!strcmp(name, "max-commit-count"))
166 ctx.cfg.max_commit_count = atoi(value); 166 ctx.cfg.max_commit_count = atoi(value);
167 else if (!strcmp(name, "scan-path")) 167 else if (!strcmp(name, "scan-path"))
168 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 168 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
169 process_cached_repolist(value); 169 process_cached_repolist(value);
170 else 170 else
171 scan_tree(value); 171 scan_tree(value, repo_config);
172 else if (!strcmp(name, "source-filter")) 172 else if (!strcmp(name, "source-filter"))
173 ctx.cfg.source_filter = new_filter(value, 1); 173 ctx.cfg.source_filter = new_filter(value, 1);
174 else if (!strcmp(name, "summary-log")) 174 else if (!strcmp(name, "summary-log"))
175 ctx.cfg.summary_log = atoi(value); 175 ctx.cfg.summary_log = atoi(value);
176 else if (!strcmp(name, "summary-branches")) 176 else if (!strcmp(name, "summary-branches"))
177 ctx.cfg.summary_branches = atoi(value); 177 ctx.cfg.summary_branches = atoi(value);
178 else if (!strcmp(name, "summary-tags")) 178 else if (!strcmp(name, "summary-tags"))
179 ctx.cfg.summary_tags = atoi(value); 179 ctx.cfg.summary_tags = atoi(value);
180 else if (!strcmp(name, "agefile")) 180 else if (!strcmp(name, "agefile"))
181 ctx.cfg.agefile = xstrdup(value); 181 ctx.cfg.agefile = xstrdup(value);
182 else if (!strcmp(name, "renamelimit")) 182 else if (!strcmp(name, "renamelimit"))
183 ctx.cfg.renamelimit = atoi(value); 183 ctx.cfg.renamelimit = atoi(value);
184 else if (!strcmp(name, "robots")) 184 else if (!strcmp(name, "robots"))
185 ctx.cfg.robots = xstrdup(value); 185 ctx.cfg.robots = xstrdup(value);
186 else if (!strcmp(name, "clone-prefix")) 186 else if (!strcmp(name, "clone-prefix"))
187 ctx.cfg.clone_prefix = xstrdup(value); 187 ctx.cfg.clone_prefix = xstrdup(value);
188 else if (!strcmp(name, "local-time")) 188 else if (!strcmp(name, "local-time"))
189 ctx.cfg.local_time = atoi(value); 189 ctx.cfg.local_time = atoi(value);
190 else if (!prefixcmp(name, "mimetype.")) 190 else if (!prefixcmp(name, "mimetype."))
191 add_mimetype(name + 9, value); 191 add_mimetype(name + 9, value);
192 else if (!strcmp(name, "include")) 192 else if (!strcmp(name, "include"))
193 parse_configfile(value, config_cb); 193 parse_configfile(value, config_cb);
194} 194}
195 195
196static void querystring_cb(const char *name, const char *value) 196static void querystring_cb(const char *name, const char *value)
197{ 197{
198 if (!value) 198 if (!value)
199 value = ""; 199 value = "";
200 200
201 if (!strcmp(name,"r")) { 201 if (!strcmp(name,"r")) {
202 ctx.qry.repo = xstrdup(value); 202 ctx.qry.repo = xstrdup(value);
203 ctx.repo = cgit_get_repoinfo(value); 203 ctx.repo = cgit_get_repoinfo(value);
204 } else if (!strcmp(name, "p")) { 204 } else if (!strcmp(name, "p")) {
205 ctx.qry.page = xstrdup(value); 205 ctx.qry.page = xstrdup(value);
206 } else if (!strcmp(name, "url")) { 206 } else if (!strcmp(name, "url")) {
207 ctx.qry.url = xstrdup(value); 207 ctx.qry.url = xstrdup(value);
208 cgit_parse_url(value); 208 cgit_parse_url(value);
209 } else if (!strcmp(name, "qt")) { 209 } else if (!strcmp(name, "qt")) {
210 ctx.qry.grep = xstrdup(value); 210 ctx.qry.grep = xstrdup(value);
211 } else if (!strcmp(name, "q")) { 211 } else if (!strcmp(name, "q")) {
212 ctx.qry.search = xstrdup(value); 212 ctx.qry.search = xstrdup(value);
213 } else if (!strcmp(name, "h")) { 213 } else if (!strcmp(name, "h")) {
214 ctx.qry.head = xstrdup(value); 214 ctx.qry.head = xstrdup(value);
215 ctx.qry.has_symref = 1; 215 ctx.qry.has_symref = 1;
216 } else if (!strcmp(name, "id")) { 216 } else if (!strcmp(name, "id")) {
217 ctx.qry.sha1 = xstrdup(value); 217 ctx.qry.sha1 = xstrdup(value);
218 ctx.qry.has_sha1 = 1; 218 ctx.qry.has_sha1 = 1;
219 } else if (!strcmp(name, "id2")) { 219 } else if (!strcmp(name, "id2")) {
220 ctx.qry.sha2 = xstrdup(value); 220 ctx.qry.sha2 = xstrdup(value);
221 ctx.qry.has_sha1 = 1; 221 ctx.qry.has_sha1 = 1;
222 } else if (!strcmp(name, "ofs")) { 222 } else if (!strcmp(name, "ofs")) {
223 ctx.qry.ofs = atoi(value); 223 ctx.qry.ofs = atoi(value);
224 } else if (!strcmp(name, "path")) { 224 } else if (!strcmp(name, "path")) {
225 ctx.qry.path = trim_end(value, '/'); 225 ctx.qry.path = trim_end(value, '/');
226 } else if (!strcmp(name, "name")) { 226 } else if (!strcmp(name, "name")) {
227 ctx.qry.name = xstrdup(value); 227 ctx.qry.name = xstrdup(value);
228 } else if (!strcmp(name, "mimetype")) { 228 } else if (!strcmp(name, "mimetype")) {
229 ctx.qry.mimetype = xstrdup(value); 229 ctx.qry.mimetype = xstrdup(value);
230 } else if (!strcmp(name, "s")){ 230 } else if (!strcmp(name, "s")){
231 ctx.qry.sort = xstrdup(value); 231 ctx.qry.sort = xstrdup(value);
232 } else if (!strcmp(name, "showmsg")) { 232 } else if (!strcmp(name, "showmsg")) {
233 ctx.qry.showmsg = atoi(value); 233 ctx.qry.showmsg = atoi(value);
234 } else if (!strcmp(name, "period")) { 234 } else if (!strcmp(name, "period")) {
235 ctx.qry.period = xstrdup(value); 235 ctx.qry.period = xstrdup(value);
236 } 236 }
237} 237}
238 238
239char *xstrdupn(const char *str) 239char *xstrdupn(const char *str)
240{ 240{
241 return (str ? xstrdup(str) : NULL); 241 return (str ? xstrdup(str) : NULL);
242} 242}
243 243
244static void prepare_context(struct cgit_context *ctx) 244static void prepare_context(struct cgit_context *ctx)
245{ 245{
246 memset(ctx, 0, sizeof(ctx)); 246 memset(ctx, 0, sizeof(ctx));
247 ctx->cfg.agefile = "info/web/last-modified"; 247 ctx->cfg.agefile = "info/web/last-modified";
248 ctx->cfg.nocache = 0; 248 ctx->cfg.nocache = 0;
249 ctx->cfg.cache_size = 0; 249 ctx->cfg.cache_size = 0;
250 ctx->cfg.cache_dynamic_ttl = 5; 250 ctx->cfg.cache_dynamic_ttl = 5;
251 ctx->cfg.cache_max_create_time = 5; 251 ctx->cfg.cache_max_create_time = 5;
252 ctx->cfg.cache_repo_ttl = 5; 252 ctx->cfg.cache_repo_ttl = 5;
253 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 253 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
254 ctx->cfg.cache_root_ttl = 5; 254 ctx->cfg.cache_root_ttl = 5;
255 ctx->cfg.cache_scanrc_ttl = 15; 255 ctx->cfg.cache_scanrc_ttl = 15;
256 ctx->cfg.cache_static_ttl = -1; 256 ctx->cfg.cache_static_ttl = -1;
257 ctx->cfg.css = "/cgit.css"; 257 ctx->cfg.css = "/cgit.css";
258 ctx->cfg.logo = "/cgit.png"; 258 ctx->cfg.logo = "/cgit.png";
259 ctx->cfg.local_time = 0; 259 ctx->cfg.local_time = 0;
260 ctx->cfg.max_repo_count = 50; 260 ctx->cfg.max_repo_count = 50;
261 ctx->cfg.max_commit_count = 50; 261 ctx->cfg.max_commit_count = 50;
262 ctx->cfg.max_lock_attempts = 5; 262 ctx->cfg.max_lock_attempts = 5;
263 ctx->cfg.max_msg_len = 80; 263 ctx->cfg.max_msg_len = 80;
264 ctx->cfg.max_repodesc_len = 80; 264 ctx->cfg.max_repodesc_len = 80;
265 ctx->cfg.max_stats = 0; 265 ctx->cfg.max_stats = 0;
266 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 266 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
267 ctx->cfg.renamelimit = -1; 267 ctx->cfg.renamelimit = -1;
268 ctx->cfg.robots = "index, nofollow"; 268 ctx->cfg.robots = "index, nofollow";
269 ctx->cfg.root_title = "Git repository browser"; 269 ctx->cfg.root_title = "Git repository browser";
270 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 270 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
271 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 271 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
272 ctx->cfg.summary_branches = 10; 272 ctx->cfg.summary_branches = 10;
273 ctx->cfg.summary_log = 10; 273 ctx->cfg.summary_log = 10;
274 ctx->cfg.summary_tags = 10; 274 ctx->cfg.summary_tags = 10;
275 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 275 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
276 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 276 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
277 ctx->env.https = xstrdupn(getenv("HTTPS")); 277 ctx->env.https = xstrdupn(getenv("HTTPS"));
278 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 278 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
279 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 279 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
280 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 280 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
281 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 281 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
282 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 282 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
283 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 283 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
284 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 284 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
285 ctx->page.mimetype = "text/html"; 285 ctx->page.mimetype = "text/html";
286 ctx->page.charset = PAGE_ENCODING; 286 ctx->page.charset = PAGE_ENCODING;
287 ctx->page.filename = NULL; 287 ctx->page.filename = NULL;
288 ctx->page.size = 0; 288 ctx->page.size = 0;
289 ctx->page.modified = time(NULL); 289 ctx->page.modified = time(NULL);
290 ctx->page.expires = ctx->page.modified; 290 ctx->page.expires = ctx->page.modified;
291 ctx->page.etag = NULL; 291 ctx->page.etag = NULL;
292 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 292 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
293 if (ctx->env.script_name) 293 if (ctx->env.script_name)
294 ctx->cfg.script_name = ctx->env.script_name; 294 ctx->cfg.script_name = ctx->env.script_name;
295 if (ctx->env.query_string) 295 if (ctx->env.query_string)
296 ctx->qry.raw = ctx->env.query_string; 296 ctx->qry.raw = ctx->env.query_string;
297 if (!ctx->env.cgit_config) 297 if (!ctx->env.cgit_config)
298 ctx->env.cgit_config = CGIT_CONFIG; 298 ctx->env.cgit_config = CGIT_CONFIG;
299} 299}
@@ -351,293 +351,293 @@ static int prepare_repo_cmd(struct cgit_context *ctx)
351 cgit_print_http_headers(ctx); 351 cgit_print_http_headers(ctx);
352 cgit_print_docstart(ctx); 352 cgit_print_docstart(ctx);
353 cgit_print_pageheader(ctx); 353 cgit_print_pageheader(ctx);
354 cgit_print_error(tmp); 354 cgit_print_error(tmp);
355 cgit_print_docend(); 355 cgit_print_docend();
356 return 1; 356 return 1;
357 } 357 }
358 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); 358 ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);
359 359
360 if (!ctx->qry.head) { 360 if (!ctx->qry.head) {
361 ctx->qry.nohead = 1; 361 ctx->qry.nohead = 1;
362 ctx->qry.head = find_default_branch(ctx->repo); 362 ctx->qry.head = find_default_branch(ctx->repo);
363 ctx->repo->defbranch = ctx->qry.head; 363 ctx->repo->defbranch = ctx->qry.head;
364 } 364 }
365 365
366 if (!ctx->qry.head) { 366 if (!ctx->qry.head) {
367 cgit_print_http_headers(ctx); 367 cgit_print_http_headers(ctx);
368 cgit_print_docstart(ctx); 368 cgit_print_docstart(ctx);
369 cgit_print_pageheader(ctx); 369 cgit_print_pageheader(ctx);
370 cgit_print_error("Repository seems to be empty"); 370 cgit_print_error("Repository seems to be empty");
371 cgit_print_docend(); 371 cgit_print_docend();
372 return 1; 372 return 1;
373 } 373 }
374 374
375 if (get_sha1(ctx->qry.head, sha1)) { 375 if (get_sha1(ctx->qry.head, sha1)) {
376 tmp = xstrdup(ctx->qry.head); 376 tmp = xstrdup(ctx->qry.head);
377 ctx->qry.head = ctx->repo->defbranch; 377 ctx->qry.head = ctx->repo->defbranch;
378 ctx->page.status = 404; 378 ctx->page.status = 404;
379 ctx->page.statusmsg = "not found"; 379 ctx->page.statusmsg = "not found";
380 cgit_print_http_headers(ctx); 380 cgit_print_http_headers(ctx);
381 cgit_print_docstart(ctx); 381 cgit_print_docstart(ctx);
382 cgit_print_pageheader(ctx); 382 cgit_print_pageheader(ctx);
383 cgit_print_error(fmt("Invalid branch: %s", tmp)); 383 cgit_print_error(fmt("Invalid branch: %s", tmp));
384 cgit_print_docend(); 384 cgit_print_docend();
385 return 1; 385 return 1;
386 } 386 }
387 return 0; 387 return 0;
388} 388}
389 389
390static void process_request(void *cbdata) 390static void process_request(void *cbdata)
391{ 391{
392 struct cgit_context *ctx = cbdata; 392 struct cgit_context *ctx = cbdata;
393 struct cgit_cmd *cmd; 393 struct cgit_cmd *cmd;
394 394
395 cmd = cgit_get_cmd(ctx); 395 cmd = cgit_get_cmd(ctx);
396 if (!cmd) { 396 if (!cmd) {
397 ctx->page.title = "cgit error"; 397 ctx->page.title = "cgit error";
398 cgit_print_http_headers(ctx); 398 cgit_print_http_headers(ctx);
399 cgit_print_docstart(ctx); 399 cgit_print_docstart(ctx);
400 cgit_print_pageheader(ctx); 400 cgit_print_pageheader(ctx);
401 cgit_print_error("Invalid request"); 401 cgit_print_error("Invalid request");
402 cgit_print_docend(); 402 cgit_print_docend();
403 return; 403 return;
404 } 404 }
405 405
406 if (cmd->want_repo && !ctx->repo) { 406 if (cmd->want_repo && !ctx->repo) {
407 cgit_print_http_headers(ctx); 407 cgit_print_http_headers(ctx);
408 cgit_print_docstart(ctx); 408 cgit_print_docstart(ctx);
409 cgit_print_pageheader(ctx); 409 cgit_print_pageheader(ctx);
410 cgit_print_error(fmt("No repository selected")); 410 cgit_print_error(fmt("No repository selected"));
411 cgit_print_docend(); 411 cgit_print_docend();
412 return; 412 return;
413 } 413 }
414 414
415 if (ctx->repo && prepare_repo_cmd(ctx)) 415 if (ctx->repo && prepare_repo_cmd(ctx))
416 return; 416 return;
417 417
418 if (cmd->want_layout) { 418 if (cmd->want_layout) {
419 cgit_print_http_headers(ctx); 419 cgit_print_http_headers(ctx);
420 cgit_print_docstart(ctx); 420 cgit_print_docstart(ctx);
421 cgit_print_pageheader(ctx); 421 cgit_print_pageheader(ctx);
422 } 422 }
423 423
424 cmd->fn(ctx); 424 cmd->fn(ctx);
425 425
426 if (cmd->want_layout) 426 if (cmd->want_layout)
427 cgit_print_docend(); 427 cgit_print_docend();
428} 428}
429 429
430int cmp_repos(const void *a, const void *b) 430int cmp_repos(const void *a, const void *b)
431{ 431{
432 const struct cgit_repo *ra = a, *rb = b; 432 const struct cgit_repo *ra = a, *rb = b;
433 return strcmp(ra->url, rb->url); 433 return strcmp(ra->url, rb->url);
434} 434}
435 435
436void print_repo(FILE *f, struct cgit_repo *repo) 436void print_repo(FILE *f, struct cgit_repo *repo)
437{ 437{
438 fprintf(f, "repo.url=%s\n", repo->url); 438 fprintf(f, "repo.url=%s\n", repo->url);
439 fprintf(f, "repo.name=%s\n", repo->name); 439 fprintf(f, "repo.name=%s\n", repo->name);
440 fprintf(f, "repo.path=%s\n", repo->path); 440 fprintf(f, "repo.path=%s\n", repo->path);
441 if (repo->owner) 441 if (repo->owner)
442 fprintf(f, "repo.owner=%s\n", repo->owner); 442 fprintf(f, "repo.owner=%s\n", repo->owner);
443 if (repo->desc) 443 if (repo->desc)
444 fprintf(f, "repo.desc=%s\n", repo->desc); 444 fprintf(f, "repo.desc=%s\n", repo->desc);
445 if (repo->readme) 445 if (repo->readme)
446 fprintf(f, "repo.readme=%s\n", repo->readme); 446 fprintf(f, "repo.readme=%s\n", repo->readme);
447 fprintf(f, "\n"); 447 fprintf(f, "\n");
448} 448}
449 449
450void print_repolist(FILE *f, struct cgit_repolist *list, int start) 450void print_repolist(FILE *f, struct cgit_repolist *list, int start)
451{ 451{
452 int i; 452 int i;
453 453
454 for(i = start; i < list->count; i++) 454 for(i = start; i < list->count; i++)
455 print_repo(f, &list->repos[i]); 455 print_repo(f, &list->repos[i]);
456} 456}
457 457
458/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' 458/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
459 * and return 0 on success. 459 * and return 0 on success.
460 */ 460 */
461static int generate_cached_repolist(const char *path, const char *cached_rc) 461static int generate_cached_repolist(const char *path, const char *cached_rc)
462{ 462{
463 char *locked_rc; 463 char *locked_rc;
464 int idx; 464 int idx;
465 FILE *f; 465 FILE *f;
466 466
467 locked_rc = xstrdup(fmt("%s.lock", cached_rc)); 467 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
468 f = fopen(locked_rc, "wx"); 468 f = fopen(locked_rc, "wx");
469 if (!f) { 469 if (!f) {
470 /* Inform about the error unless the lockfile already existed, 470 /* Inform about the error unless the lockfile already existed,
471 * since that only means we've got concurrent requests. 471 * since that only means we've got concurrent requests.
472 */ 472 */
473 if (errno != EEXIST) 473 if (errno != EEXIST)
474 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", 474 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
475 locked_rc, strerror(errno), errno); 475 locked_rc, strerror(errno), errno);
476 return errno; 476 return errno;
477 } 477 }
478 idx = cgit_repolist.count; 478 idx = cgit_repolist.count;
479 scan_tree(path); 479 scan_tree(path, repo_config);
480 print_repolist(f, &cgit_repolist, idx); 480 print_repolist(f, &cgit_repolist, idx);
481 if (rename(locked_rc, cached_rc)) 481 if (rename(locked_rc, cached_rc))
482 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", 482 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
483 locked_rc, cached_rc, strerror(errno), errno); 483 locked_rc, cached_rc, strerror(errno), errno);
484 fclose(f); 484 fclose(f);
485 return 0; 485 return 0;
486} 486}
487 487
488static void process_cached_repolist(const char *path) 488static void process_cached_repolist(const char *path)
489{ 489{
490 struct stat st; 490 struct stat st;
491 char *cached_rc; 491 char *cached_rc;
492 time_t age; 492 time_t age;
493 493
494 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, 494 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root,
495 hash_str(path))); 495 hash_str(path)));
496 496
497 if (stat(cached_rc, &st)) { 497 if (stat(cached_rc, &st)) {
498 /* Nothing is cached, we need to scan without forking. And 498 /* Nothing is cached, we need to scan without forking. And
499 * if we fail to generate a cached repolist, we need to 499 * if we fail to generate a cached repolist, we need to
500 * invoke scan_tree manually. 500 * invoke scan_tree manually.
501 */ 501 */
502 if (generate_cached_repolist(path, cached_rc)) 502 if (generate_cached_repolist(path, cached_rc))
503 scan_tree(path); 503 scan_tree(path, repo_config);
504 return; 504 return;
505 } 505 }
506 506
507 parse_configfile(cached_rc, config_cb); 507 parse_configfile(cached_rc, config_cb);
508 508
509 /* If the cached configfile hasn't expired, lets exit now */ 509 /* If the cached configfile hasn't expired, lets exit now */
510 age = time(NULL) - st.st_mtime; 510 age = time(NULL) - st.st_mtime;
511 if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) 511 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
512 return; 512 return;
513 513
514 /* The cached repolist has been parsed, but it was old. So lets 514 /* The cached repolist has been parsed, but it was old. So lets
515 * rescan the specified path and generate a new cached repolist 515 * rescan the specified path and generate a new cached repolist
516 * in a child-process to avoid latency for the current request. 516 * in a child-process to avoid latency for the current request.
517 */ 517 */
518 if (fork()) 518 if (fork())
519 return; 519 return;
520 520
521 exit(generate_cached_repolist(path, cached_rc)); 521 exit(generate_cached_repolist(path, cached_rc));
522} 522}
523 523
524static void cgit_parse_args(int argc, const char **argv) 524static void cgit_parse_args(int argc, const char **argv)
525{ 525{
526 int i; 526 int i;
527 int scan = 0; 527 int scan = 0;
528 528
529 for (i = 1; i < argc; i++) { 529 for (i = 1; i < argc; i++) {
530 if (!strncmp(argv[i], "--cache=", 8)) { 530 if (!strncmp(argv[i], "--cache=", 8)) {
531 ctx.cfg.cache_root = xstrdup(argv[i]+8); 531 ctx.cfg.cache_root = xstrdup(argv[i]+8);
532 } 532 }
533 if (!strcmp(argv[i], "--nocache")) { 533 if (!strcmp(argv[i], "--nocache")) {
534 ctx.cfg.nocache = 1; 534 ctx.cfg.nocache = 1;
535 } 535 }
536 if (!strcmp(argv[i], "--nohttp")) { 536 if (!strcmp(argv[i], "--nohttp")) {
537 ctx.env.no_http = "1"; 537 ctx.env.no_http = "1";
538 } 538 }
539 if (!strncmp(argv[i], "--query=", 8)) { 539 if (!strncmp(argv[i], "--query=", 8)) {
540 ctx.qry.raw = xstrdup(argv[i]+8); 540 ctx.qry.raw = xstrdup(argv[i]+8);
541 } 541 }
542 if (!strncmp(argv[i], "--repo=", 7)) { 542 if (!strncmp(argv[i], "--repo=", 7)) {
543 ctx.qry.repo = xstrdup(argv[i]+7); 543 ctx.qry.repo = xstrdup(argv[i]+7);
544 } 544 }
545 if (!strncmp(argv[i], "--page=", 7)) { 545 if (!strncmp(argv[i], "--page=", 7)) {
546 ctx.qry.page = xstrdup(argv[i]+7); 546 ctx.qry.page = xstrdup(argv[i]+7);
547 } 547 }
548 if (!strncmp(argv[i], "--head=", 7)) { 548 if (!strncmp(argv[i], "--head=", 7)) {
549 ctx.qry.head = xstrdup(argv[i]+7); 549 ctx.qry.head = xstrdup(argv[i]+7);
550 ctx.qry.has_symref = 1; 550 ctx.qry.has_symref = 1;
551 } 551 }
552 if (!strncmp(argv[i], "--sha1=", 7)) { 552 if (!strncmp(argv[i], "--sha1=", 7)) {
553 ctx.qry.sha1 = xstrdup(argv[i]+7); 553 ctx.qry.sha1 = xstrdup(argv[i]+7);
554 ctx.qry.has_sha1 = 1; 554 ctx.qry.has_sha1 = 1;
555 } 555 }
556 if (!strncmp(argv[i], "--ofs=", 6)) { 556 if (!strncmp(argv[i], "--ofs=", 6)) {
557 ctx.qry.ofs = atoi(argv[i]+6); 557 ctx.qry.ofs = atoi(argv[i]+6);
558 } 558 }
559 if (!strncmp(argv[i], "--scan-tree=", 12) || 559 if (!strncmp(argv[i], "--scan-tree=", 12) ||
560 !strncmp(argv[i], "--scan-path=", 12)) { 560 !strncmp(argv[i], "--scan-path=", 12)) {
561 scan++; 561 scan++;
562 scan_tree(argv[i] + 12); 562 scan_tree(argv[i] + 12, repo_config);
563 } 563 }
564 } 564 }
565 if (scan) { 565 if (scan) {
566 qsort(cgit_repolist.repos, cgit_repolist.count, 566 qsort(cgit_repolist.repos, cgit_repolist.count,
567 sizeof(struct cgit_repo), cmp_repos); 567 sizeof(struct cgit_repo), cmp_repos);
568 print_repolist(stdout, &cgit_repolist, 0); 568 print_repolist(stdout, &cgit_repolist, 0);
569 exit(0); 569 exit(0);
570 } 570 }
571} 571}
572 572
573static int calc_ttl() 573static int calc_ttl()
574{ 574{
575 if (!ctx.repo) 575 if (!ctx.repo)
576 return ctx.cfg.cache_root_ttl; 576 return ctx.cfg.cache_root_ttl;
577 577
578 if (!ctx.qry.page) 578 if (!ctx.qry.page)
579 return ctx.cfg.cache_repo_ttl; 579 return ctx.cfg.cache_repo_ttl;
580 580
581 if (ctx.qry.has_symref) 581 if (ctx.qry.has_symref)
582 return ctx.cfg.cache_dynamic_ttl; 582 return ctx.cfg.cache_dynamic_ttl;
583 583
584 if (ctx.qry.has_sha1) 584 if (ctx.qry.has_sha1)
585 return ctx.cfg.cache_static_ttl; 585 return ctx.cfg.cache_static_ttl;
586 586
587 return ctx.cfg.cache_repo_ttl; 587 return ctx.cfg.cache_repo_ttl;
588} 588}
589 589
590int main(int argc, const char **argv) 590int main(int argc, const char **argv)
591{ 591{
592 const char *path; 592 const char *path;
593 char *qry; 593 char *qry;
594 int err, ttl; 594 int err, ttl;
595 595
596 prepare_context(&ctx); 596 prepare_context(&ctx);
597 cgit_repolist.length = 0; 597 cgit_repolist.length = 0;
598 cgit_repolist.count = 0; 598 cgit_repolist.count = 0;
599 cgit_repolist.repos = NULL; 599 cgit_repolist.repos = NULL;
600 600
601 cgit_parse_args(argc, argv); 601 cgit_parse_args(argc, argv);
602 parse_configfile(ctx.env.cgit_config, config_cb); 602 parse_configfile(ctx.env.cgit_config, config_cb);
603 ctx.repo = NULL; 603 ctx.repo = NULL;
604 http_parse_querystring(ctx.qry.raw, querystring_cb); 604 http_parse_querystring(ctx.qry.raw, querystring_cb);
605 605
606 /* If virtual-root isn't specified in cgitrc, lets pretend 606 /* If virtual-root isn't specified in cgitrc, lets pretend
607 * that virtual-root equals SCRIPT_NAME. 607 * that virtual-root equals SCRIPT_NAME.
608 */ 608 */
609 if (!ctx.cfg.virtual_root) 609 if (!ctx.cfg.virtual_root)
610 ctx.cfg.virtual_root = ctx.cfg.script_name; 610 ctx.cfg.virtual_root = ctx.cfg.script_name;
611 611
612 /* If no url parameter is specified on the querystring, lets 612 /* If no url parameter is specified on the querystring, lets
613 * use PATH_INFO as url. This allows cgit to work with virtual 613 * use PATH_INFO as url. This allows cgit to work with virtual
614 * urls without the need for rewriterules in the webserver (as 614 * urls without the need for rewriterules in the webserver (as
615 * long as PATH_INFO is included in the cache lookup key). 615 * long as PATH_INFO is included in the cache lookup key).
616 */ 616 */
617 path = ctx.env.path_info; 617 path = ctx.env.path_info;
618 if (!ctx.qry.url && path) { 618 if (!ctx.qry.url && path) {
619 if (path[0] == '/') 619 if (path[0] == '/')
620 path++; 620 path++;
621 ctx.qry.url = xstrdup(path); 621 ctx.qry.url = xstrdup(path);
622 if (ctx.qry.raw) { 622 if (ctx.qry.raw) {
623 qry = ctx.qry.raw; 623 qry = ctx.qry.raw;
624 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); 624 ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
625 free(qry); 625 free(qry);
626 } else 626 } else
627 ctx.qry.raw = xstrdup(ctx.qry.url); 627 ctx.qry.raw = xstrdup(ctx.qry.url);
628 cgit_parse_url(ctx.qry.url); 628 cgit_parse_url(ctx.qry.url);
629 } 629 }
630 630
631 ttl = calc_ttl(); 631 ttl = calc_ttl();
632 ctx.page.expires += ttl*60; 632 ctx.page.expires += ttl*60;
633 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) 633 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
634 ctx.cfg.nocache = 1; 634 ctx.cfg.nocache = 1;
635 if (ctx.cfg.nocache) 635 if (ctx.cfg.nocache)
636 ctx.cfg.cache_size = 0; 636 ctx.cfg.cache_size = 0;
637 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 637 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
638 ctx.qry.raw, ttl, process_request, &ctx); 638 ctx.qry.raw, ttl, process_request, &ctx);
639 if (err) 639 if (err)
640 cgit_print_error(fmt("Error processing page: %s (%d)", 640 cgit_print_error(fmt("Error processing page: %s (%d)",
641 strerror(err), err)); 641 strerror(err), err));
642 return err; 642 return err;
643} 643}
diff --git a/cgit.h b/cgit.h
index fc7c7d5..3359be9 100644
--- a/cgit.h
+++ b/cgit.h
@@ -1,209 +1,212 @@
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 max_stats; 75 int max_stats;
76 time_t mtime; 76 time_t mtime;
77 struct cgit_filter *about_filter; 77 struct cgit_filter *about_filter;
78 struct cgit_filter *commit_filter; 78 struct cgit_filter *commit_filter;
79 struct cgit_filter *source_filter; 79 struct cgit_filter *source_filter;
80}; 80};
81 81
82typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
83 const char *value);
84
82struct cgit_repolist { 85struct cgit_repolist {
83 int length; 86 int length;
84 int count; 87 int count;
85 struct cgit_repo *repos; 88 struct cgit_repo *repos;
86}; 89};
87 90
88struct commitinfo { 91struct commitinfo {
89 struct commit *commit; 92 struct commit *commit;
90 char *author; 93 char *author;
91 char *author_email; 94 char *author_email;
92 unsigned long author_date; 95 unsigned long author_date;
93 char *committer; 96 char *committer;
94 char *committer_email; 97 char *committer_email;
95 unsigned long committer_date; 98 unsigned long committer_date;
96 char *subject; 99 char *subject;
97 char *msg; 100 char *msg;
98 char *msg_encoding; 101 char *msg_encoding;
99}; 102};
100 103
101struct taginfo { 104struct taginfo {
102 char *tagger; 105 char *tagger;
103 char *tagger_email; 106 char *tagger_email;
104 unsigned long tagger_date; 107 unsigned long tagger_date;
105 char *msg; 108 char *msg;
106}; 109};
107 110
108struct refinfo { 111struct refinfo {
109 const char *refname; 112 const char *refname;
110 struct object *object; 113 struct object *object;
111 union { 114 union {
112 struct taginfo *tag; 115 struct taginfo *tag;
113 struct commitinfo *commit; 116 struct commitinfo *commit;
114 }; 117 };
115}; 118};
116 119
117struct reflist { 120struct reflist {
118 struct refinfo **refs; 121 struct refinfo **refs;
119 int alloc; 122 int alloc;
120 int count; 123 int count;
121}; 124};
122 125
123struct cgit_query { 126struct cgit_query {
124 int has_symref; 127 int has_symref;
125 int has_sha1; 128 int has_sha1;
126 char *raw; 129 char *raw;
127 char *repo; 130 char *repo;
128 char *page; 131 char *page;
129 char *search; 132 char *search;
130 char *grep; 133 char *grep;
131 char *head; 134 char *head;
132 char *sha1; 135 char *sha1;
133 char *sha2; 136 char *sha2;
134 char *path; 137 char *path;
135 char *name; 138 char *name;
136 char *mimetype; 139 char *mimetype;
137 char *url; 140 char *url;
138 char *period; 141 char *period;
139 int ofs; 142 int ofs;
140 int nohead; 143 int nohead;
141 char *sort; 144 char *sort;
142 int showmsg; 145 int showmsg;
143}; 146};
144 147
145struct cgit_config { 148struct cgit_config {
146 char *agefile; 149 char *agefile;
147 char *cache_root; 150 char *cache_root;
148 char *clone_prefix; 151 char *clone_prefix;
149 char *css; 152 char *css;
150 char *favicon; 153 char *favicon;
151 char *footer; 154 char *footer;
152 char *head_include; 155 char *head_include;
153 char *header; 156 char *header;
154 char *index_header; 157 char *index_header;
155 char *index_info; 158 char *index_info;
156 char *logo; 159 char *logo;
157 char *logo_link; 160 char *logo_link;
158 char *module_link; 161 char *module_link;
159 char *robots; 162 char *robots;
160 char *root_title; 163 char *root_title;
161 char *root_desc; 164 char *root_desc;
162 char *root_readme; 165 char *root_readme;
163 char *script_name; 166 char *script_name;
164 char *section; 167 char *section;
165 char *virtual_root; 168 char *virtual_root;
166 int cache_size; 169 int cache_size;
167 int cache_dynamic_ttl; 170 int cache_dynamic_ttl;
168 int cache_max_create_time; 171 int cache_max_create_time;
169 int cache_repo_ttl; 172 int cache_repo_ttl;
170 int cache_root_ttl; 173 int cache_root_ttl;
171 int cache_scanrc_ttl; 174 int cache_scanrc_ttl;
172 int cache_static_ttl; 175 int cache_static_ttl;
173 int embedded; 176 int embedded;
174 int enable_index_links; 177 int enable_index_links;
175 int enable_log_filecount; 178 int enable_log_filecount;
176 int enable_log_linecount; 179 int enable_log_linecount;
177 int local_time; 180 int local_time;
178 int max_repo_count; 181 int max_repo_count;
179 int max_commit_count; 182 int max_commit_count;
180 int max_lock_attempts; 183 int max_lock_attempts;
181 int max_msg_len; 184 int max_msg_len;
182 int max_repodesc_len; 185 int max_repodesc_len;
183 int max_stats; 186 int max_stats;
184 int nocache; 187 int nocache;
185 int noplainemail; 188 int noplainemail;
186 int noheader; 189 int noheader;
187 int renamelimit; 190 int renamelimit;
188 int snapshots; 191 int snapshots;
189 int summary_branches; 192 int summary_branches;
190 int summary_log; 193 int summary_log;
191 int summary_tags; 194 int summary_tags;
192 struct string_list mimetypes; 195 struct string_list mimetypes;
193 struct cgit_filter *about_filter; 196 struct cgit_filter *about_filter;
194 struct cgit_filter *commit_filter; 197 struct cgit_filter *commit_filter;
195 struct cgit_filter *source_filter; 198 struct cgit_filter *source_filter;
196}; 199};
197 200
198struct cgit_page { 201struct cgit_page {
199 time_t modified; 202 time_t modified;
200 time_t expires; 203 time_t expires;
201 size_t size; 204 size_t size;
202 char *mimetype; 205 char *mimetype;
203 char *charset; 206 char *charset;
204 char *filename; 207 char *filename;
205 char *etag; 208 char *etag;
206 char *title; 209 char *title;
207 int status; 210 int status;
208 char *statusmsg; 211 char *statusmsg;
209}; 212};
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index e99c9f7..df494aa 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -201,256 +201,265 @@ renamelimit::
201 201
202repo.group:: 202repo.group::
203 Legacy alias for 'section' which will be deprecated starting with 203 Legacy alias for 'section' which will be deprecated starting with
204 cgit-1.0. 204 cgit-1.0.
205 205
206robots:: 206robots::
207 Text used as content for the "robots" meta-tag. Default value: 207 Text used as content for the "robots" meta-tag. Default value:
208 "index, nofollow". 208 "index, nofollow".
209 209
210root-desc:: 210root-desc::
211 Text printed below the heading on the repository index page. Default 211 Text printed below the heading on the repository index page. Default
212 value: "a fast webinterface for the git dscm". 212 value: "a fast webinterface for the git dscm".
213 213
214root-readme:: 214root-readme::
215 The content of the file specified with this option will be included 215 The content of the file specified with this option will be included
216 verbatim below the "about" link on the repository index page. Default 216 verbatim below the "about" link on the repository index page. Default
217 value: none. 217 value: none.
218 218
219root-title:: 219root-title::
220 Text printed as heading on the repository index page. Default value: 220 Text printed as heading on the repository index page. Default value:
221 "Git Repository Browser". 221 "Git Repository Browser".
222 222
223scan-path:: 223scan-path::
224 A path which will be scanned for repositories. If caching is enabled, 224 A path which will be scanned for repositories. If caching is enabled,
225 the result will be cached as a cgitrc include-file in the cache 225 the result will be cached as a cgitrc include-file in the cache
226 directory. Default value: none. See also: cache-scanrc-ttl. 226 directory. Default value: none. See also: cache-scanrc-ttl.
227 227
228section: 228section:
229 The name of the current repository section - all repositories defined 229 The name of the current repository section - all repositories defined
230 after this option will inherit the current section name. Default value: 230 after this option will inherit the current section name. Default value:
231 none. 231 none.
232 232
233snapshots:: 233snapshots::
234 Text which specifies the default set of snapshot formats generated by 234 Text which specifies the default set of snapshot formats generated by
235 cgit. The value is a space-separated list of zero or more of the 235 cgit. The value is a space-separated list of zero or more of the
236 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. 236 values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
237 237
238source-filter:: 238source-filter::
239 Specifies a command which will be invoked to format plaintext blobs 239 Specifies a command which will be invoked to format plaintext blobs
240 in the tree view. The command will get the blob content on its STDIN 240 in the tree view. The command will get the blob content on its STDIN
241 and the name of the blob as its only command line argument. The STDOUT 241 and the name of the blob as its only command line argument. The STDOUT
242 from the command will be included verbatim as the blob contents, i.e. 242 from the command will be included verbatim as the blob contents, i.e.
243 this can be used to implement e.g. syntax highlighting. Default value: 243 this can be used to implement e.g. syntax highlighting. Default value:
244 none. 244 none.
245 245
246summary-branches:: 246summary-branches::
247 Specifies the number of branches to display in the repository "summary" 247 Specifies the number of branches to display in the repository "summary"
248 view. Default value: "10". 248 view. Default value: "10".
249 249
250summary-log:: 250summary-log::
251 Specifies the number of log entries to display in the repository 251 Specifies the number of log entries to display in the repository
252 "summary" view. Default value: "10". 252 "summary" view. Default value: "10".
253 253
254summary-tags:: 254summary-tags::
255 Specifies the number of tags to display in the repository "summary" 255 Specifies the number of tags to display in the repository "summary"
256 view. Default value: "10". 256 view. Default value: "10".
257 257
258virtual-root:: 258virtual-root::
259 Url which, if specified, will be used as root for all cgit links. It 259 Url which, if specified, will be used as root for all cgit links. It
260 will also cause cgit to generate 'virtual urls', i.e. urls like 260 will also cause cgit to generate 'virtual urls', i.e. urls like
261 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default 261 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default
262 value: none. 262 value: none.
263 NOTE: cgit has recently learned how to use PATH_INFO to achieve the 263 NOTE: cgit has recently learned how to use PATH_INFO to achieve the
264 same kind of virtual urls, so this option will probably be deprecated. 264 same kind of virtual urls, so this option will probably be deprecated.
265 265
266REPOSITORY SETTINGS 266REPOSITORY SETTINGS
267------------------- 267-------------------
268repo.about-filter:: 268repo.about-filter::
269 Override the default about-filter. Default value: <about-filter>. 269 Override the default about-filter. Default value: <about-filter>.
270 270
271repo.clone-url:: 271repo.clone-url::
272 A list of space-separated urls which can be used to clone this repo. 272 A list of space-separated urls which can be used to clone this repo.
273 Default value: none. 273 Default value: none.
274 274
275repo.commit-filter:: 275repo.commit-filter::
276 Override the default commit-filter. Default value: <commit-filter>. 276 Override the default commit-filter. Default value: <commit-filter>.
277 277
278repo.defbranch:: 278repo.defbranch::
279 The name of the default branch for this repository. If no such branch 279 The name of the default branch for this repository. If no such branch
280 exists in the repository, the first branch name (when sorted) is used 280 exists in the repository, the first branch name (when sorted) is used
281 as default instead. Default value: "master". 281 as default instead. Default value: "master".
282 282
283repo.desc:: 283repo.desc::
284 The value to show as repository description. Default value: none. 284 The value to show as repository description. Default value: none.
285 285
286repo.enable-log-filecount:: 286repo.enable-log-filecount::
287 A flag which can be used to disable the global setting 287 A flag which can be used to disable the global setting
288 `enable-log-filecount'. Default value: none. 288 `enable-log-filecount'. Default value: none.
289 289
290repo.enable-log-linecount:: 290repo.enable-log-linecount::
291 A flag which can be used to disable the global setting 291 A flag which can be used to disable the global setting
292 `enable-log-linecount'. Default value: none. 292 `enable-log-linecount'. Default value: none.
293 293
294repo.max-stats:: 294repo.max-stats::
295 Override the default maximum statistics period. Valid values are equal 295 Override the default maximum statistics period. Valid values are equal
296 to the values specified for the global "max-stats" setting. Default 296 to the values specified for the global "max-stats" setting. Default
297 value: none. 297 value: none.
298 298
299repo.name:: 299repo.name::
300 The value to show as repository name. Default value: <repo.url>. 300 The value to show as repository name. Default value: <repo.url>.
301 301
302repo.owner:: 302repo.owner::
303 A value used to identify the owner of the repository. Default value: 303 A value used to identify the owner of the repository. Default value:
304 none. 304 none.
305 305
306repo.path:: 306repo.path::
307 An absolute path to the repository directory. For non-bare repositories 307 An absolute path to the repository directory. For non-bare repositories
308 this is the .git-directory. Default value: none. 308 this is the .git-directory. Default value: none.
309 309
310repo.readme:: 310repo.readme::
311 A path (relative to <repo.path>) which specifies a file to include 311 A path (relative to <repo.path>) which specifies a file to include
312 verbatim as the "About" page for this repo. Default value: none. 312 verbatim as the "About" page for this repo. Default value: none.
313 313
314repo.snapshots:: 314repo.snapshots::
315 A mask of allowed snapshot-formats for this repo, restricted by the 315 A mask of allowed snapshot-formats for this repo, restricted by the
316 "snapshots" global setting. Default value: <snapshots>. 316 "snapshots" global setting. Default value: <snapshots>.
317 317
318repo.section:: 318repo.section::
319 Override the current section for this repository. Default value: none. 319 Override the current section for this repository. Default value: none.
320 320
321repo.source-filter:: 321repo.source-filter::
322 Override the default source-filter. Default value: <source-filter>. 322 Override the default source-filter. Default value: <source-filter>.
323 323
324repo.url:: 324repo.url::
325 The relative url used to access the repository. This must be the first 325 The relative url used to access the repository. This must be the first
326 setting specified for each repo. Default value: none. 326 setting specified for each repo. Default value: none.
327 327
328 328
329REPOSITORY-SPECIFIC CGITRC FILE
330-------------------------------
331When the option 'scan-path' is used to auto-discover git repositories, cgit
332will try to parse the file 'cgitrc' within any found repository. Such a repo-
333specific config file may contain any of the repo-specific options described
334above, except 'repo.url' and 'repo.path'. Also, in a repo-specific config
335file, the 'repo.' prefix is dropped from the config option names.
336
337
329EXAMPLE CGITRC FILE 338EXAMPLE CGITRC FILE
330------------------- 339-------------------
331 340
332.... 341....
333# Enable caching of up to 1000 output entriess 342# Enable caching of up to 1000 output entriess
334cache-size=1000 343cache-size=1000
335 344
336 345
337# Specify some default clone prefixes 346# Specify some default clone prefixes
338clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git 347clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
339 348
340# Specify the css url 349# Specify the css url
341css=/css/cgit.css 350css=/css/cgit.css
342 351
343 352
344# Show extra links for each repository on the index page 353# Show extra links for each repository on the index page
345enable-index-links=1 354enable-index-links=1
346 355
347 356
348# Show number of affected files per commit on the log pages 357# Show number of affected files per commit on the log pages
349enable-log-filecount=1 358enable-log-filecount=1
350 359
351 360
352# Show number of added/removed lines per commit on the log pages 361# Show number of added/removed lines per commit on the log pages
353enable-log-linecount=1 362enable-log-linecount=1
354 363
355 364
356# Add a cgit favicon 365# Add a cgit favicon
357favicon=/favicon.ico 366favicon=/favicon.ico
358 367
359 368
360# Use a custom logo 369# Use a custom logo
361logo=/img/mylogo.png 370logo=/img/mylogo.png
362 371
363 372
364# Enable statistics per week, month and quarter 373# Enable statistics per week, month and quarter
365max-stats=quarter 374max-stats=quarter
366 375
367 376
368# Set the title and heading of the repository index page 377# Set the title and heading of the repository index page
369root-title=foobar.com git repositories 378root-title=foobar.com git repositories
370 379
371 380
372# Set a subheading for the repository index page 381# Set a subheading for the repository index page
373root-desc=tracking the foobar development 382root-desc=tracking the foobar development
374 383
375 384
376# Include some more info about foobar.com on the index page 385# Include some more info about foobar.com on the index page
377root-readme=/var/www/htdocs/about.html 386root-readme=/var/www/htdocs/about.html
378 387
379 388
380# Allow download of tar.gz, tar.bz2 and zip-files 389# Allow download of tar.gz, tar.bz2 and zip-files
381snapshots=tar.gz tar.bz2 zip 390snapshots=tar.gz tar.bz2 zip
382 391
383 392
384## 393##
385## List of common mimetypes 394## List of common mimetypes
386## 395##
387 396
388mimetype.git=image/git 397mimetype.git=image/git
389mimetype.html=text/html 398mimetype.html=text/html
390mimetype.jpg=image/jpeg 399mimetype.jpg=image/jpeg
391mimetype.jpeg=image/jpeg 400mimetype.jpeg=image/jpeg
392mimetype.pdf=application/pdf 401mimetype.pdf=application/pdf
393mimetype.png=image/png 402mimetype.png=image/png
394mimetype.svg=image/svg+xml 403mimetype.svg=image/svg+xml
395 404
396 405
397## 406##
398## List of repositories. 407## List of repositories.
399## PS: Any repositories listed when repo.group is unset will not be 408## PS: Any repositories listed when repo.group is unset will not be
400## displayed under a group heading 409## displayed under a group heading
401## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') 410## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos')
402## and included like this: 411## and included like this:
403## include=/etc/cgitrepos 412## include=/etc/cgitrepos
404## 413##
405 414
406 415
407repo.url=foo 416repo.url=foo
408repo.path=/pub/git/foo.git 417repo.path=/pub/git/foo.git
409repo.desc=the master foo repository 418repo.desc=the master foo repository
410repo.owner=fooman@foobar.com 419repo.owner=fooman@foobar.com
411repo.readme=info/web/about.html 420repo.readme=info/web/about.html
412 421
413 422
414repo.url=bar 423repo.url=bar
415repo.path=/pub/git/bar.git 424repo.path=/pub/git/bar.git
416repo.desc=the bars for your foo 425repo.desc=the bars for your foo
417repo.owner=barman@foobar.com 426repo.owner=barman@foobar.com
418repo.readme=info/web/about.html 427repo.readme=info/web/about.html
419 428
420 429
421# The next repositories will be displayed under the 'extras' heading 430# The next repositories will be displayed under the 'extras' heading
422repo.group=extras 431repo.group=extras
423 432
424 433
425repo.url=baz 434repo.url=baz
426repo.path=/pub/git/baz.git 435repo.path=/pub/git/baz.git
427repo.desc=a set of extensions for bar users 436repo.desc=a set of extensions for bar users
428 437
429repo.url=wiz 438repo.url=wiz
430repo.path=/pub/git/wiz.git 439repo.path=/pub/git/wiz.git
431repo.desc=the wizard of foo 440repo.desc=the wizard of foo
432 441
433 442
434# Add some mirrored repositories 443# Add some mirrored repositories
435repo.group=mirrors 444repo.group=mirrors
436 445
437 446
438repo.url=git 447repo.url=git
439repo.path=/pub/git/git.git 448repo.path=/pub/git/git.git
440repo.desc=the dscm 449repo.desc=the dscm
441 450
442 451
443repo.url=linux 452repo.url=linux
444repo.path=/pub/git/linux.git 453repo.path=/pub/git/linux.git
445repo.desc=the kernel 454repo.desc=the kernel
446 455
447# Disable adhoc downloads of this repo 456# Disable adhoc downloads of this repo
448repo.snapshots=0 457repo.snapshots=0
449 458
450# Disable line-counts for this repo 459# Disable line-counts for this repo
451repo.enable-log-linecount=0 460repo.enable-log-linecount=0
452 461
453# Restrict the max statistics period for this repo 462# Restrict the max statistics period for this repo
454repo.max-stats=month 463repo.max-stats=month
455.... 464....
456 465
diff --git a/scan-tree.c b/scan-tree.c
index 67f4550..dbca797 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -1,132 +1,146 @@
1#include "cgit.h" 1#include "cgit.h"
2#include "configfile.h"
2#include "html.h" 3#include "html.h"
3 4
4#define MAX_PATH 4096 5#define MAX_PATH 4096
5 6
6/* return 1 if path contains a objects/ directory and a HEAD file */ 7/* return 1 if path contains a objects/ directory and a HEAD file */
7static int is_git_dir(const char *path) 8static int is_git_dir(const char *path)
8{ 9{
9 struct stat st; 10 struct stat st;
10 static char buf[MAX_PATH]; 11 static char buf[MAX_PATH];
11 12
12 if (snprintf(buf, MAX_PATH, "%s/objects", path) >= MAX_PATH) { 13 if (snprintf(buf, MAX_PATH, "%s/objects", path) >= MAX_PATH) {
13 fprintf(stderr, "Insanely long path: %s\n", path); 14 fprintf(stderr, "Insanely long path: %s\n", path);
14 return 0; 15 return 0;
15 } 16 }
16 if (stat(buf, &st)) { 17 if (stat(buf, &st)) {
17 if (errno != ENOENT) 18 if (errno != ENOENT)
18 fprintf(stderr, "Error checking path %s: %s (%d)\n", 19 fprintf(stderr, "Error checking path %s: %s (%d)\n",
19 path, strerror(errno), errno); 20 path, strerror(errno), errno);
20 return 0; 21 return 0;
21 } 22 }
22 if (!S_ISDIR(st.st_mode)) 23 if (!S_ISDIR(st.st_mode))
23 return 0; 24 return 0;
24 25
25 sprintf(buf, "%s/HEAD", path); 26 sprintf(buf, "%s/HEAD", path);
26 if (stat(buf, &st)) { 27 if (stat(buf, &st)) {
27 if (errno != ENOENT) 28 if (errno != ENOENT)
28 fprintf(stderr, "Error checking path %s: %s (%d)\n", 29 fprintf(stderr, "Error checking path %s: %s (%d)\n",
29 path, strerror(errno), errno); 30 path, strerror(errno), errno);
30 return 0; 31 return 0;
31 } 32 }
32 if (!S_ISREG(st.st_mode)) 33 if (!S_ISREG(st.st_mode))
33 return 0; 34 return 0;
34 35
35 return 1; 36 return 1;
36} 37}
37 38
38static void add_repo(const char *base, const char *path) 39struct cgit_repo *repo;
40repo_config_fn config_fn;
41
42static void repo_config(const char *name, const char *value)
43{
44 config_fn(repo, name, value);
45}
46
47static void add_repo(const char *base, const char *path, repo_config_fn fn)
39{ 48{
40 struct cgit_repo *repo;
41 struct stat st; 49 struct stat st;
42 struct passwd *pwd; 50 struct passwd *pwd;
43 char *p; 51 char *p;
44 size_t size; 52 size_t size;
45 53
46 if (stat(path, &st)) { 54 if (stat(path, &st)) {
47 fprintf(stderr, "Error accessing %s: %s (%d)\n", 55 fprintf(stderr, "Error accessing %s: %s (%d)\n",
48 path, strerror(errno), errno); 56 path, strerror(errno), errno);
49 return; 57 return;
50 } 58 }
51 if ((pwd = getpwuid(st.st_uid)) == NULL) { 59 if ((pwd = getpwuid(st.st_uid)) == NULL) {
52 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", 60 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
53 path, strerror(errno), errno); 61 path, strerror(errno), errno);
54 return; 62 return;
55 } 63 }
56 if (base == path) 64 if (base == path)
57 p = fmt("%s", path); 65 p = fmt("%s", path);
58 else 66 else
59 p = fmt("%s", path + strlen(base) + 1); 67 p = fmt("%s", path + strlen(base) + 1);
60 68
61 if (!strcmp(p + strlen(p) - 5, "/.git")) 69 if (!strcmp(p + strlen(p) - 5, "/.git"))
62 p[strlen(p) - 5] = '\0'; 70 p[strlen(p) - 5] = '\0';
63 71
64 repo = cgit_add_repo(xstrdup(p)); 72 repo = cgit_add_repo(xstrdup(p));
65 repo->name = repo->url; 73 repo->name = repo->url;
66 repo->path = xstrdup(path); 74 repo->path = xstrdup(path);
67 p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL; 75 p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL;
68 if (p) 76 if (p)
69 *p = '\0'; 77 *p = '\0';
70 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : ""); 78 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : "");
71 79
72 p = fmt("%s/description", path); 80 p = fmt("%s/description", path);
73 if (!stat(p, &st)) 81 if (!stat(p, &st))
74 readfile(p, &repo->desc, &size); 82 readfile(p, &repo->desc, &size);
75 83
76 p = fmt("%s/README.html", path); 84 p = fmt("%s/README.html", path);
77 if (!stat(p, &st)) 85 if (!stat(p, &st))
78 repo->readme = "README.html"; 86 repo->readme = "README.html";
87
88 p = fmt("%s/cgitrc", path);
89 if (!stat(p, &st)) {
90 config_fn = fn;
91 parse_configfile(xstrdup(p), &repo_config);
92 }
79} 93}
80 94
81static void scan_path(const char *base, const char *path) 95static void scan_path(const char *base, const char *path, repo_config_fn fn)
82{ 96{
83 DIR *dir; 97 DIR *dir;
84 struct dirent *ent; 98 struct dirent *ent;
85 char *buf; 99 char *buf;
86 struct stat st; 100 struct stat st;
87 101
88 if (is_git_dir(path)) { 102 if (is_git_dir(path)) {
89 add_repo(base, path); 103 add_repo(base, path, fn);
90 return; 104 return;
91 } 105 }
92 if (is_git_dir(fmt("%s/.git", path))) { 106 if (is_git_dir(fmt("%s/.git", path))) {
93 add_repo(base, fmt("%s/.git", path)); 107 add_repo(base, fmt("%s/.git", path), fn);
94 return; 108 return;
95 } 109 }
96 dir = opendir(path); 110 dir = opendir(path);
97 if (!dir) { 111 if (!dir) {
98 fprintf(stderr, "Error opening directory %s: %s (%d)\n", 112 fprintf(stderr, "Error opening directory %s: %s (%d)\n",
99 path, strerror(errno), errno); 113 path, strerror(errno), errno);
100 return; 114 return;
101 } 115 }
102 while((ent = readdir(dir)) != NULL) { 116 while((ent = readdir(dir)) != NULL) {
103 if (ent->d_name[0] == '.') { 117 if (ent->d_name[0] == '.') {
104 if (ent->d_name[1] == '\0') 118 if (ent->d_name[1] == '\0')
105 continue; 119 continue;
106 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0') 120 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
107 continue; 121 continue;
108 } 122 }
109 buf = malloc(strlen(path) + strlen(ent->d_name) + 2); 123 buf = malloc(strlen(path) + strlen(ent->d_name) + 2);
110 if (!buf) { 124 if (!buf) {
111 fprintf(stderr, "Alloc error on %s: %s (%d)\n", 125 fprintf(stderr, "Alloc error on %s: %s (%d)\n",
112 path, strerror(errno), errno); 126 path, strerror(errno), errno);
113 exit(1); 127 exit(1);
114 } 128 }
115 sprintf(buf, "%s/%s", path, ent->d_name); 129 sprintf(buf, "%s/%s", path, ent->d_name);
116 if (stat(buf, &st)) { 130 if (stat(buf, &st)) {
117 fprintf(stderr, "Error checking path %s: %s (%d)\n", 131 fprintf(stderr, "Error checking path %s: %s (%d)\n",
118 buf, strerror(errno), errno); 132 buf, strerror(errno), errno);
119 free(buf); 133 free(buf);
120 continue; 134 continue;
121 } 135 }
122 if (S_ISDIR(st.st_mode)) 136 if (S_ISDIR(st.st_mode))
123 scan_path(base, buf); 137 scan_path(base, buf, fn);
124 free(buf); 138 free(buf);
125 } 139 }
126 closedir(dir); 140 closedir(dir);
127} 141}
128 142
129void scan_tree(const char *path) 143void scan_tree(const char *path, repo_config_fn fn)
130{ 144{
131 scan_path(path, path); 145 scan_path(path, path, fn);
132} 146}
diff --git a/scan-tree.h b/scan-tree.h
index b103b16..11539f4 100644
--- a/scan-tree.h
+++ b/scan-tree.h
@@ -1,3 +1,3 @@
1 1
2 2
3extern void scan_tree(const char *path); 3extern void scan_tree(const char *path, repo_config_fn fn);