summaryrefslogtreecommitdiffabout
Side-by-side diff
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
@@ -107,129 +107,129 @@ void config_cb(const char *name, const char *value)
else if (!strcmp(name, "header"))
ctx.cfg.header = xstrdup(value);
else if (!strcmp(name, "logo"))
ctx.cfg.logo = xstrdup(value);
else if (!strcmp(name, "index-header"))
ctx.cfg.index_header = xstrdup(value);
else if (!strcmp(name, "index-info"))
ctx.cfg.index_info = xstrdup(value);
else if (!strcmp(name, "logo-link"))
ctx.cfg.logo_link = xstrdup(value);
else if (!strcmp(name, "module-link"))
ctx.cfg.module_link = xstrdup(value);
else if (!strcmp(name, "virtual-root")) {
ctx.cfg.virtual_root = trim_end(value, '/');
if (!ctx.cfg.virtual_root && (!strcmp(value, "/")))
ctx.cfg.virtual_root = "";
} else if (!strcmp(name, "nocache"))
ctx.cfg.nocache = atoi(value);
else if (!strcmp(name, "noplainemail"))
ctx.cfg.noplainemail = atoi(value);
else if (!strcmp(name, "noheader"))
ctx.cfg.noheader = atoi(value);
else if (!strcmp(name, "snapshots"))
ctx.cfg.snapshots = cgit_parse_snapshots_mask(value);
else if (!strcmp(name, "enable-index-links"))
ctx.cfg.enable_index_links = atoi(value);
else if (!strcmp(name, "enable-log-filecount"))
ctx.cfg.enable_log_filecount = atoi(value);
else if (!strcmp(name, "enable-log-linecount"))
ctx.cfg.enable_log_linecount = atoi(value);
else if (!strcmp(name, "max-stats"))
ctx.cfg.max_stats = cgit_find_stats_period(value, NULL);
else if (!strcmp(name, "cache-size"))
ctx.cfg.cache_size = atoi(value);
else if (!strcmp(name, "cache-root"))
ctx.cfg.cache_root = xstrdup(value);
else if (!strcmp(name, "cache-root-ttl"))
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"))
ctx.cfg.cache_dynamic_ttl = atoi(value);
else if (!strcmp(name, "about-filter"))
ctx.cfg.about_filter = new_filter(value, 0);
else if (!strcmp(name, "commit-filter"))
ctx.cfg.commit_filter = new_filter(value, 0);
else if (!strcmp(name, "embedded"))
ctx.cfg.embedded = atoi(value);
else if (!strcmp(name, "max-message-length"))
ctx.cfg.max_msg_len = atoi(value);
else if (!strcmp(name, "max-repodesc-length"))
ctx.cfg.max_repodesc_len = atoi(value);
else if (!strcmp(name, "max-repo-count"))
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);
+ 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"))
ctx.cfg.summary_log = atoi(value);
else if (!strcmp(name, "summary-branches"))
ctx.cfg.summary_branches = atoi(value);
else if (!strcmp(name, "summary-tags"))
ctx.cfg.summary_tags = atoi(value);
else if (!strcmp(name, "agefile"))
ctx.cfg.agefile = xstrdup(value);
else if (!strcmp(name, "renamelimit"))
ctx.cfg.renamelimit = atoi(value);
else if (!strcmp(name, "robots"))
ctx.cfg.robots = xstrdup(value);
else if (!strcmp(name, "clone-prefix"))
ctx.cfg.clone_prefix = xstrdup(value);
else if (!strcmp(name, "local-time"))
ctx.cfg.local_time = atoi(value);
else if (!prefixcmp(name, "mimetype."))
add_mimetype(name + 9, value);
else if (!strcmp(name, "include"))
parse_configfile(value, config_cb);
}
static void querystring_cb(const char *name, const char *value)
{
if (!value)
value = "";
if (!strcmp(name,"r")) {
ctx.qry.repo = xstrdup(value);
ctx.repo = cgit_get_repoinfo(value);
} else if (!strcmp(name, "p")) {
ctx.qry.page = xstrdup(value);
} else if (!strcmp(name, "url")) {
ctx.qry.url = xstrdup(value);
cgit_parse_url(value);
} else if (!strcmp(name, "qt")) {
ctx.qry.grep = xstrdup(value);
} else if (!strcmp(name, "q")) {
ctx.qry.search = xstrdup(value);
} else if (!strcmp(name, "h")) {
ctx.qry.head = xstrdup(value);
ctx.qry.has_symref = 1;
} else if (!strcmp(name, "id")) {
ctx.qry.sha1 = xstrdup(value);
ctx.qry.has_sha1 = 1;
} else if (!strcmp(name, "id2")) {
ctx.qry.sha2 = xstrdup(value);
ctx.qry.has_sha1 = 1;
} else if (!strcmp(name, "ofs")) {
ctx.qry.ofs = atoi(value);
} else if (!strcmp(name, "path")) {
ctx.qry.path = trim_end(value, '/');
} else if (!strcmp(name, "name")) {
ctx.qry.name = xstrdup(value);
} else if (!strcmp(name, "mimetype")) {
ctx.qry.mimetype = xstrdup(value);
} else if (!strcmp(name, "s")){
ctx.qry.sort = xstrdup(value);
} else if (!strcmp(name, "showmsg")) {
ctx.qry.showmsg = atoi(value);
} else if (!strcmp(name, "period")) {
ctx.qry.period = xstrdup(value);
@@ -415,212 +415,212 @@ static void process_request(void *cbdata)
if (ctx->repo && prepare_repo_cmd(ctx))
return;
if (cmd->want_layout) {
cgit_print_http_headers(ctx);
cgit_print_docstart(ctx);
cgit_print_pageheader(ctx);
}
cmd->fn(ctx);
if (cmd->want_layout)
cgit_print_docend();
}
int cmp_repos(const void *a, const void *b)
{
const struct cgit_repo *ra = a, *rb = b;
return strcmp(ra->url, rb->url);
}
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)
fprintf(f, "repo.desc=%s\n", repo->desc);
if (repo->readme)
fprintf(f, "repo.readme=%s\n", repo->readme);
fprintf(f, "\n");
}
void print_repolist(FILE *f, struct cgit_repolist *list, int start)
{
int 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);
+ 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);
+ 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)
{
int i;
int scan = 0;
for (i = 1; i < argc; i++) {
if (!strncmp(argv[i], "--cache=", 8)) {
ctx.cfg.cache_root = xstrdup(argv[i]+8);
}
if (!strcmp(argv[i], "--nocache")) {
ctx.cfg.nocache = 1;
}
if (!strcmp(argv[i], "--nohttp")) {
ctx.env.no_http = "1";
}
if (!strncmp(argv[i], "--query=", 8)) {
ctx.qry.raw = xstrdup(argv[i]+8);
}
if (!strncmp(argv[i], "--repo=", 7)) {
ctx.qry.repo = xstrdup(argv[i]+7);
}
if (!strncmp(argv[i], "--page=", 7)) {
ctx.qry.page = xstrdup(argv[i]+7);
}
if (!strncmp(argv[i], "--head=", 7)) {
ctx.qry.head = xstrdup(argv[i]+7);
ctx.qry.has_symref = 1;
}
if (!strncmp(argv[i], "--sha1=", 7)) {
ctx.qry.sha1 = xstrdup(argv[i]+7);
ctx.qry.has_sha1 = 1;
}
if (!strncmp(argv[i], "--ofs=", 6)) {
ctx.qry.ofs = atoi(argv[i]+6);
}
if (!strncmp(argv[i], "--scan-tree=", 12) ||
!strncmp(argv[i], "--scan-path=", 12)) {
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(stdout, &cgit_repolist, 0);
exit(0);
}
}
static int calc_ttl()
{
if (!ctx.repo)
return ctx.cfg.cache_root_ttl;
if (!ctx.qry.page)
return ctx.cfg.cache_repo_ttl;
if (ctx.qry.has_symref)
return ctx.cfg.cache_dynamic_ttl;
if (ctx.qry.has_sha1)
return ctx.cfg.cache_static_ttl;
return ctx.cfg.cache_repo_ttl;
}
int main(int argc, const char **argv)
{
const char *path;
char *qry;
int err, ttl;
prepare_context(&ctx);
cgit_repolist.length = 0;
cgit_repolist.count = 0;
cgit_repolist.repos = NULL;
cgit_parse_args(argc, argv);
parse_configfile(ctx.env.cgit_config, config_cb);
ctx.repo = NULL;
http_parse_querystring(ctx.qry.raw, querystring_cb);
/* If virtual-root isn't specified in cgitrc, lets pretend
* that virtual-root equals SCRIPT_NAME.
*/
if (!ctx.cfg.virtual_root)
ctx.cfg.virtual_root = ctx.cfg.script_name;
/* If no url parameter is specified on the querystring, lets
* use PATH_INFO as url. This allows cgit to work with virtual
* urls without the need for rewriterules in the webserver (as
* long as PATH_INFO is included in the cache lookup key).
*/
path = ctx.env.path_info;
if (!ctx.qry.url && path) {
if (path[0] == '/')
path++;
ctx.qry.url = xstrdup(path);
if (ctx.qry.raw) {
qry = ctx.qry.raw;
ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry));
free(qry);
} else
diff --git a/cgit.h b/cgit.h
index fc7c7d5..3359be9 100644
--- a/cgit.h
+++ b/cgit.h
@@ -18,128 +18,131 @@
#include <string-list.h>
#include <xdiff-interface.h>
#include <xdiff/xdiff.h>
#include <utf8.h>
/*
* Dateformats used on misc. pages
*/
#define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)"
#define FMT_SHORTDATE "%Y-%m-%d"
#define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ"
/*
* Limits used for relative dates
*/
#define TM_MIN 60
#define TM_HOUR (TM_MIN * 60)
#define TM_DAY (TM_HOUR * 24)
#define TM_WEEK (TM_DAY * 7)
#define TM_YEAR (TM_DAY * 365)
#define TM_MONTH (TM_YEAR / 12.0)
/*
* Default encoding
*/
#define PAGE_ENCODING "UTF-8"
typedef void (*configfn)(const char *name, const char *value);
typedef void (*filepair_fn)(struct diff_filepair *pair);
typedef void (*linediff_fn)(char *line, int len);
struct cgit_filter {
char *cmd;
char **argv;
int old_stdout;
int pipe_fh[2];
int pid;
int exitstatus;
};
struct cgit_repo {
char *url;
char *name;
char *path;
char *desc;
char *owner;
char *defbranch;
char *module_link;
char *readme;
char *section;
char *clone_url;
int snapshots;
int enable_log_filecount;
int enable_log_linecount;
int max_stats;
time_t mtime;
struct cgit_filter *about_filter;
struct cgit_filter *commit_filter;
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;
struct cgit_repo *repos;
};
struct commitinfo {
struct commit *commit;
char *author;
char *author_email;
unsigned long author_date;
char *committer;
char *committer_email;
unsigned long committer_date;
char *subject;
char *msg;
char *msg_encoding;
};
struct taginfo {
char *tagger;
char *tagger_email;
unsigned long tagger_date;
char *msg;
};
struct refinfo {
const char *refname;
struct object *object;
union {
struct taginfo *tag;
struct commitinfo *commit;
};
};
struct reflist {
struct refinfo **refs;
int alloc;
int count;
};
struct cgit_query {
int has_symref;
int has_sha1;
char *raw;
char *repo;
char *page;
char *search;
char *grep;
char *head;
char *sha1;
char *sha2;
char *path;
char *name;
char *mimetype;
char *url;
char *period;
int ofs;
int nohead;
char *sort;
int showmsg;
};
struct cgit_config {
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index e99c9f7..df494aa 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -265,128 +265,137 @@ virtual-root::
REPOSITORY SETTINGS
-------------------
repo.about-filter::
Override the default about-filter. Default value: <about-filter>.
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>.
repo.defbranch::
The name of the default branch for this repository. If no such branch
exists in the repository, the first branch name (when sorted) is used
as default instead. Default value: "master".
repo.desc::
The value to show as repository description. Default value: none.
repo.enable-log-filecount::
A flag which can be used to disable the global setting
`enable-log-filecount'. Default value: none.
repo.enable-log-linecount::
A flag which can be used to disable the global setting
`enable-log-linecount'. Default value: none.
repo.max-stats::
Override the default maximum statistics period. Valid values are equal
to the values specified for the global "max-stats" setting. Default
value: none.
repo.name::
The value to show as repository name. Default value: <repo.url>.
repo.owner::
A value used to identify the owner of the repository. Default value:
none.
repo.path::
An absolute path to the repository directory. For non-bare repositories
this is the .git-directory. Default value: none.
repo.readme::
A path (relative to <repo.path>) which specifies a file to include
verbatim as the "About" page for this repo. Default value: none.
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 for this repository. Default value: none.
repo.source-filter::
Override the default source-filter. Default value: <source-filter>.
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'. Also, in a repo-specific config
+file, the 'repo.' prefix is dropped from the config option names.
+
+
EXAMPLE CGITRC FILE
-------------------
....
# Enable caching of up to 1000 output entriess
cache-size=1000
# Specify some default clone prefixes
clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git
# Specify the css url
css=/css/cgit.css
# Show extra links for each repository on the index page
enable-index-links=1
# Show number of affected files per commit on the log pages
enable-log-filecount=1
# Show number of added/removed lines per commit on the log pages
enable-log-linecount=1
# Add a cgit favicon
favicon=/favicon.ico
# Use a custom logo
logo=/img/mylogo.png
# Enable statistics per week, month and quarter
max-stats=quarter
# Set the title and heading of the repository index page
root-title=foobar.com git repositories
# Set a subheading for the repository index page
root-desc=tracking the foobar development
# Include some more info about foobar.com on the index page
root-readme=/var/www/htdocs/about.html
# Allow download of tar.gz, tar.bz2 and zip-files
snapshots=tar.gz tar.bz2 zip
##
## List of common mimetypes
##
mimetype.git=image/git
mimetype.html=text/html
mimetype.jpg=image/jpeg
mimetype.jpeg=image/jpeg
mimetype.pdf=application/pdf
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 @@
#include "cgit.h"
+#include "configfile.h"
#include "html.h"
#define MAX_PATH 4096
/* return 1 if path contains a objects/ directory and a HEAD file */
static int is_git_dir(const char *path)
{
struct stat st;
static char buf[MAX_PATH];
if (snprintf(buf, MAX_PATH, "%s/objects", path) >= MAX_PATH) {
fprintf(stderr, "Insanely long path: %s\n", path);
return 0;
}
if (stat(buf, &st)) {
if (errno != ENOENT)
fprintf(stderr, "Error checking path %s: %s (%d)\n",
path, strerror(errno), errno);
return 0;
}
if (!S_ISDIR(st.st_mode))
return 0;
sprintf(buf, "%s/HEAD", path);
if (stat(buf, &st)) {
if (errno != ENOENT)
fprintf(stderr, "Error checking path %s: %s (%d)\n",
path, strerror(errno), errno);
return 0;
}
if (!S_ISREG(st.st_mode))
return 0;
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;
size_t size;
if (stat(path, &st)) {
fprintf(stderr, "Error accessing %s: %s (%d)\n",
path, strerror(errno), errno);
return;
}
if ((pwd = getpwuid(st.st_uid)) == NULL) {
fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
path, strerror(errno), errno);
return;
}
if (base == path)
p = fmt("%s", path);
else
p = fmt("%s", path + strlen(base) + 1);
if (!strcmp(p + strlen(p) - 5, "/.git"))
p[strlen(p) - 5] = '\0';
repo = cgit_add_repo(xstrdup(p));
repo->name = repo->url;
repo->path = xstrdup(path);
p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL;
if (p)
*p = '\0';
repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : "");
p = fmt("%s/description", path);
if (!stat(p, &st))
readfile(p, &repo->desc, &size);
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;
char *buf;
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));
+ add_repo(base, fmt("%s/.git", path), fn);
return;
}
dir = opendir(path);
if (!dir) {
fprintf(stderr, "Error opening directory %s: %s (%d)\n",
path, strerror(errno), errno);
return;
}
while((ent = readdir(dir)) != NULL) {
if (ent->d_name[0] == '.') {
if (ent->d_name[1] == '\0')
continue;
if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
continue;
}
buf = malloc(strlen(path) + strlen(ent->d_name) + 2);
if (!buf) {
fprintf(stderr, "Alloc error on %s: %s (%d)\n",
path, strerror(errno), errno);
exit(1);
}
sprintf(buf, "%s/%s", path, ent->d_name);
if (stat(buf, &st)) {
fprintf(stderr, "Error checking path %s: %s (%d)\n",
buf, strerror(errno), errno);
free(buf);
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);