summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorLars Hjemli2007-07-22 23:42:55 +0200
committerLars Hjemli2007-07-22 23:44:57 +0200
commit4e9107abfe8d3edff17826875b417bcf40dc7390 (patch)
tree96cfafa9b8838f34e1363ee3019eae64a6229b30
parent71ebcbe23ab548e5c0ad40aa8be5741654ed3201 (diff)
downloadcgit-4e9107abfe8d3edff17826875b417bcf40dc7390.tar
cgit-4e9107abfe8d3edff17826875b417bcf40dc7390.tar.gz
cgit-4e9107abfe8d3edff17826875b417bcf40dc7390.zip
Add ui-tag.c
This file implements the tag-command, i.e. printing of annotated tags. Signed-off-by: Lars Hjemli <hjemli@gmail.com> Signed-off-by: Lars Hjemli <hjemli@gmail.com>
-rw-r--r--Makefile2
-rw-r--r--cgit.c3
-rw-r--r--cgit.h5
-rw-r--r--shared.c2
-rw-r--r--ui-shared.c24
-rw-r--r--ui-summary.c33
-rw-r--r--ui-tag.c74
7 files changed, 111 insertions, 32 deletions
diff --git a/Makefile b/Makefile
index cea09f1..50d0011 100644
--- a/Makefile
+++ b/Makefile
@@ -23,7 +23,7 @@ VERSION:
EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto
OBJECTS = shared.o cache.o parsing.o html.o ui-shared.o ui-repolist.o \
ui-summary.o ui-log.o ui-tree.o ui-commit.o ui-diff.o \
- ui-snapshot.o ui-blob.o
+ ui-snapshot.o ui-blob.o ui-tag.o
CFLAGS += -Wall
diff --git a/cgit.c b/cgit.c
index 1281bfa..4b91829 100644
--- a/cgit.c
+++ b/cgit.c
@@ -101,6 +101,9 @@ static void cgit_print_repo_page(struct cacheitem *item)
case CMD_COMMIT:
cgit_print_commit(cgit_query_sha1);
break;
+ case CMD_TAG:
+ cgit_print_tag(cgit_query_sha1);
+ break;
case CMD_DIFF:
cgit_print_diff(cgit_query_sha1, cgit_query_sha2);
break;
diff --git a/cgit.h b/cgit.h
index 2ff5340..610a16d 100644
--- a/cgit.h
+++ b/cgit.h
@@ -27,7 +27,7 @@
#define CMD_TREE 4
#define CMD_BLOB 5
#define CMD_SNAPSHOT 6
-
+#define CMD_TAG 7
/*
* Dateformats used on misc. pages
@@ -212,6 +212,8 @@ extern void cgit_commit_link(char *name, char *title, char *class, char *head,
extern void cgit_diff_link(char *name, char *title, char *class, char *head,
char *new_rev, char *old_rev, char *path);
+extern void cgit_object_link(struct object *obj);
+
extern void cgit_print_error(char *msg);
extern void cgit_print_date(time_t secs, char *format);
extern void cgit_print_age(time_t t, time_t max_relative, char *format);
@@ -228,6 +230,7 @@ extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *
extern void cgit_print_blob(struct cacheitem *item, const char *hex, char *path);
extern void cgit_print_tree(const char *rev, char *path);
extern void cgit_print_commit(char *hex);
+extern void cgit_print_tag(char *revname);
extern void cgit_print_diff(const char *new_hex, const char *old_hex);
extern void cgit_print_snapshot(struct cacheitem *item, const char *hex,
const char *format, const char *prefix,
diff --git a/shared.c b/shared.c
index 1a5b866..06693b0 100644
--- a/shared.c
+++ b/shared.c
@@ -63,7 +63,7 @@ int htmlfd = 0;
int cgit_get_cmd_index(const char *cmd)
{
static char *cmds[] = {"log", "commit", "diff", "tree", "blob",
- "snapshot", NULL};
+ "snapshot", "tag", NULL};
int i;
for(i = 0; cmds[i]; i++)
diff --git a/ui-shared.c b/ui-shared.c
index d4376ce..fd71c12 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -218,6 +218,30 @@ void cgit_diff_link(char *name, char *title, char *class, char *head,
html("</a>");
}
+void cgit_object_link(struct object *obj)
+{
+ char *page, *arg, *url;
+
+ if (obj->type == OBJ_COMMIT) {
+ cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL,
+ cgit_query_head, sha1_to_hex(obj->sha1));
+ return;
+ } else if (obj->type == OBJ_TREE) {
+ page = "tree";
+ arg = "id";
+ } else {
+ page = "blob";
+ arg = "id";
+ }
+
+ url = cgit_pageurl(cgit_query_repo, page,
+ fmt("%s=%s", arg, sha1_to_hex(obj->sha1)));
+ html_link_open(url, NULL, NULL);
+ htmlf("%s %s", typename(obj->type),
+ sha1_to_hex(obj->sha1));
+ html_link_close();
+}
+
void cgit_print_date(time_t secs, char *format)
{
char buf[64];
diff --git a/ui-summary.c b/ui-summary.c
index b4bc6d8..de8a180 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -47,31 +47,6 @@ static int cgit_print_branch_cb(const char *refname, const unsigned char *sha1,
return 0;
}
-
-static void cgit_print_object_ref(struct object *obj)
-{
- char *page, *arg, *url;
-
- if (obj->type == OBJ_COMMIT) {
- cgit_commit_link(fmt("commit %s", sha1_to_hex(obj->sha1)), NULL, NULL,
- cgit_query_head, sha1_to_hex(obj->sha1));
- return;
- } else if (obj->type == OBJ_TREE) {
- page = "tree";
- arg = "id";
- } else {
- page = "view";
- arg = "id";
- }
-
- url = cgit_pageurl(cgit_query_repo, page,
- fmt("%s=%s", arg, sha1_to_hex(obj->sha1)));
- html_link_open(url, NULL, NULL);
- htmlf("%s %s", typename(obj->type),
- sha1_to_hex(obj->sha1));
- html_link_close();
-}
-
static void print_tag_header()
{
html("<tr class='nohover'><th class='left'>Tag</th>"
@@ -100,8 +75,8 @@ static int cgit_print_tag_cb(const char *refname, const unsigned char *sha1,
if (!header)
print_tag_header();
html("<tr><td>");
- url = cgit_pageurl(cgit_query_repo, "view",
- fmt("id=%s", sha1_to_hex(sha1)));
+ url = cgit_pageurl(cgit_query_repo, "tag",
+ fmt("id=%s", refname));
html_link_open(url, NULL, NULL);
html_txt(buf);
html_link_close();
@@ -112,7 +87,7 @@ static int cgit_print_tag_cb(const char *refname, const unsigned char *sha1,
if (info->tagger)
html(info->tagger);
html("</td><td>");
- cgit_print_object_ref(tag->tagged);
+ cgit_object_link(tag->tagged);
html("</td></tr>\n");
} else {
if (!header)
@@ -120,7 +95,7 @@ static int cgit_print_tag_cb(const char *refname, const unsigned char *sha1,
html("<tr><td>");
html_txt(buf);
html("</td><td colspan='2'/><td>");
- cgit_print_object_ref(obj);
+ cgit_object_link(obj);
html("</td></tr>\n");
}
return 0;
diff --git a/ui-tag.c b/ui-tag.c
new file mode 100644
index 0000000..a6989ff
--- /dev/null
+++ b/ui-tag.c
@@ -0,0 +1,74 @@
+/* ui-tag.c: display a tag
+ *
+ * Copyright (C) 2007 Lars Hjemli
+ *
+ * Licensed under GNU General Public License v2
+ * (see COPYING for full license text)
+ */
+
+#include "cgit.h"
+
+
+static void print_tag_content(char *buf)
+{
+ char *p;
+
+ if (!buf)
+ return;
+
+ html("<div class='commit-subject'>");
+ p = strchr(buf, '\n');
+ if (p)
+ *p = '\0';
+ html_txt(buf);
+ html("</div>");
+ if (p) {
+ html("<div class='commit-msg'>");
+ html_txt(++p);
+ html("</div>");
+ }
+}
+
+void cgit_print_tag(char *revname)
+{
+ unsigned char sha1[20];
+ struct object *obj;
+ struct tag *tag;
+ struct taginfo *info;
+
+ if (get_sha1(revname, sha1)) {
+ cgit_print_error(fmt("Bad tag reference: %s", revname));
+ return;
+ }
+ obj = parse_object(sha1);
+ if (!obj) {
+ cgit_print_error(fmt("Bad object id: %s", sha1_to_hex(sha1)));
+ return;
+ }
+ if (obj->type == OBJ_TAG) {
+ tag = lookup_tag(sha1);
+ if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) {
+ cgit_print_error(fmt("Bad tag object: %s", revname));
+ return;
+ }
+ html("<table class='commit-info'>\n");
+ htmlf("<tr><td>Tag name</td><td>%s (%s)</td></tr>\n",
+ revname, sha1_to_hex(sha1));
+ if (info->tagger_date > 0) {
+ html("<tr><td>Tag date</td><td>");
+ cgit_print_date(info->tagger_date, FMT_LONGDATE);
+ html("</td><tr>\n");
+ }
+ if (info->tagger) {
+ html("<tr><td>Tagged by</td><td>");
+ html_txt(info->tagger);
+ html("</td></tr>\n");
+ }
+ html("<tr><td>Tagged object</td><td>");
+ cgit_object_link(tag->tagged);
+ html("</td></tr>\n");
+ html("</table>\n");
+ print_tag_content(info->msg);
+ }
+ return;
+}
n585' href='#n585'>585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633
/* cgit.c: cgi for the git scm
 *
 * Copyright (C) 2006 Lars Hjemli
 *
 * Licensed under GNU General Public License v2
 *   (see COPYING for full license text)
 */

#include "cgit.h"
#include "cache.h"
#include "cmd.h"
#include "configfile.h"
#include "html.h"
#include "ui-shared.h"
#include "ui-stats.h"
#include "scan-tree.h"

const char *cgit_version = CGIT_VERSION;

void add_mimetype(const char *name, const char *value)
{
	struct string_list_item *item;

	item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes);
	item->util = xstrdup(value);
}

struct cgit_filter *new_filter(const char *cmd, int extra_args)
{
	struct cgit_filter *f;

	if (!cmd || !cmd[0])
		return NULL;

	f = xmalloc(sizeof(struct cgit_filter));
	f->cmd = xstrdup(cmd);
	f->argv = xmalloc((2 + extra_args) * sizeof(char *));
	f->argv[0] = f->cmd;
	f->argv[1] = NULL;
	return f;
}

static void process_cached_repolist(const char *path);

void config_cb(const char *name, const char *value)
{
	if (!strcmp(name, "root-title"))
		ctx.cfg.root_title = xstrdup(value);
	else if (!strcmp(name, "root-desc"))
		ctx.cfg.root_desc = xstrdup(value);
	else if (!strcmp(name, "root-readme"))
		ctx.cfg.root_readme = xstrdup(value);
	else if (!strcmp(name, "css"))
		ctx.cfg.css = xstrdup(value);
	else if (!strcmp(name, "favicon"))
		ctx.cfg.favicon = xstrdup(value);
	else if (!strcmp(name, "footer"))
		ctx.cfg.footer = xstrdup(value);
	else if (!strcmp(name, "head-include"))
		ctx.cfg.head_include = xstrdup(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, "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, "repo.group"))
		ctx.cfg.repo_group = xstrdup(value);
	else if (!strcmp(name, "repo.scan"))
		if (!ctx.cfg.nocache && ctx.cfg.cache_size)
			process_cached_repolist(value);
		else
			scan_tree(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);
}

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);
	}
}

char *xstrdupn(const char *str)
{
	return (str ? xstrdup(str) : NULL);
}

static void prepare_context(struct cgit_context *ctx)
{
	memset(ctx, 0, sizeof(ctx));
	ctx->cfg.agefile = "info/web/last-modified";
	ctx->cfg.nocache = 0;
	ctx->cfg.cache_size = 0;
	ctx->cfg.cache_dynamic_ttl = 5;
	ctx->cfg.cache_max_create_time = 5;
	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";
	ctx->cfg.local_time = 0;
	ctx->cfg.max_repo_count = 50;
	ctx->cfg.max_commit_count = 50;
	ctx->cfg.max_lock_attempts = 5;
	ctx->cfg.max_msg_len = 80;
	ctx->cfg.max_repodesc_len = 80;
	ctx->cfg.max_stats = 0;
	ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s";
	ctx->cfg.renamelimit = -1;
	ctx->cfg.robots = "index, nofollow";
	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.summary_branches = 10;
	ctx->cfg.summary_log = 10;
	ctx->cfg.summary_tags = 10;
	ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG"));
	ctx->env.http_host = xstrdupn(getenv("HTTP_HOST"));
	ctx->env.https = xstrdupn(getenv("HTTPS"));
	ctx->env.no_http = xstrdupn(getenv("NO_HTTP"));
	ctx->env.path_info = xstrdupn(getenv("PATH_INFO"));
	ctx->env.query_string = xstrdupn(getenv("QUERY_STRING"));
	ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD"));
	ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME"));
	ctx->env.server_name = xstrdupn(getenv("SERVER_NAME"));
	ctx->env.server_port = xstrdupn(getenv("SERVER_PORT"));
	ctx->page.mimetype = "text/html";
	ctx->page.charset = PAGE_ENCODING;
	ctx->page.filename = NULL;
	ctx->page.size = 0;
	ctx->page.modified = time(NULL);
	ctx->page.expires = ctx->page.modified;
	ctx->page.etag = NULL;
	memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list));
	if (ctx->env.script_name)
		ctx->cfg.script_name = ctx->env.script_name;
	if (ctx->env.query_string)
		ctx->qry.raw = ctx->env.query_string;
	if (!ctx->env.cgit_config)
		ctx->env.cgit_config = CGIT_CONFIG;
}

struct refmatch {
	char *req_ref;
	char *first_ref;
	int match;
};

int find_current_ref(const char *refname, const unsigned char *sha1,
		     int flags, void *cb_data)
{
	struct refmatch *info;

	info = (struct refmatch *)cb_data;
	if (!strcmp(refname, info->req_ref))
		info->match = 1;
	if (!info->first_ref)
		info->first_ref = xstrdup(refname);
	return info->match;
}

char *find_default_branch(struct cgit_repo *repo)
{
	struct refmatch info;
	char *ref;

	info.req_ref = repo->defbranch;
	info.first_ref = NULL;
	info.match = 0;
	for_each_branch_ref(find_current_ref, &info);
	if (info.match)
		ref = info.req_ref;
	else
		ref = info.first_ref;
	if (ref)
		ref = xstrdup(ref);
	return ref;
}

static int prepare_repo_cmd(struct cgit_context *ctx)
{
	char *tmp;
	unsigned char sha1[20];
	int nongit = 0;

	setenv("GIT_DIR", ctx->repo->path, 1);
	setup_git_directory_gently(&nongit);
	if (nongit) {
		ctx->page.title = fmt("%s - %s", ctx->cfg.root_title,
				      "config error");
		tmp = fmt("Not a git repository: '%s'", ctx->repo->path);
		ctx->repo = NULL;
		cgit_print_http_headers(ctx);
		cgit_print_docstart(ctx);
		cgit_print_pageheader(ctx);
		cgit_print_error(tmp);
		cgit_print_docend();
		return 1;
	}
	ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc);

	if (!ctx->qry.head) {
		ctx->qry.nohead = 1;
		ctx->qry.head = find_default_branch(ctx->repo);
		ctx->repo->defbranch = ctx->qry.head;
	}

	if (!ctx->qry.head) {
		cgit_print_http_headers(ctx);
		cgit_print_docstart(ctx);
		cgit_print_pageheader(ctx);
		cgit_print_error("Repository seems to be empty");
		cgit_print_docend();
		return 1;
	}

	if (get_sha1(ctx->qry.head, sha1)) {
		tmp = xstrdup(ctx->qry.head);
		ctx->qry.head = ctx->repo->defbranch;
		ctx->page.status = 404;
		ctx->page.statusmsg = "not found";
		cgit_print_http_headers(ctx);
		cgit_print_docstart(ctx);
		cgit_print_pageheader(ctx);
		cgit_print_error(fmt("Invalid branch: %s", tmp));
		cgit_print_docend();
		return 1;
	}
	return 0;
}

static void process_request(void *cbdata)
{
	struct cgit_context *ctx = cbdata;
	struct cgit_cmd *cmd;

	cmd = cgit_get_cmd(ctx);
	if (!cmd) {
		ctx->page.title = "cgit error";
		cgit_print_http_headers(ctx);
		cgit_print_docstart(ctx);
		cgit_print_pageheader(ctx);
		cgit_print_error("Invalid request");
		cgit_print_docend();
		return;
	}

	if (cmd->want_repo && !ctx->repo) {
		cgit_print_http_headers(ctx);
		cgit_print_docstart(ctx);
		cgit_print_pageheader(ctx);
		cgit_print_error(fmt("No repository selected"));
		cgit_print_docend();
		return;
	}

	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);
	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);
		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)) {
			scan++;
			scan_tree(argv[i] + 12);
		}
	}
	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
			ctx.qry.raw = xstrdup(ctx.qry.url);
		cgit_parse_url(ctx.qry.url);
	}

	ttl = calc_ttl();
	ctx.page.expires += ttl*60;
	if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
		ctx.cfg.nocache = 1;
	if (ctx.cfg.nocache)
		ctx.cfg.cache_size = 0;
	err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
			    ctx.qry.raw, ttl, process_request, &ctx);
	if (err)
		cgit_print_error(fmt("Error processing page: %s (%d)",
				     strerror(err), err));
	return err;
}