summaryrefslogtreecommitdiffabout
Unidiff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--cache.h2
-rw-r--r--cgit.c267
-rw-r--r--cgit.css2
-rw-r--r--cgit.h9
-rw-r--r--cgitrc.5.txt48
-rw-r--r--scan-tree.c32
-rw-r--r--scan-tree.h2
-rw-r--r--shared.c3
-rw-r--r--ui-repolist.c37
-rw-r--r--ui-stats.c8
-rw-r--r--ui-stats.h1
11 files changed, 330 insertions, 81 deletions
diff --git a/cache.h b/cache.h
index 66cc41f..ac9276b 100644
--- a/cache.h
+++ b/cache.h
@@ -33,3 +33,5 @@ extern int cache_ls(const char *path);
33extern void cache_log(const char *format, ...); 33extern void cache_log(const char *format, ...);
34 34
35extern unsigned long hash_str(const char *str);
36
35#endif /* CGIT_CACHE_H */ 37#endif /* CGIT_CACHE_H */
diff --git a/cgit.c b/cgit.c
index ec40e1f..bd37788 100644
--- a/cgit.c
+++ b/cgit.c
@@ -41,7 +41,56 @@ struct cgit_filter *new_filter(const char *cmd, int extra_args)
41} 41}
42 42
43static void process_cached_repolist(const char *path);
44
45void repo_config(struct cgit_repo *repo, const char *name, const char *value)
46{
47 if (!strcmp(name, "name"))
48 repo->name = xstrdup(value);
49 else if (!strcmp(name, "clone-url"))
50 repo->clone_url = xstrdup(value);
51 else if (!strcmp(name, "desc"))
52 repo->desc = xstrdup(value);
53 else if (!strcmp(name, "owner"))
54 repo->owner = xstrdup(value);
55 else if (!strcmp(name, "defbranch"))
56 repo->defbranch = xstrdup(value);
57 else if (!strcmp(name, "snapshots"))
58 repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
59 else if (!strcmp(name, "enable-log-filecount"))
60 repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
61 else if (!strcmp(name, "enable-log-linecount"))
62 repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
63 else if (!strcmp(name, "max-stats"))
64 repo->max_stats = cgit_find_stats_period(value, NULL);
65 else if (!strcmp(name, "module-link"))
66 repo->module_link= xstrdup(value);
67 else if (!strcmp(name, "section"))
68 repo->section = xstrdup(value);
69 else if (!strcmp(name, "readme") && value != NULL) {
70 if (*value == '/')
71 ctx.repo->readme = xstrdup(value);
72 else
73 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
74 } else if (ctx.cfg.enable_filter_overrides) {
75 if (!strcmp(name, "about-filter"))
76 repo->about_filter = new_filter(value, 0);
77 else if (!strcmp(name, "commit-filter"))
78 repo->commit_filter = new_filter(value, 0);
79 else if (!strcmp(name, "source-filter"))
80 repo->source_filter = new_filter(value, 1);
81 }
82}
83
43void config_cb(const char *name, const char *value) 84void config_cb(const char *name, const char *value)
44{ 85{
45 if (!strcmp(name, "root-title")) 86 if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
87 ctx.cfg.section = xstrdup(value);
88 else if (!strcmp(name, "repo.url"))
89 ctx.repo = cgit_add_repo(value);
90 else if (ctx.repo && !strcmp(name, "repo.path"))
91 ctx.repo->path = trim_end(value, '/');
92 else if (ctx.repo && !prefixcmp(name, "repo."))
93 repo_config(ctx.repo, name + 5, value);
94 else if (!strcmp(name, "root-title"))
46 ctx.cfg.root_title = xstrdup(value); 95 ctx.cfg.root_title = xstrdup(value);
47 else if (!strcmp(name, "root-desc")) 96 else if (!strcmp(name, "root-desc"))
@@ -81,4 +130,6 @@ void config_cb(const char *name, const char *value)
81 else if (!strcmp(name, "snapshots")) 130 else if (!strcmp(name, "snapshots"))
82 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); 131 ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
132 else if (!strcmp(name, "enable-filter-overrides"))
133 ctx.cfg.enable_filter_overrides = atoi(value);
83 else if (!strcmp(name, "enable-index-links")) 134 else if (!strcmp(name, "enable-index-links"))
84 ctx.cfg.enable_index_links = atoi(value); 135 ctx.cfg.enable_index_links = atoi(value);
@@ -99,4 +150,6 @@ void config_cb(const char *name, const char *value)
99 else if (!strcmp(name, "cache-repo-ttl")) 150 else if (!strcmp(name, "cache-repo-ttl"))
100 ctx.cfg.cache_repo_ttl = atoi(value); 151 ctx.cfg.cache_repo_ttl = atoi(value);
152 else if (!strcmp(name, "cache-scanrc-ttl"))
153 ctx.cfg.cache_scanrc_ttl = atoi(value);
101 else if (!strcmp(name, "cache-static-ttl")) 154 else if (!strcmp(name, "cache-static-ttl"))
102 ctx.cfg.cache_static_ttl = atoi(value); 155 ctx.cfg.cache_static_ttl = atoi(value);
@@ -117,4 +170,9 @@ void config_cb(const char *name, const char *value)
117 else if (!strcmp(name, "max-commit-count")) 170 else if (!strcmp(name, "max-commit-count"))
118 ctx.cfg.max_commit_count = atoi(value); 171 ctx.cfg.max_commit_count = atoi(value);
172 else if (!strcmp(name, "scan-path"))
173 if (!ctx.cfg.nocache && ctx.cfg.cache_size)
174 process_cached_repolist(value);
175 else
176 scan_tree(value, repo_config);
119 else if (!strcmp(name, "source-filter")) 177 else if (!strcmp(name, "source-filter"))
120 ctx.cfg.source_filter = new_filter(value, 1); 178 ctx.cfg.source_filter = new_filter(value, 1);
@@ -137,42 +195,5 @@ void config_cb(const char *name, const char *value)
137 else if (!prefixcmp(name, "mimetype.")) 195 else if (!prefixcmp(name, "mimetype."))
138 add_mimetype(name + 9, value); 196 add_mimetype(name + 9, value);
139 else if (!strcmp(name, "repo.group")) 197 else if (!strcmp(name, "include"))
140 ctx.cfg.repo_group = xstrdup(value);
141 else if (!strcmp(name, "repo.url"))
142 ctx.repo = cgit_add_repo(value);
143 else if (!strcmp(name, "repo.name"))
144 ctx.repo->name = xstrdup(value);
145 else if (ctx.repo && !strcmp(name, "repo.path"))
146 ctx.repo->path = trim_end(value, '/');
147 else if (ctx.repo && !strcmp(name, "repo.clone-url"))
148 ctx.repo->clone_url = xstrdup(value);
149 else if (ctx.repo && !strcmp(name, "repo.desc"))
150 ctx.repo->desc = xstrdup(value);
151 else if (ctx.repo && !strcmp(name, "repo.owner"))
152 ctx.repo->owner = xstrdup(value);
153 else if (ctx.repo && !strcmp(name, "repo.defbranch"))
154 ctx.repo->defbranch = xstrdup(value);
155 else if (ctx.repo && !strcmp(name, "repo.snapshots"))
156 ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
157 else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
158 ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
159 else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
160 ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
161 else if (ctx.repo && !strcmp(name, "repo.max-stats"))
162 ctx.repo->max_stats = cgit_find_stats_period(value, NULL);
163 else if (ctx.repo && !strcmp(name, "repo.module-link"))
164 ctx.repo->module_link= xstrdup(value);
165 else if (ctx.repo && !strcmp(name, "repo.about-filter"))
166 ctx.repo->about_filter = new_filter(value, 0);
167 else if (ctx.repo && !strcmp(name, "repo.commit-filter"))
168 ctx.repo->commit_filter = new_filter(value, 0);
169 else if (ctx.repo && !strcmp(name, "repo.source-filter"))
170 ctx.repo->source_filter = new_filter(value, 1);
171 else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
172 if (*value == '/')
173 ctx.repo->readme = xstrdup(value);
174 else
175 ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
176 } else if (!strcmp(name, "include"))
177 parse_configfile(value, config_cb); 198 parse_configfile(value, config_cb);
178} 199}
@@ -237,4 +258,5 @@ static void prepare_context(struct cgit_context *ctx)
237 ctx->cfg.cache_root = CGIT_CACHE_ROOT; 258 ctx->cfg.cache_root = CGIT_CACHE_ROOT;
238 ctx->cfg.cache_root_ttl = 5; 259 ctx->cfg.cache_root_ttl = 5;
260 ctx->cfg.cache_scanrc_ttl = 15;
239 ctx->cfg.cache_static_ttl = -1; 261 ctx->cfg.cache_static_ttl = -1;
240 ctx->cfg.css = "/cgit.css"; 262 ctx->cfg.css = "/cgit.css";
@@ -254,4 +276,5 @@ static void prepare_context(struct cgit_context *ctx)
254 ctx->cfg.root_desc = "a fast webinterface for the git dscm"; 276 ctx->cfg.root_desc = "a fast webinterface for the git dscm";
255 ctx->cfg.script_name = CGIT_SCRIPT_NAME; 277 ctx->cfg.script_name = CGIT_SCRIPT_NAME;
278 ctx->cfg.section = "";
256 ctx->cfg.summary_branches = 10; 279 ctx->cfg.summary_branches = 10;
257 ctx->cfg.summary_log = 10; 280 ctx->cfg.summary_log = 10;
@@ -418,26 +441,149 @@ int cmp_repos(const void *a, const void *b)
418} 441}
419 442
420void print_repo(struct cgit_repo *repo) 443char *build_snapshot_setting(int bitmap)
421{ 444{
422 printf("repo.url=%s\n", repo->url); 445 const struct cgit_snapshot_format *f;
423 printf("repo.name=%s\n", repo->name); 446 char *result = xstrdup("");
424 printf("repo.path=%s\n", repo->path); 447 char *tmp;
448 int len;
449
450 for (f = cgit_snapshot_formats; f->suffix; f++) {
451 if (f->bit & bitmap) {
452 tmp = result;
453 result = xstrdup(fmt("%s%s ", tmp, f->suffix));
454 free(tmp);
455 }
456 }
457 len = strlen(result);
458 if (len)
459 result[len - 1] = '\0';
460 return result;
461}
462
463char *get_first_line(char *txt)
464{
465 char *t = xstrdup(txt);
466 char *p = strchr(t, '\n');
467 if (p)
468 *p = '\0';
469 return t;
470}
471
472void print_repo(FILE *f, struct cgit_repo *repo)
473{
474 fprintf(f, "repo.url=%s\n", repo->url);
475 fprintf(f, "repo.name=%s\n", repo->name);
476 fprintf(f, "repo.path=%s\n", repo->path);
425 if (repo->owner) 477 if (repo->owner)
426 printf("repo.owner=%s\n", repo->owner); 478 fprintf(f, "repo.owner=%s\n", repo->owner);
427 if (repo->desc) 479 if (repo->desc) {
428 printf("repo.desc=%s\n", repo->desc); 480 char *tmp = get_first_line(repo->desc);
481 fprintf(f, "repo.desc=%s\n", tmp);
482 free(tmp);
483 }
429 if (repo->readme) 484 if (repo->readme)
430 printf("repo.readme=%s\n", repo->readme); 485 fprintf(f, "repo.readme=%s\n", repo->readme);
431 printf("\n"); 486 if (repo->defbranch)
487 fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
488 if (repo->module_link)
489 fprintf(f, "repo.module-link=%s\n", repo->module_link);
490 if (repo->section)
491 fprintf(f, "repo.section=%s\n", repo->section);
492 if (repo->clone_url)
493 fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
494 fprintf(f, "repo.enable-log-filecount=%d\n",
495 repo->enable_log_filecount);
496 fprintf(f, "repo.enable-log-linecount=%d\n",
497 repo->enable_log_linecount);
498 if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
499 fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
500 if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
501 fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
502 if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
503 fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
504 if (repo->snapshots != ctx.cfg.snapshots) {
505 char *tmp = build_snapshot_setting(repo->snapshots);
506 fprintf(f, "repo.snapshots=%s\n", tmp);
507 free(tmp);
508 }
509 if (repo->max_stats != ctx.cfg.max_stats)
510 fprintf(f, "repo.max-stats=%s\n",
511 cgit_find_stats_periodname(repo->max_stats));
512 fprintf(f, "\n");
513}
514
515void print_repolist(FILE *f, struct cgit_repolist *list, int start)
516{
517 int i;
518
519 for(i = start; i < list->count; i++)
520 print_repo(f, &list->repos[i]);
432} 521}
433 522
434void print_repolist(struct cgit_repolist *list) 523/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
524 * and return 0 on success.
525 */
526static int generate_cached_repolist(const char *path, const char *cached_rc)
435{ 527{
436 int i; 528 char *locked_rc;
529 int idx;
530 FILE *f;
531
532 locked_rc = xstrdup(fmt("%s.lock", cached_rc));
533 f = fopen(locked_rc, "wx");
534 if (!f) {
535 /* Inform about the error unless the lockfile already existed,
536 * since that only means we've got concurrent requests.
537 */
538 if (errno != EEXIST)
539 fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
540 locked_rc, strerror(errno), errno);
541 return errno;
542 }
543 idx = cgit_repolist.count;
544 scan_tree(path, repo_config);
545 print_repolist(f, &cgit_repolist, idx);
546 if (rename(locked_rc, cached_rc))
547 fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
548 locked_rc, cached_rc, strerror(errno), errno);
549 fclose(f);
550 return 0;
551}
552
553static void process_cached_repolist(const char *path)
554{
555 struct stat st;
556 char *cached_rc;
557 time_t age;
437 558
438 for(i = 0; i < list->count; i++) 559 cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root,
439 print_repo(&list->repos[i]); 560 hash_str(path)));
561
562 if (stat(cached_rc, &st)) {
563 /* Nothing is cached, we need to scan without forking. And
564 * if we fail to generate a cached repolist, we need to
565 * invoke scan_tree manually.
566 */
567 if (generate_cached_repolist(path, cached_rc))
568 scan_tree(path, repo_config);
569 return;
440} 570}
441 571
572 parse_configfile(cached_rc, config_cb);
573
574 /* If the cached configfile hasn't expired, lets exit now */
575 age = time(NULL) - st.st_mtime;
576 if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
577 return;
578
579 /* The cached repolist has been parsed, but it was old. So lets
580 * rescan the specified path and generate a new cached repolist
581 * in a child-process to avoid latency for the current request.
582 */
583 if (fork())
584 return;
585
586 exit(generate_cached_repolist(path, cached_rc));
587}
442 588
443static void cgit_parse_args(int argc, const char **argv) 589static void cgit_parse_args(int argc, const char **argv)
@@ -476,7 +622,18 @@ static void cgit_parse_args(int argc, const char **argv)
476 ctx.qry.ofs = atoi(argv[i]+6); 622 ctx.qry.ofs = atoi(argv[i]+6);
477 } 623 }
478 if (!strncmp(argv[i], "--scan-tree=", 12)) { 624 if (!strncmp(argv[i], "--scan-tree=", 12) ||
625 !strncmp(argv[i], "--scan-path=", 12)) {
626 /* HACK: the global snapshot bitmask defines the
627 * set of allowed snapshot formats, but the config
628 * file hasn't been parsed yet so the mask is
629 * currently 0. By setting all bits high before
630 * scanning we make sure that any in-repo cgitrc
631 * snapshot setting is respected by scan_tree().
632 * BTW: we assume that there'll never be more than
633 * 255 different snapshot formats supported by cgit...
634 */
635 ctx.cfg.snapshots = 0xFF;
479 scan++; 636 scan++;
480 scan_tree(argv[i] + 12); 637 scan_tree(argv[i] + 12, repo_config);
481 } 638 }
482 } 639 }
@@ -484,5 +641,5 @@ static void cgit_parse_args(int argc, const char **argv)
484 qsort(cgit_repolist.repos, cgit_repolist.count, 641 qsort(cgit_repolist.repos, cgit_repolist.count,
485 sizeof(struct cgit_repo), cmp_repos); 642 sizeof(struct cgit_repo), cmp_repos);
486 print_repolist(&cgit_repolist); 643 print_repolist(stdout, &cgit_repolist, 0);
487 exit(0); 644 exit(0);
488 } 645 }
diff --git a/cgit.css b/cgit.css
index ebf3322..c47ebc9 100644
--- a/cgit.css
+++ b/cgit.css
@@ -430,5 +430,5 @@ table.diff td div.del {
430} 430}
431 431
432table.list td.repogroup { 432table.list td.reposection {
433 font-style: italic; 433 font-style: italic;
434 color: #888; 434 color: #888;
diff --git a/cgit.h b/cgit.h
index a20679a..6c6c460 100644
--- a/cgit.h
+++ b/cgit.h
@@ -66,7 +66,7 @@ struct cgit_repo {
66 char *owner; 66 char *owner;
67 char *defbranch; 67 char *defbranch;
68 char *group;
69 char *module_link; 68 char *module_link;
70 char *readme; 69 char *readme;
70 char *section;
71 char *clone_url; 71 char *clone_url;
72 int snapshots; 72 int snapshots;
@@ -80,4 +80,7 @@ struct cgit_repo {
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;
@@ -157,5 +160,4 @@ struct cgit_config {
157 char *logo_link; 160 char *logo_link;
158 char *module_link; 161 char *module_link;
159 char *repo_group;
160 char *robots; 162 char *robots;
161 char *root_title; 163 char *root_title;
@@ -163,4 +165,5 @@ struct cgit_config {
163 char *root_readme; 165 char *root_readme;
164 char *script_name; 166 char *script_name;
167 char *section;
165 char *virtual_root; 168 char *virtual_root;
166 int cache_size; 169 int cache_size;
@@ -169,6 +172,8 @@ struct cgit_config {
169 int cache_repo_ttl; 172 int cache_repo_ttl;
170 int cache_root_ttl; 173 int cache_root_ttl;
174 int cache_scanrc_ttl;
171 int cache_static_ttl; 175 int cache_static_ttl;
172 int embedded; 176 int embedded;
177 int enable_filter_overrides;
173 int enable_index_links; 178 int enable_index_links;
174 int enable_log_filecount; 179 int enable_log_filecount;
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 3b16db9..4dc383d 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -55,4 +55,8 @@ cache-root-ttl::
55 version of the repository index page. Default value: "5". 55 version of the repository index page. Default value: "5".
56 56
57cache-scanrc-ttl::
58 Number which specifies the time-to-live, in minutes, for the result
59 of scanning a path for git repositories. Default value: "15".
60
57cache-size:: 61cache-size::
58 The maximum number of entries in the cgit cache. Default value: "0" 62 The maximum number of entries in the cgit cache. Default value: "0"
@@ -85,4 +89,8 @@ embedded::
85 also: "noheader". 89 also: "noheader".
86 90
91enable-filter-overrides::
92 Flag which, when set to "1", allows all filter settings to be
93 overridden in repository-specific cgitrc files. Default value: none.
94
87enable-index-links:: 95enable-index-links::
88 Flag which, when set to "1", will make cgit generate extra links for 96 Flag which, when set to "1", will make cgit generate extra links for
@@ -201,6 +209,6 @@ renamelimit::
201 209
202repo.group:: 210repo.group::
203 A value for the current repository group, which all repositories 211 Legacy alias for "section". This option is deprecated and will not be
204 specified after this setting will inherit. Default value: none. 212 supported in cgit-1.0.
205 213
206robots:: 214robots::
@@ -221,4 +229,14 @@ root-title::
221 "Git Repository Browser". 229 "Git Repository Browser".
222 230
231scan-path::
232 A path which will be scanned for repositories. If caching is enabled,
233 the result will be cached as a cgitrc include-file in the cache
234 directory. Default value: none. See also: cache-scanrc-ttl.
235
236section::
237 The name of the current repository section - all repositories defined
238 after this option will inherit the current section name. Default value:
239 none.
240
223snapshots:: 241snapshots::
224 Text which specifies the default set of snapshot formats generated by 242 Text which specifies the default set of snapshot formats generated by
@@ -257,5 +275,6 @@ REPOSITORY SETTINGS
257------------------- 275-------------------
258repo.about-filter:: 276repo.about-filter::
259 Override the default about-filter. Default value: <about-filter>. 277 Override the default about-filter. Default value: none. See also:
278 "enable-filter-overrides".
260 279
261repo.clone-url:: 280repo.clone-url::
@@ -264,5 +283,6 @@ repo.clone-url::
264 283
265repo.commit-filter:: 284repo.commit-filter::
266 Override the default commit-filter. Default value: <commit-filter>. 285 Override the default commit-filter. Default value: none. See also:
286 "enable-filter-overrides".
267 287
268repo.defbranch:: 288repo.defbranch::
@@ -306,6 +326,11 @@ repo.snapshots::
306 "snapshots" global setting. Default value: <snapshots>. 326 "snapshots" global setting. Default value: <snapshots>.
307 327
328repo.section::
329 Override the current section name for this repository. Default value:
330 none.
331
308repo.source-filter:: 332repo.source-filter::
309 Override the default source-filter. Default value: <source-filter>. 333 Override the default source-filter. Default value: none. See also:
334 "enable-filter-overrides".
310 335
311repo.url:: 336repo.url::
@@ -314,4 +339,17 @@ repo.url::
314 339
315 340
341REPOSITORY-SPECIFIC CGITRC FILE
342-------------------------------
343When the option "scan-path" is used to auto-discover git repositories, cgit
344will try to parse the file "cgitrc" within any found repository. Such a
345repo-specific config file may contain any of the repo-specific options
346described above, except "repo.url" and "repo.path". Additionally, the "filter"
347options are only acknowledged in repo-specific config files when
348"enable-filter-overrides" is set to "1".
349
350Note: the "repo." prefix is dropped from the option names in repo-specific
351config files, e.g. "repo.desc" becomes "desc".
352
353
316EXAMPLE CGITRC FILE 354EXAMPLE CGITRC FILE
317------------------- 355-------------------
diff --git a/scan-tree.c b/scan-tree.c
index 4da21a4..dbca797 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -1,3 +1,4 @@
1#include "cgit.h" 1#include "cgit.h"
2#include "configfile.h"
2#include "html.h" 3#include "html.h"
3 4
@@ -36,7 +37,14 @@ static int is_git_dir(const char *path)
36} 37}
37 38
38static void add_repo(const char *base, const char *path)
39{
40 struct cgit_repo *repo; 39 struct 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)
48{
41 struct stat st; 49 struct stat st;
42 struct passwd *pwd; 50 struct passwd *pwd;
@@ -77,7 +85,13 @@ static void add_repo(const char *base, const char *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;
@@ -87,5 +101,9 @@ static void scan_path(const char *base, const char *path)
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);
104 return;
105 }
106 if (is_git_dir(fmt("%s/.git", path))) {
107 add_repo(base, fmt("%s/.git", path), fn);
90 return; 108 return;
91 } 109 }
@@ -117,5 +135,5 @@ static void scan_path(const char *base, const char *path)
117 } 135 }
118 if (S_ISDIR(st.st_mode)) 136 if (S_ISDIR(st.st_mode))
119 scan_path(base, buf); 137 scan_path(base, buf, fn);
120 free(buf); 138 free(buf);
121 } 139 }
@@ -123,6 +141,6 @@ static void scan_path(const char *base, const char *path)
123} 141}
124 142
125void scan_tree(const char *path) 143void scan_tree(const char *path, repo_config_fn fn)
126{ 144{
127 scan_path(path, path); 145 scan_path(path, path, fn);
128} 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);
diff --git a/shared.c b/shared.c
index 4cb9573..d7b2d5a 100644
--- a/shared.c
+++ b/shared.c
@@ -49,4 +49,5 @@ struct cgit_repo *cgit_add_repo(const char *url)
49 49
50 ret = &cgit_repolist.repos[cgit_repolist.count-1]; 50 ret = &cgit_repolist.repos[cgit_repolist.count-1];
51 memset(ret, 0, sizeof(struct cgit_repo));
51 ret->url = trim_end(url, '/'); 52 ret->url = trim_end(url, '/');
52 ret->name = ret->url; 53 ret->name = ret->url;
@@ -54,5 +55,5 @@ struct cgit_repo *cgit_add_repo(const char *url)
54 ret->desc = "[no description]"; 55 ret->desc = "[no description]";
55 ret->owner = NULL; 56 ret->owner = NULL;
56 ret->group = ctx.cfg.repo_group; 57 ret->section = ctx.cfg.section;
57 ret->defbranch = "master"; 58 ret->defbranch = "master";
58 ret->snapshots = ctx.cfg.snapshots; 59 ret->snapshots = ctx.cfg.snapshots;
diff --git a/ui-repolist.c b/ui-repolist.c
index 7c7aa9b..3ef2e99 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -137,4 +137,16 @@ static int cmp(const char *s1, const char *s2)
137} 137}
138 138
139static int sort_section(const void *a, const void *b)
140{
141 const struct cgit_repo *r1 = a;
142 const struct cgit_repo *r2 = b;
143 int result;
144
145 result = cmp(r1->section, r2->section);
146 if (!result)
147 result = cmp(r1->name, r2->name);
148 return result;
149}
150
139static int sort_name(const void *a, const void *b) 151static int sort_name(const void *a, const void *b)
140{ 152{
@@ -179,4 +191,5 @@ struct sortcolumn {
179 191
180struct sortcolumn sortcolumn[] = { 192struct sortcolumn sortcolumn[] = {
193 {"section", sort_section},
181 {"name", sort_name}, 194 {"name", sort_name},
182 {"desc", sort_desc}, 195 {"desc", sort_desc},
@@ -204,5 +217,6 @@ void cgit_print_repolist()
204{ 217{
205 int i, columns = 4, hits = 0, header = 0; 218 int i, columns = 4, hits = 0, header = 0;
206 char *last_group = NULL; 219 char *last_section = NULL;
220 char *section;
207 int sorted = 0; 221 int sorted = 0;
208 222
@@ -220,4 +234,6 @@ void cgit_print_repolist()
220 if(ctx.qry.sort) 234 if(ctx.qry.sort)
221 sorted = sort_repolist(ctx.qry.sort); 235 sorted = sort_repolist(ctx.qry.sort);
236 else
237 sort_repolist("section");
222 238
223 html("<table summary='repository list' class='list nowrap'>"); 239 html("<table summary='repository list' class='list nowrap'>");
@@ -233,17 +249,20 @@ void cgit_print_repolist()
233 if (!header++) 249 if (!header++)
234 print_header(columns); 250 print_header(columns);
251 section = ctx.repo->section;
252 if (section && !strcmp(section, ""))
253 section = NULL;
235 if (!sorted && 254 if (!sorted &&
236 ((last_group == NULL && ctx.repo->group != NULL) || 255 ((last_section == NULL && section != NULL) ||
237 (last_group != NULL && ctx.repo->group == NULL) || 256 (last_section != NULL && section == NULL) ||
238 (last_group != NULL && ctx.repo->group != NULL && 257 (last_section != NULL && section != NULL &&
239 strcmp(ctx.repo->group, last_group)))) { 258 strcmp(section, last_section)))) {
240 htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>", 259 htmlf("<tr class='nohover'><td colspan='%d' class='reposection'>",
241 columns); 260 columns);
242 html_txt(ctx.repo->group); 261 html_txt(section);
243 html("</td></tr>"); 262 html("</td></tr>");
244 last_group = ctx.repo->group; 263 last_section = section;
245 } 264 }
246 htmlf("<tr><td class='%s'>", 265 htmlf("<tr><td class='%s'>",
247 !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); 266 !sorted && section ? "sublevel-repo" : "toplevel-repo");
248 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); 267 cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
249 html("</td><td>"); 268 html("</td><td>");
diff --git a/ui-stats.c b/ui-stats.c
index 9fc06d3..bdaf9cc 100644
--- a/ui-stats.c
+++ b/ui-stats.c
@@ -155,4 +155,12 @@ int cgit_find_stats_period(const char *expr, struct cgit_period **period)
155} 155}
156 156
157const char *cgit_find_stats_periodname(int idx)
158{
159 if (idx > 0 && idx < 4)
160 return periods[idx - 1].name;
161 else
162 return "";
163}
164
157static void add_commit(struct string_list *authors, struct commit *commit, 165static void add_commit(struct string_list *authors, struct commit *commit,
158 struct cgit_period *period) 166 struct cgit_period *period)
diff --git a/ui-stats.h b/ui-stats.h
index 4f13dba..f0761ba 100644
--- a/ui-stats.h
+++ b/ui-stats.h
@@ -22,4 +22,5 @@ struct cgit_period {
22 22
23extern int cgit_find_stats_period(const char *expr, struct cgit_period **period); 23extern int cgit_find_stats_period(const char *expr, struct cgit_period **period);
24extern const char *cgit_find_stats_periodname(int idx);
24 25
25extern void cgit_show_stats(struct cgit_context *ctx); 26extern void cgit_show_stats(struct cgit_context *ctx);