summaryrefslogtreecommitdiffabout
authorLars Hjemli <hjemli@gmail.com>2009-08-23 22:04:58 (UTC)
committer Lars Hjemli <hjemli@gmail.com>2009-08-24 08:22:58 (UTC)
commit74061ed5f03e72796450aa3b8ca1cf6ced5d59e2 (patch) (unidiff)
tree235ab2c29c027f19b00da815af3bf1d2856edc21
parenta1b3938f711c9b0e5eedad1678535e5779da82c1 (diff)
downloadcgit-74061ed5f03e72796450aa3b8ca1cf6ced5d59e2.zip
cgit-74061ed5f03e72796450aa3b8ca1cf6ced5d59e2.tar.gz
cgit-74061ed5f03e72796450aa3b8ca1cf6ced5d59e2.tar.bz2
Add support for repo-local cgitrc file
When recursively scanning a directory tree looking for git repositories, cgit will now parse cgitrc files found within such repositories. The repo-specific config files can include any repo-specific options except 'repo.url' and 'repo.path'. Also, in such config files the 'repo.' prefix can not be used, i.e. the valid options then becomes: * name * clone-url * desc * ower * defbranch * snapshots * enable-log-filecount * enable-log-linecount * max-stats * module-link * section * about-filter * commit-filter * source-filter * readme Signed-off-by: Lars Hjemli <hjemli@gmail.com>
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
@@ -123,97 +123,97 @@ void config_cb(const char *name, const char *value)
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")) {
@@ -431,180 +431,180 @@ int 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;
diff --git a/cgit.h b/cgit.h
index fc7c7d5..3359be9 100644
--- a/cgit.h
+++ b/cgit.h
@@ -34,96 +34,99 @@
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;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index e99c9f7..df494aa 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -281,96 +281,105 @@ repo.defbranch::
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
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);