Merge branch 'lh/repo-scan'

This commit is contained in:
Lars Hjemli 2009-09-13 22:02:07 +02:00
commit 92f6940975
11 changed files with 331 additions and 82 deletions

View File

@ -32,4 +32,6 @@ extern int cache_ls(const char *path);
/* Print a message to stdout */ /* Print a message to stdout */
extern void cache_log(const char *format, ...); extern void cache_log(const char *format, ...);
extern unsigned long hash_str(const char *str);
#endif /* CGIT_CACHE_H */ #endif /* CGIT_CACHE_H */

269
cgit.c
View File

@ -40,9 +40,58 @@ struct cgit_filter *new_filter(const char *cmd, int extra_args)
return f; return f;
} }
static void process_cached_repolist(const char *path);
void repo_config(struct cgit_repo *repo, const char *name, const char *value)
{
if (!strcmp(name, "name"))
repo->name = xstrdup(value);
else if (!strcmp(name, "clone-url"))
repo->clone_url = xstrdup(value);
else if (!strcmp(name, "desc"))
repo->desc = xstrdup(value);
else if (!strcmp(name, "owner"))
repo->owner = xstrdup(value);
else if (!strcmp(name, "defbranch"))
repo->defbranch = xstrdup(value);
else if (!strcmp(name, "snapshots"))
repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value);
else if (!strcmp(name, "enable-log-filecount"))
repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
else if (!strcmp(name, "enable-log-linecount"))
repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
else if (!strcmp(name, "max-stats"))
repo->max_stats = cgit_find_stats_period(value, NULL);
else if (!strcmp(name, "module-link"))
repo->module_link= xstrdup(value);
else if (!strcmp(name, "section"))
repo->section = xstrdup(value);
else if (!strcmp(name, "readme") && value != NULL) {
if (*value == '/')
ctx.repo->readme = xstrdup(value);
else
ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
} else if (ctx.cfg.enable_filter_overrides) {
if (!strcmp(name, "about-filter"))
repo->about_filter = new_filter(value, 0);
else if (!strcmp(name, "commit-filter"))
repo->commit_filter = new_filter(value, 0);
else if (!strcmp(name, "source-filter"))
repo->source_filter = new_filter(value, 1);
}
}
void config_cb(const char *name, const char *value) void config_cb(const char *name, const char *value)
{ {
if (!strcmp(name, "root-title")) if (!strcmp(name, "section") || !strcmp(name, "repo.group"))
ctx.cfg.section = xstrdup(value);
else if (!strcmp(name, "repo.url"))
ctx.repo = cgit_add_repo(value);
else if (ctx.repo && !strcmp(name, "repo.path"))
ctx.repo->path = trim_end(value, '/');
else if (ctx.repo && !prefixcmp(name, "repo."))
repo_config(ctx.repo, name + 5, value);
else if (!strcmp(name, "root-title"))
ctx.cfg.root_title = xstrdup(value); ctx.cfg.root_title = xstrdup(value);
else if (!strcmp(name, "root-desc")) else if (!strcmp(name, "root-desc"))
ctx.cfg.root_desc = xstrdup(value); ctx.cfg.root_desc = xstrdup(value);
@ -80,6 +129,8 @@ void config_cb(const char *name, const char *value)
ctx.cfg.noheader = atoi(value); ctx.cfg.noheader = atoi(value);
else if (!strcmp(name, "snapshots")) else if (!strcmp(name, "snapshots"))
ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
else if (!strcmp(name, "enable-filter-overrides"))
ctx.cfg.enable_filter_overrides = atoi(value);
else if (!strcmp(name, "enable-index-links")) else if (!strcmp(name, "enable-index-links"))
ctx.cfg.enable_index_links = atoi(value); ctx.cfg.enable_index_links = atoi(value);
else if (!strcmp(name, "enable-log-filecount")) else if (!strcmp(name, "enable-log-filecount"))
@ -98,6 +149,8 @@ void config_cb(const char *name, const char *value)
ctx.cfg.cache_root_ttl = atoi(value); ctx.cfg.cache_root_ttl = atoi(value);
else if (!strcmp(name, "cache-repo-ttl")) else if (!strcmp(name, "cache-repo-ttl"))
ctx.cfg.cache_repo_ttl = atoi(value); ctx.cfg.cache_repo_ttl = atoi(value);
else if (!strcmp(name, "cache-scanrc-ttl"))
ctx.cfg.cache_scanrc_ttl = atoi(value);
else if (!strcmp(name, "cache-static-ttl")) else if (!strcmp(name, "cache-static-ttl"))
ctx.cfg.cache_static_ttl = atoi(value); ctx.cfg.cache_static_ttl = atoi(value);
else if (!strcmp(name, "cache-dynamic-ttl")) else if (!strcmp(name, "cache-dynamic-ttl"))
@ -116,6 +169,11 @@ void config_cb(const char *name, const char *value)
ctx.cfg.max_repo_count = atoi(value); ctx.cfg.max_repo_count = atoi(value);
else if (!strcmp(name, "max-commit-count")) else if (!strcmp(name, "max-commit-count"))
ctx.cfg.max_commit_count = atoi(value); ctx.cfg.max_commit_count = atoi(value);
else if (!strcmp(name, "scan-path"))
if (!ctx.cfg.nocache && ctx.cfg.cache_size)
process_cached_repolist(value);
else
scan_tree(value, repo_config);
else if (!strcmp(name, "source-filter")) else if (!strcmp(name, "source-filter"))
ctx.cfg.source_filter = new_filter(value, 1); ctx.cfg.source_filter = new_filter(value, 1);
else if (!strcmp(name, "summary-log")) else if (!strcmp(name, "summary-log"))
@ -136,44 +194,7 @@ void config_cb(const char *name, const char *value)
ctx.cfg.local_time = atoi(value); ctx.cfg.local_time = atoi(value);
else if (!prefixcmp(name, "mimetype.")) else if (!prefixcmp(name, "mimetype."))
add_mimetype(name + 9, value); add_mimetype(name + 9, value);
else if (!strcmp(name, "repo.group")) else if (!strcmp(name, "include"))
ctx.cfg.repo_group = xstrdup(value);
else if (!strcmp(name, "repo.url"))
ctx.repo = cgit_add_repo(value);
else if (!strcmp(name, "repo.name"))
ctx.repo->name = xstrdup(value);
else if (ctx.repo && !strcmp(name, "repo.path"))
ctx.repo->path = trim_end(value, '/');
else if (ctx.repo && !strcmp(name, "repo.clone-url"))
ctx.repo->clone_url = xstrdup(value);
else if (ctx.repo && !strcmp(name, "repo.desc"))
ctx.repo->desc = xstrdup(value);
else if (ctx.repo && !strcmp(name, "repo.owner"))
ctx.repo->owner = xstrdup(value);
else if (ctx.repo && !strcmp(name, "repo.defbranch"))
ctx.repo->defbranch = xstrdup(value);
else if (ctx.repo && !strcmp(name, "repo.snapshots"))
ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */
else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount"))
ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value);
else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount"))
ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value);
else if (ctx.repo && !strcmp(name, "repo.max-stats"))
ctx.repo->max_stats = cgit_find_stats_period(value, NULL);
else if (ctx.repo && !strcmp(name, "repo.module-link"))
ctx.repo->module_link= xstrdup(value);
else if (ctx.repo && !strcmp(name, "repo.about-filter"))
ctx.repo->about_filter = new_filter(value, 0);
else if (ctx.repo && !strcmp(name, "repo.commit-filter"))
ctx.repo->commit_filter = new_filter(value, 0);
else if (ctx.repo && !strcmp(name, "repo.source-filter"))
ctx.repo->source_filter = new_filter(value, 1);
else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) {
if (*value == '/')
ctx.repo->readme = xstrdup(value);
else
ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value));
} else if (!strcmp(name, "include"))
parse_configfile(value, config_cb); parse_configfile(value, config_cb);
} }
@ -236,6 +257,7 @@ static void prepare_context(struct cgit_context *ctx)
ctx->cfg.cache_repo_ttl = 5; ctx->cfg.cache_repo_ttl = 5;
ctx->cfg.cache_root = CGIT_CACHE_ROOT; ctx->cfg.cache_root = CGIT_CACHE_ROOT;
ctx->cfg.cache_root_ttl = 5; ctx->cfg.cache_root_ttl = 5;
ctx->cfg.cache_scanrc_ttl = 15;
ctx->cfg.cache_static_ttl = -1; ctx->cfg.cache_static_ttl = -1;
ctx->cfg.css = "/cgit.css"; ctx->cfg.css = "/cgit.css";
ctx->cfg.logo = "/cgit.png"; ctx->cfg.logo = "/cgit.png";
@ -253,6 +275,7 @@ static void prepare_context(struct cgit_context *ctx)
ctx->cfg.root_title = "Git repository browser"; ctx->cfg.root_title = "Git repository browser";
ctx->cfg.root_desc = "a fast webinterface for the git dscm"; ctx->cfg.root_desc = "a fast webinterface for the git dscm";
ctx->cfg.script_name = CGIT_SCRIPT_NAME; ctx->cfg.script_name = CGIT_SCRIPT_NAME;
ctx->cfg.section = "";
ctx->cfg.summary_branches = 10; ctx->cfg.summary_branches = 10;
ctx->cfg.summary_log = 10; ctx->cfg.summary_log = 10;
ctx->cfg.summary_tags = 10; ctx->cfg.summary_tags = 10;
@ -417,28 +440,151 @@ int cmp_repos(const void *a, const void *b)
return strcmp(ra->url, rb->url); return strcmp(ra->url, rb->url);
} }
void print_repo(struct cgit_repo *repo) char *build_snapshot_setting(int bitmap)
{ {
printf("repo.url=%s\n", repo->url); const struct cgit_snapshot_format *f;
printf("repo.name=%s\n", repo->name); char *result = xstrdup("");
printf("repo.path=%s\n", repo->path); char *tmp;
if (repo->owner) int len;
printf("repo.owner=%s\n", repo->owner);
if (repo->desc) for (f = cgit_snapshot_formats; f->suffix; f++) {
printf("repo.desc=%s\n", repo->desc); if (f->bit & bitmap) {
if (repo->readme) tmp = result;
printf("repo.readme=%s\n", repo->readme); result = xstrdup(fmt("%s%s ", tmp, f->suffix));
printf("\n"); free(tmp);
}
}
len = strlen(result);
if (len)
result[len - 1] = '\0';
return result;
} }
void print_repolist(struct cgit_repolist *list) char *get_first_line(char *txt)
{
char *t = xstrdup(txt);
char *p = strchr(t, '\n');
if (p)
*p = '\0';
return t;
}
void print_repo(FILE *f, struct cgit_repo *repo)
{
fprintf(f, "repo.url=%s\n", repo->url);
fprintf(f, "repo.name=%s\n", repo->name);
fprintf(f, "repo.path=%s\n", repo->path);
if (repo->owner)
fprintf(f, "repo.owner=%s\n", repo->owner);
if (repo->desc) {
char *tmp = get_first_line(repo->desc);
fprintf(f, "repo.desc=%s\n", tmp);
free(tmp);
}
if (repo->readme)
fprintf(f, "repo.readme=%s\n", repo->readme);
if (repo->defbranch)
fprintf(f, "repo.defbranch=%s\n", repo->defbranch);
if (repo->module_link)
fprintf(f, "repo.module-link=%s\n", repo->module_link);
if (repo->section)
fprintf(f, "repo.section=%s\n", repo->section);
if (repo->clone_url)
fprintf(f, "repo.clone-url=%s\n", repo->clone_url);
fprintf(f, "repo.enable-log-filecount=%d\n",
repo->enable_log_filecount);
fprintf(f, "repo.enable-log-linecount=%d\n",
repo->enable_log_linecount);
if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter)
fprintf(f, "repo.about-filter=%s\n", repo->about_filter->cmd);
if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter)
fprintf(f, "repo.commit-filter=%s\n", repo->commit_filter->cmd);
if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter)
fprintf(f, "repo.source-filter=%s\n", repo->source_filter->cmd);
if (repo->snapshots != ctx.cfg.snapshots) {
char *tmp = build_snapshot_setting(repo->snapshots);
fprintf(f, "repo.snapshots=%s\n", tmp);
free(tmp);
}
if (repo->max_stats != ctx.cfg.max_stats)
fprintf(f, "repo.max-stats=%s\n",
cgit_find_stats_periodname(repo->max_stats));
fprintf(f, "\n");
}
void print_repolist(FILE *f, struct cgit_repolist *list, int start)
{ {
int i; int i;
for(i = 0; i < list->count; i++) for(i = start; i < list->count; i++)
print_repo(&list->repos[i]); print_repo(f, &list->repos[i]);
} }
/* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc'
* and return 0 on success.
*/
static int generate_cached_repolist(const char *path, const char *cached_rc)
{
char *locked_rc;
int idx;
FILE *f;
locked_rc = xstrdup(fmt("%s.lock", cached_rc));
f = fopen(locked_rc, "wx");
if (!f) {
/* Inform about the error unless the lockfile already existed,
* since that only means we've got concurrent requests.
*/
if (errno != EEXIST)
fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n",
locked_rc, strerror(errno), errno);
return errno;
}
idx = cgit_repolist.count;
scan_tree(path, repo_config);
print_repolist(f, &cgit_repolist, idx);
if (rename(locked_rc, cached_rc))
fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n",
locked_rc, cached_rc, strerror(errno), errno);
fclose(f);
return 0;
}
static void process_cached_repolist(const char *path)
{
struct stat st;
char *cached_rc;
time_t age;
cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root,
hash_str(path)));
if (stat(cached_rc, &st)) {
/* Nothing is cached, we need to scan without forking. And
* if we fail to generate a cached repolist, we need to
* invoke scan_tree manually.
*/
if (generate_cached_repolist(path, cached_rc))
scan_tree(path, repo_config);
return;
}
parse_configfile(cached_rc, config_cb);
/* If the cached configfile hasn't expired, lets exit now */
age = time(NULL) - st.st_mtime;
if (age <= (ctx.cfg.cache_scanrc_ttl * 60))
return;
/* The cached repolist has been parsed, but it was old. So lets
* rescan the specified path and generate a new cached repolist
* in a child-process to avoid latency for the current request.
*/
if (fork())
return;
exit(generate_cached_repolist(path, cached_rc));
}
static void cgit_parse_args(int argc, const char **argv) static void cgit_parse_args(int argc, const char **argv)
{ {
@ -475,15 +621,26 @@ static void cgit_parse_args(int argc, const char **argv)
if (!strncmp(argv[i], "--ofs=", 6)) { if (!strncmp(argv[i], "--ofs=", 6)) {
ctx.qry.ofs = atoi(argv[i]+6); ctx.qry.ofs = atoi(argv[i]+6);
} }
if (!strncmp(argv[i], "--scan-tree=", 12)) { if (!strncmp(argv[i], "--scan-tree=", 12) ||
!strncmp(argv[i], "--scan-path=", 12)) {
/* HACK: the global snapshot bitmask defines the
* set of allowed snapshot formats, but the config
* file hasn't been parsed yet so the mask is
* currently 0. By setting all bits high before
* scanning we make sure that any in-repo cgitrc
* snapshot setting is respected by scan_tree().
* BTW: we assume that there'll never be more than
* 255 different snapshot formats supported by cgit...
*/
ctx.cfg.snapshots = 0xFF;
scan++; scan++;
scan_tree(argv[i] + 12); scan_tree(argv[i] + 12, repo_config);
} }
} }
if (scan) { if (scan) {
qsort(cgit_repolist.repos, cgit_repolist.count, qsort(cgit_repolist.repos, cgit_repolist.count,
sizeof(struct cgit_repo), cmp_repos); sizeof(struct cgit_repo), cmp_repos);
print_repolist(&cgit_repolist); print_repolist(stdout, &cgit_repolist, 0);
exit(0); exit(0);
} }
} }

View File

@ -429,7 +429,7 @@ table.diff td div.del {
text-align: right; text-align: right;
} }
table.list td.repogroup { table.list td.reposection {
font-style: italic; font-style: italic;
color: #888; color: #888;
} }

9
cgit.h
View File

@ -65,9 +65,9 @@ struct cgit_repo {
char *desc; char *desc;
char *owner; char *owner;
char *defbranch; char *defbranch;
char *group;
char *module_link; char *module_link;
char *readme; char *readme;
char *section;
char *clone_url; char *clone_url;
int snapshots; int snapshots;
int enable_log_filecount; int enable_log_filecount;
@ -79,6 +79,9 @@ struct cgit_repo {
struct cgit_filter *source_filter; struct cgit_filter *source_filter;
}; };
typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
const char *value);
struct cgit_repolist { struct cgit_repolist {
int length; int length;
int count; int count;
@ -156,20 +159,22 @@ struct cgit_config {
char *logo; char *logo;
char *logo_link; char *logo_link;
char *module_link; char *module_link;
char *repo_group;
char *robots; char *robots;
char *root_title; char *root_title;
char *root_desc; char *root_desc;
char *root_readme; char *root_readme;
char *script_name; char *script_name;
char *section;
char *virtual_root; char *virtual_root;
int cache_size; int cache_size;
int cache_dynamic_ttl; int cache_dynamic_ttl;
int cache_max_create_time; int cache_max_create_time;
int cache_repo_ttl; int cache_repo_ttl;
int cache_root_ttl; int cache_root_ttl;
int cache_scanrc_ttl;
int cache_static_ttl; int cache_static_ttl;
int embedded; int embedded;
int enable_filter_overrides;
int enable_index_links; int enable_index_links;
int enable_log_filecount; int enable_log_filecount;
int enable_log_linecount; int enable_log_linecount;

View File

@ -54,6 +54,10 @@ cache-root-ttl::
Number which specifies the time-to-live, in minutes, for the cached Number which specifies the time-to-live, in minutes, for the cached
version of the repository index page. Default value: "5". version of the repository index page. Default value: "5".
cache-scanrc-ttl::
Number which specifies the time-to-live, in minutes, for the result
of scanning a path for git repositories. Default value: "15".
cache-size:: cache-size::
The maximum number of entries in the cgit cache. Default value: "0" The maximum number of entries in the cgit cache. Default value: "0"
(i.e. caching is disabled). (i.e. caching is disabled).
@ -84,6 +88,10 @@ embedded::
suitable for embedding in other html pages. Default value: none. See suitable for embedding in other html pages. Default value: none. See
also: "noheader". also: "noheader".
enable-filter-overrides::
Flag which, when set to "1", allows all filter settings to be
overridden in repository-specific cgitrc files. Default value: none.
enable-index-links:: enable-index-links::
Flag which, when set to "1", will make cgit generate extra links for Flag which, when set to "1", will make cgit generate extra links for
each repo in the repository index (specifically, to the "summary", each repo in the repository index (specifically, to the "summary",
@ -200,8 +208,8 @@ renamelimit::
`man git-diff`). Default value: "-1". `man git-diff`). Default value: "-1".
repo.group:: repo.group::
A value for the current repository group, which all repositories Legacy alias for "section". This option is deprecated and will not be
specified after this setting will inherit. Default value: none. supported in cgit-1.0.
robots:: robots::
Text used as content for the "robots" meta-tag. Default value: Text used as content for the "robots" meta-tag. Default value:
@ -220,6 +228,16 @@ root-title::
Text printed as heading on the repository index page. Default value: Text printed as heading on the repository index page. Default value:
"Git Repository Browser". "Git Repository Browser".
scan-path::
A path which will be scanned for repositories. If caching is enabled,
the result will be cached as a cgitrc include-file in the cache
directory. Default value: none. See also: cache-scanrc-ttl.
section::
The name of the current repository section - all repositories defined
after this option will inherit the current section name. Default value:
none.
snapshots:: snapshots::
Text which specifies the default set of snapshot formats generated by Text which specifies the default set of snapshot formats generated by
cgit. The value is a space-separated list of zero or more of the cgit. The value is a space-separated list of zero or more of the
@ -256,14 +274,16 @@ virtual-root::
REPOSITORY SETTINGS REPOSITORY SETTINGS
------------------- -------------------
repo.about-filter:: repo.about-filter::
Override the default about-filter. Default value: <about-filter>. Override the default about-filter. Default value: none. See also:
"enable-filter-overrides".
repo.clone-url:: repo.clone-url::
A list of space-separated urls which can be used to clone this repo. A list of space-separated urls which can be used to clone this repo.
Default value: none. Default value: none.
repo.commit-filter:: repo.commit-filter::
Override the default commit-filter. Default value: <commit-filter>. Override the default commit-filter. Default value: none. See also:
"enable-filter-overrides".
repo.defbranch:: repo.defbranch::
The name of the default branch for this repository. If no such branch The name of the default branch for this repository. If no such branch
@ -305,14 +325,32 @@ repo.snapshots::
A mask of allowed snapshot-formats for this repo, restricted by the A mask of allowed snapshot-formats for this repo, restricted by the
"snapshots" global setting. Default value: <snapshots>. "snapshots" global setting. Default value: <snapshots>.
repo.section::
Override the current section name for this repository. Default value:
none.
repo.source-filter:: repo.source-filter::
Override the default source-filter. Default value: <source-filter>. Override the default source-filter. Default value: none. See also:
"enable-filter-overrides".
repo.url:: repo.url::
The relative url used to access the repository. This must be the first The relative url used to access the repository. This must be the first
setting specified for each repo. Default value: none. setting specified for each repo. Default value: none.
REPOSITORY-SPECIFIC CGITRC FILE
-------------------------------
When the option "scan-path" is used to auto-discover git repositories, cgit
will try to parse the file "cgitrc" within any found repository. Such a
repo-specific config file may contain any of the repo-specific options
described above, except "repo.url" and "repo.path". Additionally, the "filter"
options are only acknowledged in repo-specific config files when
"enable-filter-overrides" is set to "1".
Note: the "repo." prefix is dropped from the option names in repo-specific
config files, e.g. "repo.desc" becomes "desc".
EXAMPLE CGITRC FILE EXAMPLE CGITRC FILE
------------------- -------------------

View File

@ -1,4 +1,5 @@
#include "cgit.h" #include "cgit.h"
#include "configfile.h"
#include "html.h" #include "html.h"
#define MAX_PATH 4096 #define MAX_PATH 4096
@ -35,9 +36,16 @@ static int is_git_dir(const char *path)
return 1; return 1;
} }
static void add_repo(const char *base, const char *path) struct cgit_repo *repo;
repo_config_fn config_fn;
static void repo_config(const char *name, const char *value)
{
config_fn(repo, name, value);
}
static void add_repo(const char *base, const char *path, repo_config_fn fn)
{ {
struct cgit_repo *repo;
struct stat st; struct stat st;
struct passwd *pwd; struct passwd *pwd;
char *p; char *p;
@ -76,9 +84,15 @@ static void add_repo(const char *base, const char *path)
p = fmt("%s/README.html", path); p = fmt("%s/README.html", path);
if (!stat(p, &st)) if (!stat(p, &st))
repo->readme = "README.html"; repo->readme = "README.html";
p = fmt("%s/cgitrc", path);
if (!stat(p, &st)) {
config_fn = fn;
parse_configfile(xstrdup(p), &repo_config);
}
} }
static void scan_path(const char *base, const char *path) static void scan_path(const char *base, const char *path, repo_config_fn fn)
{ {
DIR *dir; DIR *dir;
struct dirent *ent; struct dirent *ent;
@ -86,7 +100,11 @@ static void scan_path(const char *base, const char *path)
struct stat st; struct stat st;
if (is_git_dir(path)) { if (is_git_dir(path)) {
add_repo(base, path); add_repo(base, path, fn);
return;
}
if (is_git_dir(fmt("%s/.git", path))) {
add_repo(base, fmt("%s/.git", path), fn);
return; return;
} }
dir = opendir(path); dir = opendir(path);
@ -116,13 +134,13 @@ static void scan_path(const char *base, const char *path)
continue; continue;
} }
if (S_ISDIR(st.st_mode)) if (S_ISDIR(st.st_mode))
scan_path(base, buf); scan_path(base, buf, fn);
free(buf); free(buf);
} }
closedir(dir); closedir(dir);
} }
void scan_tree(const char *path) void scan_tree(const char *path, repo_config_fn fn)
{ {
scan_path(path, path); scan_path(path, path, fn);
} }

View File

@ -1,3 +1,3 @@
extern void scan_tree(const char *path); extern void scan_tree(const char *path, repo_config_fn fn);

View File

@ -48,12 +48,13 @@ struct cgit_repo *cgit_add_repo(const char *url)
} }
ret = &cgit_repolist.repos[cgit_repolist.count-1]; ret = &cgit_repolist.repos[cgit_repolist.count-1];
memset(ret, 0, sizeof(struct cgit_repo));
ret->url = trim_end(url, '/'); ret->url = trim_end(url, '/');
ret->name = ret->url; ret->name = ret->url;
ret->path = NULL; ret->path = NULL;
ret->desc = "[no description]"; ret->desc = "[no description]";
ret->owner = NULL; ret->owner = NULL;
ret->group = ctx.cfg.repo_group; ret->section = ctx.cfg.section;
ret->defbranch = "master"; ret->defbranch = "master";
ret->snapshots = ctx.cfg.snapshots; ret->snapshots = ctx.cfg.snapshots;
ret->enable_log_filecount = ctx.cfg.enable_log_filecount; ret->enable_log_filecount = ctx.cfg.enable_log_filecount;

View File

@ -136,6 +136,18 @@ static int cmp(const char *s1, const char *s2)
return 0; return 0;
} }
static int sort_section(const void *a, const void *b)
{
const struct cgit_repo *r1 = a;
const struct cgit_repo *r2 = b;
int result;
result = cmp(r1->section, r2->section);
if (!result)
result = cmp(r1->name, r2->name);
return result;
}
static int sort_name(const void *a, const void *b) static int sort_name(const void *a, const void *b)
{ {
const struct cgit_repo *r1 = a; const struct cgit_repo *r1 = a;
@ -178,6 +190,7 @@ struct sortcolumn {
}; };
struct sortcolumn sortcolumn[] = { struct sortcolumn sortcolumn[] = {
{"section", sort_section},
{"name", sort_name}, {"name", sort_name},
{"desc", sort_desc}, {"desc", sort_desc},
{"owner", sort_owner}, {"owner", sort_owner},
@ -203,7 +216,8 @@ int sort_repolist(char *field)
void cgit_print_repolist() void cgit_print_repolist()
{ {
int i, columns = 4, hits = 0, header = 0; int i, columns = 4, hits = 0, header = 0;
char *last_group = NULL; char *last_section = NULL;
char *section;
int sorted = 0; int sorted = 0;
if (ctx.cfg.enable_index_links) if (ctx.cfg.enable_index_links)
@ -219,6 +233,8 @@ void cgit_print_repolist()
if(ctx.qry.sort) if(ctx.qry.sort)
sorted = sort_repolist(ctx.qry.sort); sorted = sort_repolist(ctx.qry.sort);
else
sort_repolist("section");
html("<table summary='repository list' class='list nowrap'>"); html("<table summary='repository list' class='list nowrap'>");
for (i=0; i<cgit_repolist.count; i++) { for (i=0; i<cgit_repolist.count; i++) {
@ -232,19 +248,22 @@ void cgit_print_repolist()
continue; continue;
if (!header++) if (!header++)
print_header(columns); print_header(columns);
section = ctx.repo->section;
if (section && !strcmp(section, ""))
section = NULL;
if (!sorted && if (!sorted &&
((last_group == NULL && ctx.repo->group != NULL) || ((last_section == NULL && section != NULL) ||
(last_group != NULL && ctx.repo->group == NULL) || (last_section != NULL && section == NULL) ||
(last_group != NULL && ctx.repo->group != NULL && (last_section != NULL && section != NULL &&
strcmp(ctx.repo->group, last_group)))) { strcmp(section, last_section)))) {
htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>", htmlf("<tr class='nohover'><td colspan='%d' class='reposection'>",
columns); columns);
html_txt(ctx.repo->group); html_txt(section);
html("</td></tr>"); html("</td></tr>");
last_group = ctx.repo->group; last_section = section;
} }
htmlf("<tr><td class='%s'>", htmlf("<tr><td class='%s'>",
!sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); !sorted && section ? "sublevel-repo" : "toplevel-repo");
cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
html("</td><td>"); html("</td><td>");
html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL);

View File

@ -154,6 +154,14 @@ int cgit_find_stats_period(const char *expr, struct cgit_period **period)
return 0; return 0;
} }
const char *cgit_find_stats_periodname(int idx)
{
if (idx > 0 && idx < 4)
return periods[idx - 1].name;
else
return "";
}
static void add_commit(struct string_list *authors, struct commit *commit, static void add_commit(struct string_list *authors, struct commit *commit,
struct cgit_period *period) struct cgit_period *period)
{ {

View File

@ -21,6 +21,7 @@ struct cgit_period {
}; };
extern int cgit_find_stats_period(const char *expr, struct cgit_period **period); extern int cgit_find_stats_period(const char *expr, struct cgit_period **period);
extern const char *cgit_find_stats_periodname(int idx);
extern void cgit_show_stats(struct cgit_context *ctx); extern void cgit_show_stats(struct cgit_context *ctx);