summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--cgit.c3
-rw-r--r--cgit.h1
-rw-r--r--cgitrc.5.txt5
-rw-r--r--scan-tree.c33
4 files changed, 33 insertions, 9 deletions
diff --git a/cgit.c b/cgit.c
index f9a42bb..eff5b7a 100644
--- a/cgit.c
+++ b/cgit.c
@@ -74,128 +74,130 @@ void repo_config(struct cgit_repo *repo, const char *name, const char *value)
74 if (*value == '/') 74 if (*value == '/')
75 repo->readme = xstrdup(value); 75 repo->readme = xstrdup(value);
76 else 76 else
77 repo->readme = xstrdup(fmt("%s/%s", repo->path, value)); 77 repo->readme = xstrdup(fmt("%s/%s", repo->path, value));
78 } else if (ctx.cfg.enable_filter_overrides) { 78 } else if (ctx.cfg.enable_filter_overrides) {
79 if (!strcmp(name, "about-filter")) 79 if (!strcmp(name, "about-filter"))
80 repo->about_filter = new_filter(value, 0); 80 repo->about_filter = new_filter(value, 0);
81 else if (!strcmp(name, "commit-filter")) 81 else if (!strcmp(name, "commit-filter"))
82 repo->commit_filter = new_filter(value, 0); 82 repo->commit_filter = new_filter(value, 0);
83 else if (!strcmp(name, "source-filter")) 83 else if (!strcmp(name, "source-filter"))
84 repo->source_filter = new_filter(value, 1); 84 repo->source_filter = new_filter(value, 1);
85 } 85 }
86} 86}
87 87
88void config_cb(const char *name, const char *value) 88void config_cb(const char *name, const char *value)
89{ 89{
90 if (!strcmp(name, "section") || !strcmp(name, "repo.group")) 90 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
91 ctx.cfg.section = xstrdup(value); 91 ctx.cfg.section = xstrdup(value);
92 else if (!strcmp(name, "repo.url")) 92 else if (!strcmp(name, "repo.url"))
93 ctx.repo = cgit_add_repo(value); 93 ctx.repo = cgit_add_repo(value);
94 else if (ctx.repo && !strcmp(name, "repo.path")) 94 else if (ctx.repo && !strcmp(name, "repo.path"))
95 ctx.repo->path = trim_end(value, '/'); 95 ctx.repo->path = trim_end(value, '/');
96 else if (ctx.repo && !prefixcmp(name, "repo.")) 96 else if (ctx.repo && !prefixcmp(name, "repo."))
97 repo_config(ctx.repo, name + 5, value); 97 repo_config(ctx.repo, name + 5, value);
98 else if (!strcmp(name, "root-title")) 98 else if (!strcmp(name, "root-title"))
99 ctx.cfg.root_title = xstrdup(value); 99 ctx.cfg.root_title = xstrdup(value);
100 else if (!strcmp(name, "root-desc")) 100 else if (!strcmp(name, "root-desc"))
101 ctx.cfg.root_desc = xstrdup(value); 101 ctx.cfg.root_desc = xstrdup(value);
102 else if (!strcmp(name, "root-readme")) 102 else if (!strcmp(name, "root-readme"))
103 ctx.cfg.root_readme = xstrdup(value); 103 ctx.cfg.root_readme = xstrdup(value);
104 else if (!strcmp(name, "css")) 104 else if (!strcmp(name, "css"))
105 ctx.cfg.css = xstrdup(value); 105 ctx.cfg.css = xstrdup(value);
106 else if (!strcmp(name, "favicon")) 106 else if (!strcmp(name, "favicon"))
107 ctx.cfg.favicon = xstrdup(value); 107 ctx.cfg.favicon = xstrdup(value);
108 else if (!strcmp(name, "footer")) 108 else if (!strcmp(name, "footer"))
109 ctx.cfg.footer = xstrdup(value); 109 ctx.cfg.footer = xstrdup(value);
110 else if (!strcmp(name, "head-include")) 110 else if (!strcmp(name, "head-include"))
111 ctx.cfg.head_include = xstrdup(value); 111 ctx.cfg.head_include = xstrdup(value);
112 else if (!strcmp(name, "header")) 112 else if (!strcmp(name, "header"))
113 ctx.cfg.header = xstrdup(value); 113 ctx.cfg.header = xstrdup(value);
114 else if (!strcmp(name, "logo")) 114 else if (!strcmp(name, "logo"))
115 ctx.cfg.logo = xstrdup(value); 115 ctx.cfg.logo = xstrdup(value);
116 else if (!strcmp(name, "index-header")) 116 else if (!strcmp(name, "index-header"))
117 ctx.cfg.index_header = xstrdup(value); 117 ctx.cfg.index_header = xstrdup(value);
118 else if (!strcmp(name, "index-info")) 118 else if (!strcmp(name, "index-info"))
119 ctx.cfg.index_info = xstrdup(value); 119 ctx.cfg.index_info = xstrdup(value);
120 else if (!strcmp(name, "logo-link")) 120 else if (!strcmp(name, "logo-link"))
121 ctx.cfg.logo_link = xstrdup(value); 121 ctx.cfg.logo_link = xstrdup(value);
122 else if (!strcmp(name, "module-link")) 122 else if (!strcmp(name, "module-link"))
123 ctx.cfg.module_link = xstrdup(value); 123 ctx.cfg.module_link = xstrdup(value);
124 else if (!strcmp(name, "virtual-root")) { 124 else if (!strcmp(name, "virtual-root")) {
125 ctx.cfg.virtual_root = trim_end(value, '/'); 125 ctx.cfg.virtual_root = trim_end(value, '/');
126 if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) 126 if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
127 ctx.cfg.virtual_root = ""; 127 ctx.cfg.virtual_root = "";
128 } else if (!strcmp(name, "nocache")) 128 } else if (!strcmp(name, "nocache"))
129 ctx.cfg.nocache = atoi(value); 129 ctx.cfg.nocache = atoi(value);
130 else if (!strcmp(name, "noplainemail")) 130 else if (!strcmp(name, "noplainemail"))
131 ctx.cfg.noplainemail = atoi(value); 131 ctx.cfg.noplainemail = atoi(value);
132 else if (!strcmp(name, "noheader")) 132 else if (!strcmp(name, "noheader"))
133 ctx.cfg.noheader = atoi(value); 133 ctx.cfg.noheader = atoi(value);
134 else if (!strcmp(name, "snapshots")) 134 else if (!strcmp(name, "snapshots"))
135 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 135 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
136 else if (!strcmp(name, "enable-filter-overrides")) 136 else if (!strcmp(name, "enable-filter-overrides"))
137 ctx.cfg.enable_filter_overrides = atoi(value); 137 ctx.cfg.enable_filter_overrides = atoi(value);
138 else if (!strcmp(name, "enable-gitweb-owner"))
139 ctx.cfg.enable_gitweb_owner = atoi(value);
138 else if (!strcmp(name, "enable-index-links")) 140 else if (!strcmp(name, "enable-index-links"))
139 ctx.cfg.enable_index_links = atoi(value); 141 ctx.cfg.enable_index_links = atoi(value);
140 else if (!strcmp(name, "enable-log-filecount")) 142 else if (!strcmp(name, "enable-log-filecount"))
141 ctx.cfg.enable_log_filecount = atoi(value); 143 ctx.cfg.enable_log_filecount = atoi(value);
142 else if (!strcmp(name, "enable-log-linecount")) 144 else if (!strcmp(name, "enable-log-linecount"))
143 ctx.cfg.enable_log_linecount = atoi(value); 145 ctx.cfg.enable_log_linecount = atoi(value);
144 else if (!strcmp(name, "enable-remote-branches")) 146 else if (!strcmp(name, "enable-remote-branches"))
145 ctx.cfg.enable_remote_branches = atoi(value); 147 ctx.cfg.enable_remote_branches = atoi(value);
146 else if (!strcmp(name, "enable-subject-links")) 148 else if (!strcmp(name, "enable-subject-links"))
147 ctx.cfg.enable_subject_links = atoi(value); 149 ctx.cfg.enable_subject_links = atoi(value);
148 else if (!strcmp(name, "enable-tree-linenumbers")) 150 else if (!strcmp(name, "enable-tree-linenumbers"))
149 ctx.cfg.enable_tree_linenumbers = atoi(value); 151 ctx.cfg.enable_tree_linenumbers = atoi(value);
150 else if (!strcmp(name, "max-stats")) 152 else if (!strcmp(name, "max-stats"))
151 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); 153 ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
152 else if (!strcmp(name, "cache-size")) 154 else if (!strcmp(name, "cache-size"))
153 ctx.cfg.cache_size = atoi(value); 155 ctx.cfg.cache_size = atoi(value);
154 else if (!strcmp(name, "cache-root")) 156 else if (!strcmp(name, "cache-root"))
155 ctx.cfg.cache_root = xstrdup(expand_macros(value)); 157 ctx.cfg.cache_root = xstrdup(expand_macros(value));
156 else if (!strcmp(name, "cache-root-ttl")) 158 else if (!strcmp(name, "cache-root-ttl"))
157 ctx.cfg.cache_root_ttl = atoi(value); 159 ctx.cfg.cache_root_ttl = atoi(value);
158 else if (!strcmp(name, "cache-repo-ttl")) 160 else if (!strcmp(name, "cache-repo-ttl"))
159 ctx.cfg.cache_repo_ttl = atoi(value); 161 ctx.cfg.cache_repo_ttl = atoi(value);
160 else if (!strcmp(name, "cache-scanrc-ttl")) 162 else if (!strcmp(name, "cache-scanrc-ttl"))
161 ctx.cfg.cache_scanrc_ttl = atoi(value); 163 ctx.cfg.cache_scanrc_ttl = atoi(value);
162 else if (!strcmp(name, "cache-static-ttl")) 164 else if (!strcmp(name, "cache-static-ttl"))
163 ctx.cfg.cache_static_ttl = atoi(value); 165 ctx.cfg.cache_static_ttl = atoi(value);
164 else if (!strcmp(name, "cache-dynamic-ttl")) 166 else if (!strcmp(name, "cache-dynamic-ttl"))
165 ctx.cfg.cache_dynamic_ttl = atoi(value); 167 ctx.cfg.cache_dynamic_ttl = atoi(value);
166 else if (!strcmp(name, "about-filter")) 168 else if (!strcmp(name, "about-filter"))
167 ctx.cfg.about_filter = new_filter(value, 0); 169 ctx.cfg.about_filter = new_filter(value, 0);
168 else if (!strcmp(name, "commit-filter")) 170 else if (!strcmp(name, "commit-filter"))
169 ctx.cfg.commit_filter = new_filter(value, 0); 171 ctx.cfg.commit_filter = new_filter(value, 0);
170 else if (!strcmp(name, "embedded")) 172 else if (!strcmp(name, "embedded"))
171 ctx.cfg.embedded = atoi(value); 173 ctx.cfg.embedded = atoi(value);
172 else if (!strcmp(name, "max-atom-items")) 174 else if (!strcmp(name, "max-atom-items"))
173 ctx.cfg.max_atom_items = atoi(value); 175 ctx.cfg.max_atom_items = atoi(value);
174 else if (!strcmp(name, "max-message-length")) 176 else if (!strcmp(name, "max-message-length"))
175 ctx.cfg.max_msg_len = atoi(value); 177 ctx.cfg.max_msg_len = atoi(value);
176 else if (!strcmp(name, "max-repodesc-length")) 178 else if (!strcmp(name, "max-repodesc-length"))
177 ctx.cfg.max_repodesc_len = atoi(value); 179 ctx.cfg.max_repodesc_len = atoi(value);
178 else if (!strcmp(name, "max-blob-size")) 180 else if (!strcmp(name, "max-blob-size"))
179 ctx.cfg.max_blob_size = atoi(value); 181 ctx.cfg.max_blob_size = atoi(value);
180 else if (!strcmp(name, "max-repo-count")) 182 else if (!strcmp(name, "max-repo-count"))
181 ctx.cfg.max_repo_count = atoi(value); 183 ctx.cfg.max_repo_count = atoi(value);
182 else if (!strcmp(name, "max-commit-count")) 184 else if (!strcmp(name, "max-commit-count"))
183 ctx.cfg.max_commit_count = atoi(value); 185 ctx.cfg.max_commit_count = atoi(value);
184 else if (!strcmp(name, "project-list")) 186 else if (!strcmp(name, "project-list"))
185 ctx.cfg.project_list = xstrdup(expand_macros(value)); 187 ctx.cfg.project_list = xstrdup(expand_macros(value));
186 else if (!strcmp(name, "scan-path")) 188 else if (!strcmp(name, "scan-path"))
187 if (!ctx.cfg.nocache && ctx.cfg.cache_size) 189 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
188 process_cached_repolist(expand_macros(value)); 190 process_cached_repolist(expand_macros(value));
189 else if (ctx.cfg.project_list) 191 else if (ctx.cfg.project_list)
190 scan_projects(expand_macros(value), 192 scan_projects(expand_macros(value),
191 ctx.cfg.project_list, repo_config); 193 ctx.cfg.project_list, repo_config);
192 else 194 else
193 scan_tree(expand_macros(value), repo_config); 195 scan_tree(expand_macros(value), repo_config);
194 else if (!strcmp(name, "source-filter")) 196 else if (!strcmp(name, "source-filter"))
195 ctx.cfg.source_filter = new_filter(value, 1); 197 ctx.cfg.source_filter = new_filter(value, 1);
196 else if (!strcmp(name, "summary-log")) 198 else if (!strcmp(name, "summary-log"))
197 ctx.cfg.summary_log = atoi(value); 199 ctx.cfg.summary_log = atoi(value);
198 else if (!strcmp(name, "summary-branches")) 200 else if (!strcmp(name, "summary-branches"))
199 ctx.cfg.summary_branches = atoi(value); 201 ctx.cfg.summary_branches = atoi(value);
200 else if (!strcmp(name, "summary-tags")) 202 else if (!strcmp(name, "summary-tags"))
201 ctx.cfg.summary_tags = atoi(value); 203 ctx.cfg.summary_tags = atoi(value);
@@ -232,128 +234,129 @@ static void querystring_cb(const char *name, const char *value)
232 } else if (!strcmp(name, "url")) { 234 } else if (!strcmp(name, "url")) {
233 if (*value == '/') 235 if (*value == '/')
234 value++; 236 value++;
235 ctx.qry.url = xstrdup(value); 237 ctx.qry.url = xstrdup(value);
236 cgit_parse_url(value); 238 cgit_parse_url(value);
237 } else if (!strcmp(name, "qt")) { 239 } else if (!strcmp(name, "qt")) {
238 ctx.qry.grep = xstrdup(value); 240 ctx.qry.grep = xstrdup(value);
239 } else if (!strcmp(name, "q")) { 241 } else if (!strcmp(name, "q")) {
240 ctx.qry.search = xstrdup(value); 242 ctx.qry.search = xstrdup(value);
241 } else if (!strcmp(name, "h")) { 243 } else if (!strcmp(name, "h")) {
242 ctx.qry.head = xstrdup(value); 244 ctx.qry.head = xstrdup(value);
243 ctx.qry.has_symref = 1; 245 ctx.qry.has_symref = 1;
244 } else if (!strcmp(name, "id")) { 246 } else if (!strcmp(name, "id")) {
245 ctx.qry.sha1 = xstrdup(value); 247 ctx.qry.sha1 = xstrdup(value);
246 ctx.qry.has_sha1 = 1; 248 ctx.qry.has_sha1 = 1;
247 } else if (!strcmp(name, "id2")) { 249 } else if (!strcmp(name, "id2")) {
248 ctx.qry.sha2 = xstrdup(value); 250 ctx.qry.sha2 = xstrdup(value);
249 ctx.qry.has_sha1 = 1; 251 ctx.qry.has_sha1 = 1;
250 } else if (!strcmp(name, "ofs")) { 252 } else if (!strcmp(name, "ofs")) {
251 ctx.qry.ofs = atoi(value); 253 ctx.qry.ofs = atoi(value);
252 } else if (!strcmp(name, "path")) { 254 } else if (!strcmp(name, "path")) {
253 ctx.qry.path = trim_end(value, '/'); 255 ctx.qry.path = trim_end(value, '/');
254 } else if (!strcmp(name, "name")) { 256 } else if (!strcmp(name, "name")) {
255 ctx.qry.name = xstrdup(value); 257 ctx.qry.name = xstrdup(value);
256 } else if (!strcmp(name, "mimetype")) { 258 } else if (!strcmp(name, "mimetype")) {
257 ctx.qry.mimetype = xstrdup(value); 259 ctx.qry.mimetype = xstrdup(value);
258 } else if (!strcmp(name, "s")){ 260 } else if (!strcmp(name, "s")){
259 ctx.qry.sort = xstrdup(value); 261 ctx.qry.sort = xstrdup(value);
260 } else if (!strcmp(name, "showmsg")) { 262 } else if (!strcmp(name, "showmsg")) {
261 ctx.qry.showmsg = atoi(value); 263 ctx.qry.showmsg = atoi(value);
262 } else if (!strcmp(name, "period")) { 264 } else if (!strcmp(name, "period")) {
263 ctx.qry.period = xstrdup(value); 265 ctx.qry.period = xstrdup(value);
264 } else if (!strcmp(name, "ss")) { 266 } else if (!strcmp(name, "ss")) {
265 ctx.qry.ssdiff = atoi(value); 267 ctx.qry.ssdiff = atoi(value);
266 } else if (!strcmp(name, "all")) { 268 } else if (!strcmp(name, "all")) {
267 ctx.qry.show_all = atoi(value); 269 ctx.qry.show_all = atoi(value);
268 } else if (!strcmp(name, "context")) { 270 } else if (!strcmp(name, "context")) {
269 ctx.qry.context = atoi(value); 271 ctx.qry.context = atoi(value);
270 } else if (!strcmp(name, "ignorews")) { 272 } else if (!strcmp(name, "ignorews")) {
271 ctx.qry.ignorews = atoi(value); 273 ctx.qry.ignorews = atoi(value);
272 } 274 }
273} 275}
274 276
275char *xstrdupn(const char *str) 277char *xstrdupn(const char *str)
276{ 278{
277 return (str ? xstrdup(str) : NULL); 279 return (str ? xstrdup(str) : NULL);
278} 280}
279 281
280static void prepare_context(struct cgit_context *ctx) 282static void prepare_context(struct cgit_context *ctx)
281{ 283{
282 memset(ctx, 0, sizeof(*ctx)); 284 memset(ctx, 0, sizeof(*ctx));
283 ctx->cfg.agefile = "info/web/last-modified"; 285 ctx->cfg.agefile = "info/web/last-modified";
284 ctx->cfg.nocache = 0; 286 ctx->cfg.nocache = 0;
285 ctx->cfg.cache_size = 0; 287 ctx->cfg.cache_size = 0;
286 ctx->cfg.cache_dynamic_ttl = 5; 288 ctx->cfg.cache_dynamic_ttl = 5;
287 ctx->cfg.cache_max_create_time = 5; 289 ctx->cfg.cache_max_create_time = 5;
288 ctx->cfg.cache_repo_ttl = 5; 290 ctx->cfg.cache_repo_ttl = 5;
289 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 291 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
290 ctx->cfg.cache_root_ttl = 5; 292 ctx->cfg.cache_root_ttl = 5;
291 ctx->cfg.cache_scanrc_ttl = 15; 293 ctx->cfg.cache_scanrc_ttl = 15;
292 ctx->cfg.cache_static_ttl = -1; 294 ctx->cfg.cache_static_ttl = -1;
293 ctx->cfg.css = "/cgit.css"; 295 ctx->cfg.css = "/cgit.css";
294 ctx->cfg.logo = "/cgit.png"; 296 ctx->cfg.logo = "/cgit.png";
295 ctx->cfg.local_time = 0; 297 ctx->cfg.local_time = 0;
298 ctx->cfg.enable_gitweb_owner = 1;
296 ctx->cfg.enable_tree_linenumbers = 1; 299 ctx->cfg.enable_tree_linenumbers = 1;
297 ctx->cfg.max_repo_count = 50; 300 ctx->cfg.max_repo_count = 50;
298 ctx->cfg.max_commit_count = 50; 301 ctx->cfg.max_commit_count = 50;
299 ctx->cfg.max_lock_attempts = 5; 302 ctx->cfg.max_lock_attempts = 5;
300 ctx->cfg.max_msg_len = 80; 303 ctx->cfg.max_msg_len = 80;
301 ctx->cfg.max_repodesc_len = 80; 304 ctx->cfg.max_repodesc_len = 80;
302 ctx->cfg.max_blob_size = 0; 305 ctx->cfg.max_blob_size = 0;
303 ctx->cfg.max_stats = 0; 306 ctx->cfg.max_stats = 0;
304 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; 307 ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
305 ctx->cfg.project_list = NULL; 308 ctx->cfg.project_list = NULL;
306 ctx->cfg.renamelimit = -1; 309 ctx->cfg.renamelimit = -1;
307 ctx->cfg.remove_suffix = 0; 310 ctx->cfg.remove_suffix = 0;
308 ctx->cfg.robots = "index, nofollow"; 311 ctx->cfg.robots = "index, nofollow";
309 ctx->cfg.root_title = "Git repository browser"; 312 ctx->cfg.root_title = "Git repository browser";
310 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 313 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
311 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 314 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
312 ctx->cfg.section = ""; 315 ctx->cfg.section = "";
313 ctx->cfg.summary_branches = 10; 316 ctx->cfg.summary_branches = 10;
314 ctx->cfg.summary_log = 10; 317 ctx->cfg.summary_log = 10;
315 ctx->cfg.summary_tags = 10; 318 ctx->cfg.summary_tags = 10;
316 ctx->cfg.max_atom_items = 10; 319 ctx->cfg.max_atom_items = 10;
317 ctx->cfg.ssdiff = 0; 320 ctx->cfg.ssdiff = 0;
318 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); 321 ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
319 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); 322 ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
320 ctx->env.https = xstrdupn(getenv("HTTPS")); 323 ctx->env.https = xstrdupn(getenv("HTTPS"));
321 ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); 324 ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
322 ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); 325 ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
323 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); 326 ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
324 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); 327 ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
325 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); 328 ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
326 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); 329 ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
327 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); 330 ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
328 ctx->page.mimetype = "text/html"; 331 ctx->page.mimetype = "text/html";
329 ctx->page.charset = PAGE_ENCODING; 332 ctx->page.charset = PAGE_ENCODING;
330 ctx->page.filename = NULL; 333 ctx->page.filename = NULL;
331 ctx->page.size = 0; 334 ctx->page.size = 0;
332 ctx->page.modified = time(NULL); 335 ctx->page.modified = time(NULL);
333 ctx->page.expires = ctx->page.modified; 336 ctx->page.expires = ctx->page.modified;
334 ctx->page.etag = NULL; 337 ctx->page.etag = NULL;
335 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); 338 memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
336 if (ctx->env.script_name) 339 if (ctx->env.script_name)
337 ctx->cfg.script_name = ctx->env.script_name; 340 ctx->cfg.script_name = ctx->env.script_name;
338 if (ctx->env.query_string) 341 if (ctx->env.query_string)
339 ctx->qry.raw = ctx->env.query_string; 342 ctx->qry.raw = ctx->env.query_string;
340 if (!ctx->env.cgit_config) 343 if (!ctx->env.cgit_config)
341 ctx->env.cgit_config = CGIT_CONFIG; 344 ctx->env.cgit_config = CGIT_CONFIG;
342} 345}
343 346
344struct refmatch { 347struct refmatch {
345 char *req_ref; 348 char *req_ref;
346 char *first_ref; 349 char *first_ref;
347 int match; 350 int match;
348}; 351};
349 352
350int find_current_ref(const char *refname, const unsigned char *sha1, 353int find_current_ref(const char *refname, const unsigned char *sha1,
351 int flags, void *cb_data) 354 int flags, void *cb_data)
352{ 355{
353 struct refmatch *info; 356 struct refmatch *info;
354 357
355 info = (struct refmatch *)cb_data; 358 info = (struct refmatch *)cb_data;
356 if (!strcmp(refname, info->req_ref)) 359 if (!strcmp(refname, info->req_ref))
357 info->match = 1; 360 info->match = 1;
358 if (!info->first_ref) 361 if (!info->first_ref)
359 info->first_ref = xstrdup(refname); 362 info->first_ref = xstrdup(refname);
diff --git a/cgit.h b/cgit.h
index ada8535..232099d 100644
--- a/cgit.h
+++ b/cgit.h
@@ -122,128 +122,129 @@ struct refinfo {
122struct reflist { 122struct reflist {
123 struct refinfo **refs; 123 struct refinfo **refs;
124 int alloc; 124 int alloc;
125 int count; 125 int count;
126}; 126};
127 127
128struct cgit_query { 128struct cgit_query {
129 int has_symref; 129 int has_symref;
130 int has_sha1; 130 int has_sha1;
131 char *raw; 131 char *raw;
132 char *repo; 132 char *repo;
133 char *page; 133 char *page;
134 char *search; 134 char *search;
135 char *grep; 135 char *grep;
136 char *head; 136 char *head;
137 char *sha1; 137 char *sha1;
138 char *sha2; 138 char *sha2;
139 char *path; 139 char *path;
140 char *name; 140 char *name;
141 char *mimetype; 141 char *mimetype;
142 char *url; 142 char *url;
143 char *period; 143 char *period;
144 int ofs; 144 int ofs;
145 int nohead; 145 int nohead;
146 char *sort; 146 char *sort;
147 int showmsg; 147 int showmsg;
148 int ssdiff; 148 int ssdiff;
149 int show_all; 149 int show_all;
150 int context; 150 int context;
151 int ignorews; 151 int ignorews;
152 char *vpath; 152 char *vpath;
153}; 153};
154 154
155struct cgit_config { 155struct cgit_config {
156 char *agefile; 156 char *agefile;
157 char *cache_root; 157 char *cache_root;
158 char *clone_prefix; 158 char *clone_prefix;
159 char *css; 159 char *css;
160 char *favicon; 160 char *favicon;
161 char *footer; 161 char *footer;
162 char *head_include; 162 char *head_include;
163 char *header; 163 char *header;
164 char *index_header; 164 char *index_header;
165 char *index_info; 165 char *index_info;
166 char *logo; 166 char *logo;
167 char *logo_link; 167 char *logo_link;
168 char *module_link; 168 char *module_link;
169 char *project_list; 169 char *project_list;
170 char *robots; 170 char *robots;
171 char *root_title; 171 char *root_title;
172 char *root_desc; 172 char *root_desc;
173 char *root_readme; 173 char *root_readme;
174 char *script_name; 174 char *script_name;
175 char *section; 175 char *section;
176 char *virtual_root; 176 char *virtual_root;
177 int cache_size; 177 int cache_size;
178 int cache_dynamic_ttl; 178 int cache_dynamic_ttl;
179 int cache_max_create_time; 179 int cache_max_create_time;
180 int cache_repo_ttl; 180 int cache_repo_ttl;
181 int cache_root_ttl; 181 int cache_root_ttl;
182 int cache_scanrc_ttl; 182 int cache_scanrc_ttl;
183 int cache_static_ttl; 183 int cache_static_ttl;
184 int embedded; 184 int embedded;
185 int enable_filter_overrides; 185 int enable_filter_overrides;
186 int enable_gitweb_owner;
186 int enable_index_links; 187 int enable_index_links;
187 int enable_log_filecount; 188 int enable_log_filecount;
188 int enable_log_linecount; 189 int enable_log_linecount;
189 int enable_remote_branches; 190 int enable_remote_branches;
190 int enable_subject_links; 191 int enable_subject_links;
191 int enable_tree_linenumbers; 192 int enable_tree_linenumbers;
192 int local_time; 193 int local_time;
193 int max_atom_items; 194 int max_atom_items;
194 int max_repo_count; 195 int max_repo_count;
195 int max_commit_count; 196 int max_commit_count;
196 int max_lock_attempts; 197 int max_lock_attempts;
197 int max_msg_len; 198 int max_msg_len;
198 int max_repodesc_len; 199 int max_repodesc_len;
199 int max_blob_size; 200 int max_blob_size;
200 int max_stats; 201 int max_stats;
201 int nocache; 202 int nocache;
202 int noplainemail; 203 int noplainemail;
203 int noheader; 204 int noheader;
204 int renamelimit; 205 int renamelimit;
205 int remove_suffix; 206 int remove_suffix;
206 int snapshots; 207 int snapshots;
207 int summary_branches; 208 int summary_branches;
208 int summary_log; 209 int summary_log;
209 int summary_tags; 210 int summary_tags;
210 int ssdiff; 211 int ssdiff;
211 struct string_list mimetypes; 212 struct string_list mimetypes;
212 struct cgit_filter *about_filter; 213 struct cgit_filter *about_filter;
213 struct cgit_filter *commit_filter; 214 struct cgit_filter *commit_filter;
214 struct cgit_filter *source_filter; 215 struct cgit_filter *source_filter;
215}; 216};
216 217
217struct cgit_page { 218struct cgit_page {
218 time_t modified; 219 time_t modified;
219 time_t expires; 220 time_t expires;
220 size_t size; 221 size_t size;
221 char *mimetype; 222 char *mimetype;
222 char *charset; 223 char *charset;
223 char *filename; 224 char *filename;
224 char *etag; 225 char *etag;
225 char *title; 226 char *title;
226 int status; 227 int status;
227 char *statusmsg; 228 char *statusmsg;
228}; 229};
229 230
230struct cgit_environment { 231struct cgit_environment {
231 char *cgit_config; 232 char *cgit_config;
232 char *http_host; 233 char *http_host;
233 char *https; 234 char *https;
234 char *no_http; 235 char *no_http;
235 char *path_info; 236 char *path_info;
236 char *query_string; 237 char *query_string;
237 char *request_method; 238 char *request_method;
238 char *script_name; 239 char *script_name;
239 char *server_name; 240 char *server_name;
240 char *server_port; 241 char *server_port;
241}; 242};
242 243
243struct cgit_context { 244struct cgit_context {
244 struct cgit_environment env; 245 struct cgit_environment env;
245 struct cgit_query qry; 246 struct cgit_query qry;
246 struct cgit_config cfg; 247 struct cgit_config cfg;
247 struct cgit_repo *repo; 248 struct cgit_repo *repo;
248 struct cgit_page page; 249 struct cgit_page page;
249}; 250};
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 6fb1083..5d77973 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -34,128 +34,133 @@ about-filter::
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-gitweb-owner::
99 If set to "1" and scan-path is enabled, we first check each repository
100 for the git config value "gitweb.owner" to determine the owner.
101 Default value: "1". See also: scan-path.
102
98enable-index-links:: 103enable-index-links::
99 Flag which, when set to "1", will make cgit generate extra links for 104 Flag which, when set to "1", will make cgit generate extra links for
100 each repo in the repository index (specifically, to the "summary", 105 each repo in the repository index (specifically, to the "summary",
101 "commit" and "tree" pages). Default value: "0". 106 "commit" and "tree" pages). Default value: "0".
102 107
103enable-log-filecount:: 108enable-log-filecount::
104 Flag which, when set to "1", will make cgit print the number of 109 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 110 modified files for each commit on the repository log page. Default
106 value: "0". 111 value: "0".
107 112
108enable-log-linecount:: 113enable-log-linecount::
109 Flag which, when set to "1", will make cgit print the number of added 114 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 115 and removed lines for each commit on the repository log page. Default
111 value: "0". 116 value: "0".
112 117
113enable-remote-branches:: 118enable-remote-branches::
114 Flag which, when set to "1", will make cgit display remote branches 119 Flag which, when set to "1", will make cgit display remote branches
115 in the summary and refs views. Default value: "0". See also: 120 in the summary and refs views. Default value: "0". See also:
116 "repo.enable-remote-branches". 121 "repo.enable-remote-branches".
117 122
118enable-subject-links:: 123enable-subject-links::
119 Flag which, when set to "1", will make cgit use the subject of the 124 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 125 parent commit as link text when generating links to parent commits
121 in commit view. Default value: "0". See also: 126 in commit view. Default value: "0". See also:
122 "repo.enable-subject-links". 127 "repo.enable-subject-links".
123 128
124enable-tree-linenumbers:: 129enable-tree-linenumbers::
125 Flag which, when set to "1", will make cgit generate linenumber links 130 Flag which, when set to "1", will make cgit generate linenumber links
126 for plaintext blobs printed in the tree view. Default value: "1". 131 for plaintext blobs printed in the tree view. Default value: "1".
127 132
128favicon:: 133favicon::
129 Url used as link to a shortcut icon for cgit. If specified, it is 134 Url used as link to a shortcut icon for cgit. If specified, it is
130 suggested to use the value "/favicon.ico" since certain browsers will 135 suggested to use the value "/favicon.ico" since certain browsers will
131 ignore other values. Default value: none. 136 ignore other values. Default value: none.
132 137
133footer:: 138footer::
134 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
135 verbatim at the bottom of all pages (i.e. it replaces the standard 140 verbatim at the bottom of all pages (i.e. it replaces the standard
136 "generated by..." message. Default value: none. 141 "generated by..." message. Default value: none.
137 142
138head-include:: 143head-include::
139 The content of the file specified with this option will be included 144 The content of the file specified with this option will be included
140 verbatim in the html HEAD section on all pages. Default value: none. 145 verbatim in the html HEAD section on all pages. Default value: none.
141 146
142header:: 147header::
143 The content of the file specified with this option will be included 148 The content of the file specified with this option will be included
144 verbatim at the top of all pages. Default value: none. 149 verbatim at the top of all pages. Default value: none.
145 150
146include:: 151include::
147 Name of a configfile to include before the rest of the current config- 152 Name of a configfile to include before the rest of the current config-
148 file is parsed. Default value: none. 153 file is parsed. Default value: none.
149 154
150index-header:: 155index-header::
151 The content of the file specified with this option will be included 156 The content of the file specified with this option will be included
152 verbatim above the repository index. This setting is deprecated, and 157 verbatim above the repository index. This setting is deprecated, and
153 will not be supported by cgit-1.0 (use root-readme instead). Default 158 will not be supported by cgit-1.0 (use root-readme instead). Default
154 value: none. 159 value: none.
155 160
156index-info:: 161index-info::
157 The content of the file specified with this option will be included 162 The content of the file specified with this option will be included
158 verbatim below the heading on the repository index page. This setting 163 verbatim below the heading on the repository index page. This setting
159 is deprecated, and will not be supported by cgit-1.0 (use root-desc 164 is deprecated, and will not be supported by cgit-1.0 (use root-desc
160 instead). Default value: none. 165 instead). Default value: none.
161 166
diff --git a/scan-tree.c b/scan-tree.c
index a83a78c..e987824 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -1,156 +1,171 @@
1/* scan-tree.c 1/* scan-tree.c
2 * 2 *
3 * Copyright (C) 2008-2009 Lars Hjemli 3 * Copyright (C) 2008-2009 Lars Hjemli
4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com> 4 * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com>
5 * 5 *
6 * Licensed under GNU General Public License v2 6 * Licensed under GNU General Public License v2
7 * (see COPYING for full license text) 7 * (see COPYING for full license text)
8 */ 8 */
9 9
10#include "cgit.h" 10#include "cgit.h"
11#include "configfile.h" 11#include "configfile.h"
12#include "html.h" 12#include "html.h"
13 13
14#define MAX_PATH 4096 14#define MAX_PATH 4096
15 15
16/* return 1 if path contains a objects/ directory and a HEAD file */ 16/* return 1 if path contains a objects/ directory and a HEAD file */
17static int is_git_dir(const char *path) 17static int is_git_dir(const char *path)
18{ 18{
19 struct stat st; 19 struct stat st;
20 static char buf[MAX_PATH]; 20 static char buf[MAX_PATH];
21 21
22 if (snprintf(buf, MAX_PATH, "%s/objects", path) >= MAX_PATH) { 22 if (snprintf(buf, MAX_PATH, "%s/objects", path) >= MAX_PATH) {
23 fprintf(stderr, "Insanely long path: %s\n", path); 23 fprintf(stderr, "Insanely long path: %s\n", path);
24 return 0; 24 return 0;
25 } 25 }
26 if (stat(buf, &st)) { 26 if (stat(buf, &st)) {
27 if (errno != ENOENT) 27 if (errno != ENOENT)
28 fprintf(stderr, "Error checking path %s: %s (%d)\n", 28 fprintf(stderr, "Error checking path %s: %s (%d)\n",
29 path, strerror(errno), errno); 29 path, strerror(errno), errno);
30 return 0; 30 return 0;
31 } 31 }
32 if (!S_ISDIR(st.st_mode)) 32 if (!S_ISDIR(st.st_mode))
33 return 0; 33 return 0;
34 34
35 sprintf(buf, "%s/HEAD", path); 35 sprintf(buf, "%s/HEAD", path);
36 if (stat(buf, &st)) { 36 if (stat(buf, &st)) {
37 if (errno != ENOENT) 37 if (errno != ENOENT)
38 fprintf(stderr, "Error checking path %s: %s (%d)\n", 38 fprintf(stderr, "Error checking path %s: %s (%d)\n",
39 path, strerror(errno), errno); 39 path, strerror(errno), errno);
40 return 0; 40 return 0;
41 } 41 }
42 if (!S_ISREG(st.st_mode)) 42 if (!S_ISREG(st.st_mode))
43 return 0; 43 return 0;
44 44
45 return 1; 45 return 1;
46} 46}
47 47
48struct cgit_repo *repo; 48struct cgit_repo *repo;
49repo_config_fn config_fn; 49repo_config_fn config_fn;
50char *owner;
50 51
51static void repo_config(const char *name, const char *value) 52static void repo_config(const char *name, const char *value)
52{ 53{
53 config_fn(repo, name, value); 54 config_fn(repo, name, value);
54} 55}
55 56
57static int git_owner_config(const char *key, const char *value, void *cb)
58{
59 if (!strcmp(key, "gitweb.owner"))
60 owner = xstrdup(value);
61 return 0;
62}
63
56static void add_repo(const char *base, const char *path, repo_config_fn fn) 64static void add_repo(const char *base, const char *path, repo_config_fn fn)
57{ 65{
58 struct stat st; 66 struct stat st;
59 struct passwd *pwd; 67 struct passwd *pwd;
60 char *p; 68 char *p;
61 size_t size; 69 size_t size;
62 70
63 if (stat(path, &st)) { 71 if (stat(path, &st)) {
64 fprintf(stderr, "Error accessing %s: %s (%d)\n", 72 fprintf(stderr, "Error accessing %s: %s (%d)\n",
65 path, strerror(errno), errno); 73 path, strerror(errno), errno);
66 return; 74 return;
67 } 75 }
68 if (!stat(fmt("%s/noweb", path), &st)) 76 if (!stat(fmt("%s/noweb", path), &st))
69 return; 77 return;
70 if ((pwd = getpwuid(st.st_uid)) == NULL) { 78
71 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", 79 owner = NULL;
72 path, strerror(errno), errno); 80 if (ctx.cfg.enable_gitweb_owner)
73 return; 81 git_config_from_file(git_owner_config, fmt("%s/config", path), NULL);
74 }
75 if (base == path) 82 if (base == path)
76 p = fmt("%s", path); 83 p = fmt("%s", path);
77 else 84 else
78 p = fmt("%s", path + strlen(base) + 1); 85 p = fmt("%s", path + strlen(base) + 1);
79 86
80 if (!strcmp(p + strlen(p) - 5, "/.git")) 87 if (!strcmp(p + strlen(p) - 5, "/.git"))
81 p[strlen(p) - 5] = '\0'; 88 p[strlen(p) - 5] = '\0';
82 89
83 repo = cgit_add_repo(xstrdup(p)); 90 repo = cgit_add_repo(xstrdup(p));
84 if (ctx.cfg.remove_suffix) 91 if (ctx.cfg.remove_suffix)
85 if ((p = strrchr(repo->url, '.')) && !strcmp(p, ".git")) 92 if ((p = strrchr(repo->url, '.')) && !strcmp(p, ".git"))
86 *p = '\0'; 93 *p = '\0';
87 repo->name = repo->url; 94 repo->name = repo->url;
88 repo->path = xstrdup(path); 95 repo->path = xstrdup(path);
89 p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL; 96 while (!owner) {
90 if (p) 97 if ((pwd = getpwuid(st.st_uid)) == NULL) {
91 *p = '\0'; 98 fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
92 repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : ""); 99 path, strerror(errno), errno);
100 break;
101 }
102 if (pwd->pw_gecos)
103 if ((p = strchr(pwd->pw_gecos, ',')))
104 *p = '\0';
105 owner = xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name);
106 }
107 repo->owner = owner;
93 108
94 p = fmt("%s/description", path); 109 p = fmt("%s/description", path);
95 if (!stat(p, &st)) 110 if (!stat(p, &st))
96 readfile(p, &repo->desc, &size); 111 readfile(p, &repo->desc, &size);
97 112
98 p = fmt("%s/README.html", path); 113 p = fmt("%s/README.html", path);
99 if (!stat(p, &st)) 114 if (!stat(p, &st))
100 repo->readme = "README.html"; 115 repo->readme = "README.html";
101 116
102 p = fmt("%s/cgitrc", path); 117 p = fmt("%s/cgitrc", path);
103 if (!stat(p, &st)) { 118 if (!stat(p, &st)) {
104 config_fn = fn; 119 config_fn = fn;
105 parse_configfile(xstrdup(p), &repo_config); 120 parse_configfile(xstrdup(p), &repo_config);
106 } 121 }
107} 122}
108 123
109static void scan_path(const char *base, const char *path, repo_config_fn fn) 124static void scan_path(const char *base, const char *path, repo_config_fn fn)
110{ 125{
111 DIR *dir; 126 DIR *dir;
112 struct dirent *ent; 127 struct dirent *ent;
113 char *buf; 128 char *buf;
114 struct stat st; 129 struct stat st;
115 130
116 if (is_git_dir(path)) { 131 if (is_git_dir(path)) {
117 add_repo(base, path, fn); 132 add_repo(base, path, fn);
118 return; 133 return;
119 } 134 }
120 if (is_git_dir(fmt("%s/.git", path))) { 135 if (is_git_dir(fmt("%s/.git", path))) {
121 add_repo(base, fmt("%s/.git", path), fn); 136 add_repo(base, fmt("%s/.git", path), fn);
122 return; 137 return;
123 } 138 }
124 dir = opendir(path); 139 dir = opendir(path);
125 if (!dir) { 140 if (!dir) {
126 fprintf(stderr, "Error opening directory %s: %s (%d)\n", 141 fprintf(stderr, "Error opening directory %s: %s (%d)\n",
127 path, strerror(errno), errno); 142 path, strerror(errno), errno);
128 return; 143 return;
129 } 144 }
130 while((ent = readdir(dir)) != NULL) { 145 while((ent = readdir(dir)) != NULL) {
131 if (ent->d_name[0] == '.') { 146 if (ent->d_name[0] == '.') {
132 if (ent->d_name[1] == '\0') 147 if (ent->d_name[1] == '\0')
133 continue; 148 continue;
134 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0') 149 if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
135 continue; 150 continue;
136 } 151 }
137 buf = malloc(strlen(path) + strlen(ent->d_name) + 2); 152 buf = malloc(strlen(path) + strlen(ent->d_name) + 2);
138 if (!buf) { 153 if (!buf) {
139 fprintf(stderr, "Alloc error on %s: %s (%d)\n", 154 fprintf(stderr, "Alloc error on %s: %s (%d)\n",
140 path, strerror(errno), errno); 155 path, strerror(errno), errno);
141 exit(1); 156 exit(1);
142 } 157 }
143 sprintf(buf, "%s/%s", path, ent->d_name); 158 sprintf(buf, "%s/%s", path, ent->d_name);
144 if (stat(buf, &st)) { 159 if (stat(buf, &st)) {
145 fprintf(stderr, "Error checking path %s: %s (%d)\n", 160 fprintf(stderr, "Error checking path %s: %s (%d)\n",
146 buf, strerror(errno), errno); 161 buf, strerror(errno), errno);
147 free(buf); 162 free(buf);
148 continue; 163 continue;
149 } 164 }
150 if (S_ISDIR(st.st_mode)) 165 if (S_ISDIR(st.st_mode))
151 scan_path(base, buf, fn); 166 scan_path(base, buf, fn);
152 free(buf); 167 free(buf);
153 } 168 }
154 closedir(dir); 169 closedir(dir);
155} 170}
156 171