diff options
| -rw-r--r-- | cache.h | 2 | ||||
| -rw-r--r-- | cgit.c | 265 | ||||
| -rw-r--r-- | cgit.css | 2 | ||||
| -rw-r--r-- | cgit.h | 9 | ||||
| -rw-r--r-- | cgitrc.5.txt | 48 | ||||
| -rw-r--r-- | scan-tree.c | 32 | ||||
| -rw-r--r-- | scan-tree.h | 2 | ||||
| -rw-r--r-- | shared.c | 3 | ||||
| -rw-r--r-- | ui-repolist.c | 37 | ||||
| -rw-r--r-- | ui-stats.c | 8 | ||||
| -rw-r--r-- | ui-stats.h | 1 | 
11 files changed, 329 insertions, 80 deletions
| @@ -32,4 +32,6 @@ extern int cache_ls(const char *path);  /* Print a message to stdout */  extern void cache_log(const char *format, ...); +extern unsigned long hash_str(const char *str); +  #endif /* CGIT_CACHE_H */ @@ -40,9 +40,58 @@ struct cgit_filter *new_filter(const char *cmd, int extra_args)  	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)  { -	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);  	else if (!strcmp(name, "root-desc"))  		ctx.cfg.root_desc = xstrdup(value); @@ -80,6 +129,8 @@ void config_cb(const char *name, const char *value)  		ctx.cfg.noheader = atoi(value);  	else if (!strcmp(name, "snapshots"))  		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"))  		ctx.cfg.enable_index_links = atoi(value);  	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);  	else if (!strcmp(name, "cache-repo-ttl"))  		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"))  		ctx.cfg.cache_static_ttl = atoi(value);  	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);  	else if (!strcmp(name, "max-commit-count"))  		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"))  		ctx.cfg.source_filter = new_filter(value, 1);  	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);  	else if (!prefixcmp(name, "mimetype."))  		add_mimetype(name + 9, value); -	else if (!strcmp(name, "repo.group")) -		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")) +	else if (!strcmp(name, "include"))  		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_root = CGIT_CACHE_ROOT;  	ctx->cfg.cache_root_ttl = 5; +	ctx->cfg.cache_scanrc_ttl = 15;  	ctx->cfg.cache_static_ttl = -1;  	ctx->cfg.css = "/cgit.css";  	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_desc = "a fast webinterface for the git dscm";  	ctx->cfg.script_name = CGIT_SCRIPT_NAME; +	ctx->cfg.section = "";  	ctx->cfg.summary_branches = 10;  	ctx->cfg.summary_log = 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);  } -void print_repo(struct cgit_repo *repo) +char *build_snapshot_setting(int bitmap) +{ +	const struct cgit_snapshot_format *f; +	char *result = xstrdup(""); +	char *tmp; +	int len; + +	for (f = cgit_snapshot_formats; f->suffix; f++) { +		if (f->bit & bitmap) { +			tmp = result; +			result = xstrdup(fmt("%s%s ", tmp, f->suffix)); +			free(tmp); +		} +	} +	len = strlen(result); +	if (len) +		result[len - 1] = '\0'; +	return result; +} + +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)  { -	printf("repo.url=%s\n", repo->url); -	printf("repo.name=%s\n", repo->name); -	printf("repo.path=%s\n", repo->path); +	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) -		printf("repo.owner=%s\n", repo->owner); -	if (repo->desc) -		printf("repo.desc=%s\n", repo->desc); +		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) -		printf("repo.readme=%s\n", repo->readme); -	printf("\n"); +		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(struct cgit_repolist *list) +void print_repolist(FILE *f, struct cgit_repolist *list, int start)  {  	int i; -	for(i = 0; i < list->count; i++) -		print_repo(&list->repos[i]); +	for(i = start; i < list->count; 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)  { @@ -475,15 +621,26 @@ static void cgit_parse_args(int argc, const char **argv)  		if (!strncmp(argv[i], "--ofs=", 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_tree(argv[i] + 12); +			scan_tree(argv[i] + 12, repo_config);  		}  	}  	if (scan) {  		qsort(cgit_repolist.repos, cgit_repolist.count,  			sizeof(struct cgit_repo), cmp_repos); -		print_repolist(&cgit_repolist); +		print_repolist(stdout, &cgit_repolist, 0);  		exit(0);  	}  } @@ -429,7 +429,7 @@ table.diff td div.del {  	text-align: right;  } -table.list td.repogroup { +table.list td.reposection {  	font-style: italic;  	color: #888;  } @@ -65,9 +65,9 @@ struct cgit_repo {  	char *desc;  	char *owner;  	char *defbranch; -	char *group;  	char *module_link;  	char *readme; +	char *section;  	char *clone_url;  	int snapshots;  	int enable_log_filecount; @@ -79,6 +79,9 @@ struct cgit_repo {  	struct cgit_filter *source_filter;  }; +typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, +	      const char *value); +  struct cgit_repolist {  	int length;  	int count; @@ -156,20 +159,22 @@ struct cgit_config {  	char *logo;  	char *logo_link;  	char *module_link; -	char *repo_group;  	char *robots;  	char *root_title;  	char *root_desc;  	char *root_readme;  	char *script_name; +	char *section;  	char *virtual_root;  	int cache_size;  	int cache_dynamic_ttl;  	int cache_max_create_time;  	int cache_repo_ttl;  	int cache_root_ttl; +	int cache_scanrc_ttl;  	int cache_static_ttl;  	int embedded; +	int enable_filter_overrides;  	int enable_index_links;  	int enable_log_filecount;  	int enable_log_linecount; diff --git a/cgitrc.5.txt b/cgitrc.5.txt index 3b16db9..4dc383d 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt @@ -54,6 +54,10 @@ cache-root-ttl::  	Number which specifies the time-to-live, in minutes, for the cached  	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::  	The maximum number of entries in the cgit cache. Default value: "0"  	(i.e. caching is disabled). @@ -84,6 +88,10 @@ embedded::  	suitable for embedding in other html pages. Default value: none. See  	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::  	Flag which, when set to "1", will make cgit generate extra links for  	each repo in the repository index (specifically, to the "summary", @@ -200,8 +208,8 @@ renamelimit::  	  `man git-diff`). Default value: "-1".  repo.group:: -	A value for the current repository group, which all repositories -	specified after this setting will inherit. Default value: none. +	Legacy alias for "section". This option is deprecated and will not be +	supported in cgit-1.0.  robots::  	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:  	"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::  	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 @@ -256,14 +274,16 @@ virtual-root::  REPOSITORY SETTINGS  -------------------  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::  	A list of space-separated urls which can be used to clone this repo.  	Default value: none.  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::  	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  	"snapshots" global setting. Default value: <snapshots>. +repo.section:: +	Override the current section name for this repository. Default value: +	none. +  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::  	The relative url used to access the repository. This must be the first  	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  ------------------- diff --git a/scan-tree.c b/scan-tree.c index 4da21a4..dbca797 100644 --- a/scan-tree.c +++ b/scan-tree.c @@ -1,4 +1,5 @@  #include "cgit.h" +#include "configfile.h"  #include "html.h"  #define MAX_PATH 4096 @@ -35,9 +36,16 @@ static int is_git_dir(const char *path)  	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 passwd *pwd;  	char *p; @@ -76,9 +84,15 @@ static void add_repo(const char *base, const char *path)  	p = fmt("%s/README.html", path);  	if (!stat(p, &st))  		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;  	struct dirent *ent; @@ -86,7 +100,11 @@ static void scan_path(const char *base, const char *path)  	struct stat st;  	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;  	}  	dir = opendir(path); @@ -116,13 +134,13 @@ static void scan_path(const char *base, const char *path)  			continue;  		}  		if (S_ISDIR(st.st_mode)) -			scan_path(base, buf); +			scan_path(base, buf, fn);  		free(buf);  	}  	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);  } 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 @@ -extern void scan_tree(const char *path); +extern void scan_tree(const char *path, repo_config_fn fn); @@ -48,12 +48,13 @@ struct cgit_repo *cgit_add_repo(const char *url)  	}  	ret = &cgit_repolist.repos[cgit_repolist.count-1]; +	memset(ret, 0, sizeof(struct cgit_repo));  	ret->url = trim_end(url, '/');  	ret->name = ret->url;  	ret->path = NULL;  	ret->desc = "[no description]";  	ret->owner = NULL; -	ret->group = ctx.cfg.repo_group; +	ret->section = ctx.cfg.section;  	ret->defbranch = "master";  	ret->snapshots = ctx.cfg.snapshots;  	ret->enable_log_filecount = ctx.cfg.enable_log_filecount; diff --git a/ui-repolist.c b/ui-repolist.c index 7c7aa9b..3ef2e99 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -136,6 +136,18 @@ static int cmp(const char *s1, const char *s2)  	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)  {  	const struct cgit_repo *r1 = a; @@ -178,6 +190,7 @@ struct sortcolumn {  };  struct sortcolumn sortcolumn[] = { +	{"section", sort_section},  	{"name", sort_name},  	{"desc", sort_desc},  	{"owner", sort_owner}, @@ -203,7 +216,8 @@ int sort_repolist(char *field)  void cgit_print_repolist()  {  	int i, columns = 4, hits = 0, header = 0; -	char *last_group = NULL; +	char *last_section = NULL; +	char *section;  	int sorted = 0;  	if (ctx.cfg.enable_index_links) @@ -219,6 +233,8 @@ void cgit_print_repolist()  	if(ctx.qry.sort)  		sorted = sort_repolist(ctx.qry.sort); +	else +		sort_repolist("section");  	html("<table summary='repository list' class='list nowrap'>");  	for (i=0; i<cgit_repolist.count; i++) { @@ -232,19 +248,22 @@ void cgit_print_repolist()  			continue;  		if (!header++)  			print_header(columns); +		section = ctx.repo->section; +		if (section && !strcmp(section, "")) +			section = NULL;  		if (!sorted && -		    ((last_group == NULL && ctx.repo->group != NULL) || -		    (last_group != NULL && ctx.repo->group == NULL) || -		    (last_group != NULL && ctx.repo->group != NULL && -		     strcmp(ctx.repo->group, last_group)))) { -			htmlf("<tr class='nohover'><td colspan='%d' class='repogroup'>", +		    ((last_section == NULL && section != NULL) || +		    (last_section != NULL && section == NULL) || +		    (last_section != NULL && section != NULL && +		     strcmp(section, last_section)))) { +			htmlf("<tr class='nohover'><td colspan='%d' class='reposection'>",  			      columns); -			html_txt(ctx.repo->group); +			html_txt(section);  			html("</td></tr>"); -			last_group = ctx.repo->group; +			last_section = section;  		}  		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);  		html("</td><td>");  		html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); @@ -154,6 +154,14 @@ int cgit_find_stats_period(const char *expr, struct cgit_period **period)  	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,  	struct cgit_period *period)  { @@ -21,6 +21,7 @@ struct cgit_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); | 
