summaryrefslogtreecommitdiffabout
Side-by-side diff
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--.gitignore5
-rw-r--r--Makefile21
-rw-r--r--cache.h2
-rw-r--r--cgit-doc.css3
-rw-r--r--cgit.c345
-rw-r--r--cgit.css10
-rw-r--r--cgit.h52
-rw-r--r--cgitrc.5.txt244
-rw-r--r--cmd.c2
-rwxr-xr-xfilters/commit-links.sh12
-rwxr-xr-xfilters/syntax-highlighting.sh39
m---------git0
-rw-r--r--scan-tree.c45
-rw-r--r--scan-tree.h2
-rw-r--r--shared.c62
-rw-r--r--ui-atom.c8
-rw-r--r--ui-blob.c8
-rw-r--r--ui-commit.c22
-rw-r--r--ui-log.c6
-rw-r--r--ui-patch.c6
-rw-r--r--ui-plain.c18
-rw-r--r--ui-refs.c19
-rw-r--r--ui-repolist.c65
-rw-r--r--ui-shared.c81
-rw-r--r--ui-shared.h1
-rw-r--r--ui-snapshot.c35
-rw-r--r--ui-stats.c8
-rw-r--r--ui-stats.h1
-rw-r--r--ui-summary.c28
-rw-r--r--ui-summary.h2
-rw-r--r--ui-tag.c2
-rw-r--r--ui-tree.c55
32 files changed, 942 insertions, 267 deletions
diff --git a/.gitignore b/.gitignore
index 1e016e5..487728b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,2 +4,7 @@ cgit.conf
VERSION
+cgitrc.5
+cgitrc.5.fo
+cgitrc.5.html
+cgitrc.5.pdf
+cgitrc.5.xml
*.o
diff --git a/Makefile b/Makefile
index 44138ea..da2518b 100644
--- a/Makefile
+++ b/Makefile
@@ -7,3 +7,3 @@ CACHE_ROOT = /var/cache/cgit
SHA1_HEADER = <openssl/sha.h>
-GIT_VER = 1.6.1.1
+GIT_VER = 1.6.4.3
GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2
@@ -102,3 +102,4 @@ endif
-.PHONY: all libgit test install uninstall clean force-version get-git
+.PHONY: all libgit test install uninstall clean force-version get-git \
+ doc man-doc html-doc clean-doc
@@ -151,5 +152,19 @@ uninstall:
-clean:
+doc: man-doc html-doc pdf-doc
+
+man-doc: cgitrc.5.txt
+ a2x -f manpage cgitrc.5.txt
+
+html-doc: cgitrc.5.txt
+ a2x -f xhtml --stylesheet=cgit-doc.css cgitrc.5.txt
+
+pdf-doc: cgitrc.5.txt
+ a2x -f pdf cgitrc.5.txt
+
+clean: clean-doc
rm -f cgit VERSION *.o *.d
+clean-doc:
+ rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo
+
get-git:
diff --git a/cache.h b/cache.h
index 66cc41f..ac9276b 100644
--- a/cache.h
+++ b/cache.h
@@ -34,2 +34,4 @@ extern void cache_log(const char *format, ...);
+extern unsigned long hash_str(const char *str);
+
#endif /* CGIT_CACHE_H */
diff --git a/cgit-doc.css b/cgit-doc.css
new file mode 100644
index 0000000..5a399b6
--- a/dev/null
+++ b/cgit-doc.css
@@ -0,0 +1,3 @@
+div.variablelist dt {
+ margin-top: 1em;
+}
diff --git a/cgit.c b/cgit.c
index 5301840..bd37788 100644
--- a/cgit.c
+++ b/cgit.c
@@ -19,5 +19,77 @@ 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 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);
@@ -33,2 +105,4 @@ void config_cb(const char *name, const char *value)
ctx.cfg.footer = xstrdup(value);
+ else if (!strcmp(name, "head-include"))
+ ctx.cfg.head_include = xstrdup(value);
else if (!strcmp(name, "header"))
@@ -51,4 +125,10 @@ void config_cb(const char *name, const char *value)
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-filter-overrides"))
+ ctx.cfg.enable_filter_overrides = atoi(value);
else if (!strcmp(name, "enable-index-links"))
@@ -59,2 +139,4 @@ void config_cb(const char *name, const char *value)
ctx.cfg.enable_log_linecount = atoi(value);
+ else if (!strcmp(name, "enable-tree-linenumbers"))
+ ctx.cfg.enable_tree_linenumbers = atoi(value);
else if (!strcmp(name, "max-stats"))
@@ -69,2 +151,4 @@ void config_cb(const char *name, const char *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"))
@@ -73,2 +157,8 @@ void config_cb(const char *name, const char *value)
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"))
@@ -81,2 +171,9 @@ void config_cb(const char *name, const char *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"))
+ ctx.cfg.source_filter = new_filter(value, 1);
else if (!strcmp(name, "summary-log"))
@@ -97,34 +194,5 @@ void config_cb(const char *name, const char *value)
ctx.cfg.local_time = atoi(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.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 (!prefixcmp(name, "mimetype."))
+ add_mimetype(name + 9, value);
+ else if (!strcmp(name, "include"))
parse_configfile(value, config_cb);
@@ -175,2 +243,7 @@ static void querystring_cb(const char *name, const char *value)
+char *xstrdupn(const char *str)
+{
+ return (str ? xstrdup(str) : NULL);
+}
+
static void prepare_context(struct cgit_context *ctx)
@@ -186,6 +259,8 @@ static void prepare_context(struct cgit_context *ctx)
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 = "/git-logo.png";
+ ctx->cfg.logo = "/cgit.png";
ctx->cfg.local_time = 0;
+ ctx->cfg.enable_tree_linenumbers = 1;
ctx->cfg.max_repo_count = 50;
@@ -202,2 +277,3 @@ static void prepare_context(struct cgit_context *ctx)
ctx->cfg.script_name = CGIT_SCRIPT_NAME;
+ ctx->cfg.section = "";
ctx->cfg.summary_branches = 10;
@@ -205,2 +281,12 @@ static void prepare_context(struct cgit_context *ctx)
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";
@@ -211,2 +297,10 @@ static void prepare_context(struct cgit_context *ctx)
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;
}
@@ -290,2 +384,4 @@ static int prepare_repo_cmd(struct cgit_context *ctx)
ctx->qry.head = ctx->repo->defbranch;
+ ctx->page.status = 404;
+ ctx->page.statusmsg = "not found";
cgit_print_http_headers(ctx);
@@ -346,17 +442,75 @@ int cmp_repos(const void *a, const void *b)
-void print_repo(struct cgit_repo *repo)
+char *build_snapshot_setting(int bitmap)
{
- printf("repo.url=%s\n", repo->url);
- printf("repo.name=%s\n", repo->name);
- printf("repo.path=%s\n", repo->path);
+ 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)
+{
+ 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)
{
@@ -364,6 +518,71 @@ void print_repolist(struct cgit_repolist *list)
- 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));
+}
@@ -381,2 +600,5 @@ static void cgit_parse_args(int argc, const char **argv)
}
+ if (!strcmp(argv[i], "--nohttp")) {
+ ctx.env.no_http = "1";
+ }
if (!strncmp(argv[i], "--query=", 8)) {
@@ -401,5 +623,16 @@ static void cgit_parse_args(int argc, const char **argv)
}
- 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);
}
@@ -409,3 +642,3 @@ static void cgit_parse_args(int argc, const char **argv)
sizeof(struct cgit_repo), cmp_repos);
- print_repolist(&cgit_repolist);
+ print_repolist(stdout, &cgit_repolist, 0);
exit(0);
@@ -433,3 +666,2 @@ int main(int argc, const char **argv)
{
- const char *cgit_config_env = getenv("CGIT_CONFIG");
const char *path;
@@ -443,9 +675,4 @@ int main(int argc, const char **argv)
- if (getenv("SCRIPT_NAME"))
- ctx.cfg.script_name = xstrdup(getenv("SCRIPT_NAME"));
- if (getenv("QUERY_STRING"))
- ctx.qry.raw = xstrdup(getenv("QUERY_STRING"));
cgit_parse_args(argc, argv);
- parse_configfile(cgit_config_env ? cgit_config_env : CGIT_CONFIG,
- config_cb);
+ parse_configfile(ctx.env.cgit_config, config_cb);
ctx.repo = NULL;
@@ -464,3 +691,3 @@ int main(int argc, const char **argv)
*/
- path = getenv("PATH_INFO");
+ path = ctx.env.path_info;
if (!ctx.qry.url && path) {
@@ -474,3 +701,3 @@ int main(int argc, const char **argv)
} else
- ctx.qry.raw = ctx.qry.url;
+ ctx.qry.raw = xstrdup(ctx.qry.url);
cgit_parse_url(ctx.qry.url);
@@ -480,2 +707,4 @@ int main(int argc, const char **argv)
ctx.page.expires += ttl*60;
+ if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))
+ ctx.cfg.nocache = 1;
if (ctx.cfg.nocache)
diff --git a/cgit.css b/cgit.css
index adfc8ae..c47ebc9 100644
--- a/cgit.css
+++ b/cgit.css
@@ -157,3 +157,3 @@ table.list td.logmsg {
white-space: pre;
- padding: 1em 0em 2em 0em;
+ padding: 1em 0.5em 2em 0.5em;
}
@@ -239,3 +239,3 @@ table.blob {
table.blob td.lines {
- margin: 0; padding: 0;
+ margin: 0; padding: 0 0 0 0.5em;
vertical-align: top;
@@ -245,6 +245,6 @@ table.blob td.lines {
table.blob td.linenumbers {
- margin: 0; padding: 0;
+ margin: 0; padding: 0 0.5em 0 0.5em;
vertical-align: top;
+ text-align: right;
border-right: 1px solid gray;
- background-color: #eee;
}
@@ -431,3 +431,3 @@ table.diff td div.del {
-table.list td.repogroup {
+table.list td.reposection {
font-style: italic;
diff --git a/cgit.h b/cgit.h
index 5f7af51..6c6c460 100644
--- a/cgit.h
+++ b/cgit.h
@@ -17,2 +17,3 @@
#include <archive.h>
+#include <string-list.h>
#include <xdiff-interface.h>
@@ -50,2 +51,11 @@ 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 {
@@ -57,5 +67,5 @@ struct cgit_repo {
char *defbranch;
- char *group;
char *module_link;
char *readme;
+ char *section;
char *clone_url;
@@ -66,4 +76,10 @@ struct cgit_repo {
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 {
@@ -138,2 +154,3 @@ struct cgit_config {
char *footer;
+ char *head_include;
char *header;
@@ -144,3 +161,2 @@ struct cgit_config {
char *module_link;
- char *repo_group;
char *robots;
@@ -150,2 +166,3 @@ struct cgit_config {
char *script_name;
+ char *section;
char *virtual_root;
@@ -156,3 +173,6 @@ struct cgit_config {
int cache_root_ttl;
+ int cache_scanrc_ttl;
int cache_static_ttl;
+ int embedded;
+ int enable_filter_overrides;
int enable_index_links;
@@ -160,2 +180,3 @@ struct cgit_config {
int enable_log_linecount;
+ int enable_tree_linenumbers;
int local_time;
@@ -168,2 +189,4 @@ struct cgit_config {
int nocache;
+ int noplainemail;
+ int noheader;
int renamelimit;
@@ -173,2 +196,6 @@ struct cgit_config {
int summary_tags;
+ struct string_list mimetypes;
+ struct cgit_filter *about_filter;
+ struct cgit_filter *commit_filter;
+ struct cgit_filter *source_filter;
};
@@ -182,3 +209,19 @@ struct cgit_page {
char *filename;
+ char *etag;
char *title;
+ int status;
+ char *statusmsg;
+};
+
+struct cgit_environment {
+ char *cgit_config;
+ char *http_host;
+ char *https;
+ char *no_http;
+ char *path_info;
+ char *query_string;
+ char *request_method;
+ char *script_name;
+ char *server_name;
+ char *server_port;
};
@@ -186,2 +229,3 @@ struct cgit_page {
struct cgit_context {
+ struct cgit_environment env;
struct cgit_query qry;
@@ -244,2 +288,6 @@ extern int cgit_parse_snapshots_mask(const char *str);
+extern int cgit_open_filter(struct cgit_filter *filter);
+extern int cgit_close_filter(struct cgit_filter *filter);
+
+extern int readfile(const char *path, char **buf, size_t *size);
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index fd299ae..4dc383d 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -1,3 +1,3 @@
-CGITRC
-======
+CGITRC(5)
+========
@@ -6,7 +6,7 @@ NAME
----
- cgitrc - runtime configuration for cgit
+cgitrc - runtime configuration for cgit
-DESCRIPTION
------------
+SYNOPSIS
+--------
Cgitrc contains all runtime settings for cgit, including the list of git
@@ -16,5 +16,19 @@ lines, and lines starting with '#', are ignored.
+LOCATION
+--------
+The default location of cgitrc, defined at compile time, is /etc/cgitrc. At
+runtime, cgit will consult the environment variable CGIT_CONFIG and, if
+defined, use its value instead.
+
+
GLOBAL SETTINGS
---------------
-agefile
+about-filter::
+ Specifies a command which will be invoked to format the content of
+ about pages (both top-level and for each repository). The command will
+ get the content of the about-file on its STDIN, and the STDOUT from the
+ command will be included verbatim on the about page. Default value:
+ none.
+
+agefile::
Specifies a path, relative to each repository path, which can be used
@@ -25,3 +39,3 @@ agefile
-cache-root
+cache-root::
Path used to store the cgit cache entries. Default value:
@@ -29,3 +43,3 @@ cache-root
-cache-dynamic-ttl
+cache-dynamic-ttl::
Number which specifies the time-to-live, in minutes, for the cached
@@ -34,3 +48,3 @@ cache-dynamic-ttl
-cache-repo-ttl
+cache-repo-ttl::
Number which specifies the time-to-live, in minutes, for the cached
@@ -38,3 +52,3 @@ cache-repo-ttl
-cache-root-ttl
+cache-root-ttl::
Number which specifies the time-to-live, in minutes, for the cached
@@ -42,3 +56,7 @@ cache-root-ttl
-cache-size
+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"
@@ -46,3 +64,3 @@ cache-size
-cache-static-ttl
+cache-static-ttl::
Number which specifies the time-to-live, in minutes, for the cached
@@ -51,3 +69,3 @@ cache-static-ttl
-clone-prefix
+clone-prefix::
Space-separated list of common prefixes which, when combined with a
@@ -57,3 +75,9 @@ clone-prefix
-css
+commit-filter::
+ Specifies a command which will be invoked to format commit messages.
+ The command will get the message on its STDIN, and the STDOUT from the
+ command will be included verbatim as the commit message, i.e. this can
+ be used to implement bugtracker integration. Default value: none.
+
+css::
Url which specifies the css document to include in all cgit pages.
@@ -61,3 +85,12 @@ css
-enable-index-links
+embedded::
+ Flag which, when set to "1", will make cgit generate a html fragment
+ 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
@@ -66,3 +99,3 @@ enable-index-links
-enable-log-filecount
+enable-log-filecount::
Flag which, when set to "1", will make cgit print the number of
@@ -71,3 +104,3 @@ enable-log-filecount
-enable-log-linecount
+enable-log-linecount::
Flag which, when set to "1", will make cgit print the number of added
@@ -76,3 +109,7 @@ enable-log-linecount
-favicon
+enable-tree-linenumbers::
+ Flag which, when set to "1", will make cgit generate linenumber links
+ for plaintext blobs printed in the tree view. Default value: "1".
+
+favicon::
Url used as link to a shortcut icon for cgit. If specified, it is
@@ -81,3 +118,3 @@ favicon
-footer
+footer::
The content of the file specified with this option will be included
@@ -86,3 +123,7 @@ footer
-header
+head-include::
+ The content of the file specified with this option will be included
+ verbatim in the html HEAD section on all pages. Default value: none.
+
+header::
The content of the file specified with this option will be included
@@ -90,3 +131,3 @@ header
-include
+include::
Name of a configfile to include before the rest of the current config-
@@ -94,3 +135,3 @@ include
-index-header
+index-header::
The content of the file specified with this option will be included
@@ -100,3 +141,3 @@ index-header
-index-info
+index-info::
The content of the file specified with this option will be included
@@ -106,3 +147,3 @@ index-info
-local-time
+local-time::
Flag which, if set to "1", makes cgit print commit and tag times in the
@@ -110,7 +151,7 @@ local-time
-logo
+logo::
Url which specifies the source of an image which will be used as a logo
- on all cgit pages.
+ on all cgit pages. Default value: "/cgit.png".
-logo-link
+logo-link::
Url loaded when clicking on the cgit logo image. If unspecified the
@@ -119,3 +160,3 @@ logo-link
-max-commit-count
+max-commit-count::
Specifies the number of entries to list per page in "log" view. Default
@@ -123,3 +164,3 @@ max-commit-count
-max-message-length
+max-message-length::
Specifies the maximum number of commit message characters to display in
@@ -127,3 +168,3 @@ max-message-length
-max-repo-count
+max-repo-count::
Specifies the number of entries to list per page on the repository
@@ -131,3 +172,3 @@ max-repo-count
-max-repodesc-length
+max-repodesc-length::
Specifies the maximum number of repo description characters to display
@@ -135,3 +176,3 @@ max-repodesc-length
-max-stats
+max-stats::
Set the default maximum statistics period. Valid values are "week",
@@ -140,3 +181,7 @@ max-stats
-module-link
+mimetype.<ext>::
+ Set the mimetype for the specified filename extension. This is used
+ by the `plain` command when returning blob content.
+
+module-link::
Text which will be used as the formatstring for a hyperlink when a
@@ -146,3 +191,3 @@ module-link
-nocache
+nocache::
If set to the value "1" caching will be disabled. This settings is
@@ -151,3 +196,11 @@ nocache
-renamelimit
+noplainemail::
+ If set to "1" showing full author email adresses will be disabled.
+ Default value: "0".
+
+noheader::
+ Flag which, when set to "1", will make cgit omit the standard header
+ on all pages. Default value: none. See also: "embedded".
+
+renamelimit::
Maximum number of files to consider when detecting renames. The value
@@ -156,7 +209,7 @@ renamelimit
-repo.group
- A value for the current repository group, which all repositories
- specified after this setting will inherit. Default value: none.
+repo.group::
+ Legacy alias for "section". This option is deprecated and will not be
+ supported in cgit-1.0.
-robots
+robots::
Text used as content for the "robots" meta-tag. Default value:
@@ -164,3 +217,3 @@ robots
-root-desc
+root-desc::
Text printed below the heading on the repository index page. Default
@@ -168,3 +221,3 @@ root-desc
-root-readme:
+root-readme::
The content of the file specified with this option will be included
@@ -173,3 +226,3 @@ root-readme:
-root-title
+root-title::
Text printed as heading on the repository index page. Default value:
@@ -177,13 +230,26 @@ root-title
-snapshots
- Text which specifies the default (and allowed) set of snapshot formats
- supported by cgit. The value is a space-separated list of zero or more
- of the following values:
- "tar" uncompressed tar-file
- "tar.gz" gzip-compressed tar-file
- "tar.bz2" bzip-compressed tar-file
- "zip" zip-file
- Default value: none.
+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.
-summary-branches
+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
+ values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none.
+
+source-filter::
+ Specifies a command which will be invoked to format plaintext blobs
+ in the tree view. The command will get the blob content on its STDIN
+ and the name of the blob as its only command line argument. The STDOUT
+ from the command will be included verbatim as the blob contents, i.e.
+ this can be used to implement e.g. syntax highlighting. Default value:
+ none.
+
+summary-branches::
Specifies the number of branches to display in the repository "summary"
@@ -191,3 +257,3 @@ summary-branches
-summary-log
+summary-log::
Specifies the number of log entries to display in the repository
@@ -195,3 +261,3 @@ summary-log
-summary-tags
+summary-tags::
Specifies the number of tags to display in the repository "summary"
@@ -199,3 +265,3 @@ summary-tags
-virtual-root
+virtual-root::
Url which, if specified, will be used as root for all cgit links. It
@@ -209,3 +275,7 @@ REPOSITORY SETTINGS
-------------------
-repo.clone-url
+repo.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.
@@ -213,3 +283,7 @@ repo.clone-url
-repo.defbranch
+repo.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
@@ -218,6 +292,6 @@ repo.defbranch
-repo.desc
+repo.desc::
The value to show as repository description. Default value: none.
-repo.enable-log-filecount
+repo.enable-log-filecount::
A flag which can be used to disable the global setting
@@ -225,3 +299,3 @@ repo.enable-log-filecount
-repo.enable-log-linecount
+repo.enable-log-linecount::
A flag which can be used to disable the global setting
@@ -229,3 +303,3 @@ repo.enable-log-linecount
-repo.max-stats
+repo.max-stats::
Override the default maximum statistics period. Valid values are equal
@@ -234,6 +308,6 @@ repo.max-stats
-repo.name
+repo.name::
The value to show as repository name. Default value: <repo.url>.
-repo.owner
+repo.owner::
A value used to identify the owner of the repository. Default value:
@@ -241,3 +315,3 @@ repo.owner
-repo.path
+repo.path::
An absolute path to the repository directory. For non-bare repositories
@@ -245,3 +319,3 @@ repo.path
-repo.readme
+repo.readme::
A path (relative to <repo.path>) which specifies a file to include
@@ -249,3 +323,3 @@ repo.readme
-repo.snapshots
+repo.snapshots::
A mask of allowed snapshot-formats for this repo, restricted by the
@@ -253,3 +327,11 @@ repo.snapshots
-repo.url
+repo.section::
+ Override the current section name for this repository. Default value:
+ none.
+
+repo.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
@@ -258,2 +340,15 @@ repo.url
+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
@@ -261,2 +356,3 @@ EXAMPLE CGITRC FILE
+....
# Enable caching of up to 1000 output entriess
@@ -313,2 +409,15 @@ 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
+mimetype.png=image/png
+mimetype.svg=image/svg+xml
+
+
+##
## List of repositories.
@@ -370,2 +479,3 @@ repo.enable-log-linecount=0
repo.max-stats=month
+....
diff --git a/cmd.c b/cmd.c
index cf97da7..766f903 100644
--- a/cmd.c
+++ b/cmd.c
@@ -41,3 +41,3 @@ static void about_fn(struct cgit_context *ctx)
if (ctx->repo)
- cgit_print_repo_readme();
+ cgit_print_repo_readme(ctx->qry.path);
else
diff --git a/filters/commit-links.sh b/filters/commit-links.sh
new file mode 100755
index 0000000..165a533
--- a/dev/null
+++ b/filters/commit-links.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+# This script can be used to generate links in commit messages - the first
+# sed expression generates links to commits referenced by their SHA1, while
+# the second expression generates links to a fictional bugtracker.
+#
+# To use this script, refer to this file with either the commit-filter or the
+# repo.commit-filter options in cgitrc.
+
+sed -re '
+s|\b([0-9a-fA-F]{8,40})\b|<a href="./?id=\1">\1</a>|g
+s| #([0-9]+)\b|<a href="http://bugs.example.com/?bug=\1">#\1</a>|g
+'
diff --git a/filters/syntax-highlighting.sh b/filters/syntax-highlighting.sh
new file mode 100755
index 0000000..999ad0c
--- a/dev/null
+++ b/filters/syntax-highlighting.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+# This script can be used to implement syntax highlighting in the cgit
+# tree-view by refering to this file with the source-filter or repo.source-
+# filter options in cgitrc.
+#
+# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
+# highlighting, so you'll probably want something like the following included
+# in your css file (generated by highlight 2.4.8 and adapted for cgit):
+#
+# table.blob .num { color:#2928ff; }
+# table.blob .esc { color:#ff00ff; }
+# table.blob .str { color:#ff0000; }
+# table.blob .dstr { color:#818100; }
+# table.blob .slc { color:#838183; font-style:italic; }
+# table.blob .com { color:#838183; font-style:italic; }
+# table.blob .dir { color:#008200; }
+# table.blob .sym { color:#000000; }
+# table.blob .kwa { color:#000000; font-weight:bold; }
+# table.blob .kwb { color:#830000; }
+# table.blob .kwc { color:#000000; font-weight:bold; }
+# table.blob .kwd { color:#010181; }
+
+case "$1" in
+ *.c)
+ highlight -f -I -X -S c
+ ;;
+ *.h)
+ highlight -f -I -X -S c
+ ;;
+ *.sh)
+ highlight -f -I -X -S sh
+ ;;
+ *.css)
+ highlight -f -I -X -S css
+ ;;
+ *)
+ highlight -f -I -X -S txt
+ ;;
+esac
diff --git a/git b/git
-Subproject 5c415311f743ccb11a50f350ff1c385778f049d
+Subproject 7fb6bcff2dece2ff9fbc5ebfe526d9b2a7e764c
diff --git a/scan-tree.c b/scan-tree.c
index 47f3988..dbca797 100644
--- a/scan-tree.c
+++ b/scan-tree.c
@@ -1,2 +1,3 @@
#include "cgit.h"
+#include "configfile.h"
#include "html.h"
@@ -37,18 +38,12 @@ static int is_git_dir(const char *path)
-char *readfile(const char *path)
-{
- FILE *f;
- static char buf[MAX_PATH];
+struct cgit_repo *repo;
+repo_config_fn config_fn;
- if (!(f = fopen(path, "r")))
- return NULL;
- buf[0] = 0;
- fgets(buf, MAX_PATH, f);
- fclose(f);
- return buf;
+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)
+static void add_repo(const char *base, const char *path, repo_config_fn fn)
{
- struct cgit_repo *repo;
struct stat st;
@@ -56,2 +51,3 @@ static void add_repo(const char *base, const char *path)
char *p;
+ size_t size;
@@ -78,2 +74,5 @@ static void add_repo(const char *base, const char *path)
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) : "");
@@ -82,3 +81,3 @@ static void add_repo(const char *base, const char *path)
if (!stat(p, &st))
- repo->desc = xstrdup(readfile(p));
+ readfile(p, &repo->desc, &size);
@@ -87,5 +86,11 @@ static void add_repo(const char *base, const char *path)
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)
{
@@ -97,3 +102,7 @@ static void scan_path(const char *base, const char *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;
@@ -127,3 +136,3 @@ static void scan_path(const char *base, const char *path)
if (S_ISDIR(st.st_mode))
- scan_path(base, buf);
+ scan_path(base, buf, fn);
free(buf);
@@ -133,5 +142,5 @@ static void scan_path(const char *base, const char *path)
-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
@@ -2,2 +2,2 @@
-extern void scan_tree(const char *path);
+extern void scan_tree(const char *path, repo_config_fn fn);
diff --git a/shared.c b/shared.c
index cce0af4..d7b2d5a 100644
--- a/shared.c
+++ b/shared.c
@@ -50,2 +50,3 @@ 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, '/');
@@ -55,3 +56,3 @@ struct cgit_repo *cgit_add_repo(const char *url)
ret->owner = NULL;
- ret->group = ctx.cfg.repo_group;
+ ret->section = ctx.cfg.section;
ret->defbranch = "master";
@@ -64,2 +65,5 @@ struct cgit_repo *cgit_add_repo(const char *url)
ret->mtime = -1;
+ ret->about_filter = ctx.cfg.about_filter;
+ ret->commit_filter = ctx.cfg.commit_filter;
+ ret->source_filter = ctx.cfg.source_filter;
return ret;
@@ -357 +361,57 @@ int cgit_parse_snapshots_mask(const char *str)
}
+
+int cgit_open_filter(struct cgit_filter *filter)
+{
+
+ filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
+ "Unable to duplicate STDOUT");
+ chk_zero(pipe(filter->pipe_fh), "Unable to create pipe to subprocess");
+ filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
+ if (filter->pid == 0) {
+ close(filter->pipe_fh[1]);
+ chk_non_negative(dup2(filter->pipe_fh[0], STDIN_FILENO),
+ "Unable to use pipe as STDIN");
+ execvp(filter->cmd, filter->argv);
+ die("Unable to exec subprocess %s: %s (%d)", filter->cmd,
+ strerror(errno), errno);
+ }
+ close(filter->pipe_fh[0]);
+ chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO),
+ "Unable to use pipe as STDOUT");
+ close(filter->pipe_fh[1]);
+ return 0;
+}
+
+int cgit_close_filter(struct cgit_filter *filter)
+{
+ chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
+ "Unable to restore STDOUT");
+ close(filter->old_stdout);
+ if (filter->pid < 0)
+ return 0;
+ waitpid(filter->pid, &filter->exitstatus, 0);
+ if (WIFEXITED(filter->exitstatus) && !WEXITSTATUS(filter->exitstatus))
+ return 0;
+ die("Subprocess %s exited abnormally", filter->cmd);
+}
+
+/* Read the content of the specified file into a newly allocated buffer,
+ * zeroterminate the buffer and return 0 on success, errno otherwise.
+ */
+int readfile(const char *path, char **buf, size_t *size)
+{
+ int fd;
+ struct stat st;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return errno;
+ if (fstat(fd, &st))
+ return errno;
+ if (!S_ISREG(st.st_mode))
+ return EISDIR;
+ *buf = xmalloc(st.st_size + 1);
+ *size = read_in_full(fd, *buf, st.st_size);
+ (*buf)[*size] = '\0';
+ return (*size == st.st_size ? 0 : errno);
+}
diff --git a/ui-atom.c b/ui-atom.c
index a6ea3ee..808b2d0 100644
--- a/ui-atom.c
+++ b/ui-atom.c
@@ -34,3 +34,3 @@ void add_entry(struct commit *commit, char *host)
}
- if (info->author_email) {
+ if (info->author_email && !ctx.cfg.noplainemail) {
mail = xstrdup(info->author_email);
@@ -54,3 +54,4 @@ void add_entry(struct commit *commit, char *host)
if (host) {
- html("<link rel='alternate' type='text/html' href='http://");
+ html("<link rel='alternate' type='text/html' href='");
+ html(cgit_httpscheme());
html_attr(host);
@@ -115,3 +116,4 @@ void cgit_print_atom(char *tip, char *path, int max_count)
if (host) {
- html("<link rel='alternate' type='text/html' href='http://");
+ html("<link rel='alternate' type='text/html' href='");
+ html(cgit_httpscheme());
html_attr(host);
diff --git a/ui-blob.c b/ui-blob.c
index 3cda03d..2ccd31d 100644
--- a/ui-blob.c
+++ b/ui-blob.c
@@ -29,3 +29,3 @@ void cgit_print_blob(const char *hex, char *path, const char *head)
enum object_type type;
- unsigned char *buf;
+ char *buf;
unsigned long size;
@@ -69,2 +69,8 @@ void cgit_print_blob(const char *hex, char *path, const char *head)
ctx.page.mimetype = ctx.qry.mimetype;
+ if (!ctx.page.mimetype) {
+ if (buffer_is_binary(buf, size))
+ ctx.page.mimetype = "application/octet-stream";
+ else
+ ctx.page.mimetype = "text/plain";
+ }
ctx.page.filename = path;
diff --git a/ui-commit.c b/ui-commit.c
index 41ce70e..f5b0ae5 100644
--- a/ui-commit.c
+++ b/ui-commit.c
@@ -37,3 +37,3 @@ void cgit_print_commit(char *hex)
- load_ref_decorations();
+ load_ref_decorations(DECORATE_FULL_REFS);
@@ -42,4 +42,6 @@ void cgit_print_commit(char *hex)
html_txt(info->author);
- html(" ");
- html_txt(info->author_email);
+ if (!ctx.cfg.noplainemail) {
+ html(" ");
+ html_txt(info->author_email);
+ }
html("</td><td class='right'>");
@@ -49,4 +51,6 @@ void cgit_print_commit(char *hex)
html_txt(info->committer);
- html(" ");
- html_txt(info->committer_email);
+ if (!ctx.cfg.noplainemail) {
+ html(" ");
+ html_txt(info->committer_email);
+ }
html("</td><td class='right'>");
@@ -91,3 +95,7 @@ void cgit_print_commit(char *hex)
html("<div class='commit-subject'>");
+ if (ctx.repo->commit_filter)
+ cgit_open_filter(ctx.repo->commit_filter);
html_txt(info->subject);
+ if (ctx.repo->commit_filter)
+ cgit_close_filter(ctx.repo->commit_filter);
show_commit_decorations(commit);
@@ -95,3 +103,7 @@ void cgit_print_commit(char *hex)
html("<div class='commit-msg'>");
+ if (ctx.repo->commit_filter)
+ cgit_open_filter(ctx.repo->commit_filter);
html_txt(info->msg);
+ if (ctx.repo->commit_filter)
+ cgit_close_filter(ctx.repo->commit_filter);
html("</div>");
diff --git a/ui-log.c b/ui-log.c
index ba2ab03..f3132c9 100644
--- a/ui-log.c
+++ b/ui-log.c
@@ -55,2 +55,6 @@ void show_commit_decorations(struct commit *commit)
}
+ else if (!prefixcmp(deco->name, "refs/tags/")) {
+ strncpy(buf, deco->name + 10, sizeof(buf) - 1);
+ cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf);
+ }
else if (!prefixcmp(deco->name, "refs/remotes/")) {
@@ -159,3 +163,3 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern
setup_revisions(argc, argv, &rev, NULL);
- load_ref_decorations();
+ load_ref_decorations(DECORATE_FULL_REFS);
rev.show_decorations = 1;
diff --git a/ui-patch.c b/ui-patch.c
index 5d665d3..2a8f7a5 100644
--- a/ui-patch.c
+++ b/ui-patch.c
@@ -110,3 +110,7 @@ void cgit_print_patch(char *hex)
htmlf("From %s Mon Sep 17 00:00:00 2001\n", sha1_to_hex(sha1));
- htmlf("From: %s %s\n", info->author, info->author_email);
+ htmlf("From: %s", info->author);
+ if (!ctx.cfg.noplainemail) {
+ htmlf(" %s", info->author_email);
+ }
+ html("\n");
html("Date: ");
diff --git a/ui-plain.c b/ui-plain.c
index e08b15b..a4ce077 100644
--- a/ui-plain.c
+++ b/ui-plain.c
@@ -19,4 +19,5 @@ static void print_object(const unsigned char *sha1, const char *path)
enum object_type type;
- char *buf;
+ char *buf, *ext;
unsigned long size;
+ struct string_list_item *mime;
@@ -33,5 +34,18 @@ static void print_object(const unsigned char *sha1, const char *path)
}
- ctx.page.mimetype = "text/plain";
+ ctx.page.mimetype = NULL;
+ ext = strrchr(path, '.');
+ if (ext && *(++ext)) {
+ mime = string_list_lookup(ext, &ctx.cfg.mimetypes);
+ if (mime)
+ ctx.page.mimetype = (char *)mime->util;
+ }
+ if (!ctx.page.mimetype) {
+ if (buffer_is_binary(buf, size))
+ ctx.page.mimetype = "application/octet-stream";
+ else
+ ctx.page.mimetype = "text/plain";
+ }
ctx.page.filename = fmt("%s", path);
ctx.page.size = size;
+ ctx.page.etag = sha1_to_hex(sha1);
cgit_print_http_headers(&ctx);
diff --git a/ui-refs.c b/ui-refs.c
index 25da00a..d3b4f6e 100644
--- a/ui-refs.c
+++ b/ui-refs.c
@@ -48,4 +48,15 @@ static int cmp_tag_age(const void *a, const void *b)
struct refinfo *r2 = *(struct refinfo **)b;
+ int r1date, r2date;
- return cmp_age(r1->tag->tagger_date, r2->tag->tagger_date);
+ if (r1->object->type != OBJ_COMMIT)
+ r1date = r1->tag->tagger_date;
+ else
+ r1date = r1->commit->committer_date;
+
+ if (r2->object->type != OBJ_COMMIT)
+ r2date = r2->tag->tagger_date;
+ else
+ r2date = r2->commit->committer_date;
+
+ return cmp_age(r1date, r2date);
}
@@ -147,2 +158,8 @@ static int print_tag(struct refinfo *ref)
cgit_object_link(ref->object);
+ html("</td><td>");
+ if (ref->object->type == OBJ_COMMIT)
+ html(ref->commit->author);
+ html("</td><td colspan='2'>");
+ if (ref->object->type == OBJ_COMMIT)
+ cgit_print_age(ref->commit->commit->date, -1, NULL);
html("</td></tr>\n");
diff --git a/ui-repolist.c b/ui-repolist.c
index 3aedde5..3ef2e99 100644
--- a/ui-repolist.c
+++ b/ui-repolist.c
@@ -20,15 +20,16 @@ time_t read_agefile(char *path)
{
- FILE *f;
- static char buf[64], buf2[64];
+ time_t result;
+ size_t size;
+ char *buf;
+ static char buf2[64];
- if (!(f = fopen(path, "r")))
+ if (readfile(path, &buf, &size))
return -1;
- buf[0] = 0;
- if (fgets(buf, sizeof(buf), f) == NULL)
- return -1;
- fclose(f);
+
if (parse_date(buf, buf2, sizeof(buf2)))
- return strtoul(buf2, NULL, 10);
+ result = strtoul(buf2, NULL, 10);
else
- return 0;
+ result = 0;
+ free(buf);
+ return result;
}
@@ -137,2 +138,14 @@ static int cmp(const char *s1, const char *s2)
+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)
@@ -179,2 +192,3 @@ struct sortcolumn {
struct sortcolumn sortcolumn[] = {
+ {"section", sort_section},
{"name", sort_name},
@@ -204,3 +218,4 @@ 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;
@@ -220,2 +235,4 @@ void cgit_print_repolist()
sorted = sort_repolist(ctx.qry.sort);
+ else
+ sort_repolist("section");
@@ -233,15 +250,18 @@ void cgit_print_repolist()
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);
@@ -276,4 +296,9 @@ void cgit_print_site_readme()
{
- if (ctx.cfg.root_readme)
- html_include(ctx.cfg.root_readme);
+ if (!ctx.cfg.root_readme)
+ return;
+ if (ctx.cfg.about_filter)
+ cgit_open_filter(ctx.cfg.about_filter);
+ html_include(ctx.cfg.root_readme);
+ if (ctx.cfg.about_filter)
+ cgit_close_filter(ctx.cfg.about_filter);
}
diff --git a/ui-shared.c b/ui-shared.c
index 40060ba..07d5dd4 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -36,20 +36,19 @@ void cgit_print_error(char *msg)
-char *cgit_hosturl()
+char *cgit_httpscheme()
{
- char *host, *port;
+ if (ctx.env.https && !strcmp(ctx.env.https, "on"))
+ return "https://";
+ else
+ return "http://";
+}
- host = getenv("HTTP_HOST");
- if (host) {
- host = xstrdup(host);
- } else {
- host = getenv("SERVER_NAME");
- if (!host)
- return NULL;
- port = getenv("SERVER_PORT");
- if (port && atoi(port) != 80)
- host = xstrdup(fmt("%s:%d", host, atoi(port)));
- else
- host = xstrdup(host);
- }
- return host;
+char *cgit_hosturl()
+{
+ if (ctx.env.http_host)
+ return ctx.env.http_host;
+ if (!ctx.env.server_name)
+ return NULL;
+ if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80)
+ return ctx.env.server_name;
+ return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port));
}
@@ -458,2 +457,7 @@ void cgit_print_http_headers(struct cgit_context *ctx)
{
+ if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1"))
+ return;
+
+ if (ctx->page.status)
+ htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg);
if (ctx->page.mimetype && ctx->page.charset)
@@ -470,3 +474,7 @@ void cgit_print_http_headers(struct cgit_context *ctx)
htmlf("Expires: %s\n", http_date(ctx->page.expires));
+ if (ctx->page.etag)
+ htmlf("ETag: \"%s\"\n", ctx->page.etag);
html("\n");
+ if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD"))
+ exit(0);
}
@@ -475,2 +483,8 @@ void cgit_print_docstart(struct cgit_context *ctx)
{
+ if (ctx->cfg.embedded) {
+ if (ctx->cfg.header)
+ html_include(ctx->cfg.header);
+ return;
+ }
+
char *host = cgit_hosturl();
@@ -494,3 +508,4 @@ void cgit_print_docstart(struct cgit_context *ctx)
if (host && ctx->repo) {
- html("<link rel='alternate' title='Atom feed' href='http://");
+ html("<link rel='alternate' title='Atom feed' href='");
+ html(cgit_httpscheme());
html_attr(cgit_hosturl());
@@ -498,4 +513,6 @@ void cgit_print_docstart(struct cgit_context *ctx)
fmt("h=%s", ctx->qry.head)));
- html("' type='application/atom+xml'/>");
+ html("' type='application/atom+xml'/>\n");
}
+ if (ctx->cfg.head_include)
+ html_include(ctx->cfg.head_include);
html("</head>\n");
@@ -508,3 +525,9 @@ void cgit_print_docend()
{
- html("</div>");
+ html("</div> <!-- class=content -->\n");
+ if (ctx.cfg.embedded) {
+ html("</div> <!-- id=cgit -->\n");
+ if (ctx.cfg.footer)
+ html_include(ctx.cfg.footer);
+ return;
+ }
if (ctx.cfg.footer)
@@ -517,2 +540,3 @@ void cgit_print_docend()
}
+ html("</div> <!-- id=cgit -->\n");
html("</body>\n</html>\n");
@@ -604,9 +628,4 @@ char *hc(struct cgit_cmd *cmd, const char *page)
-void cgit_print_pageheader(struct cgit_context *ctx)
+static void print_header(struct cgit_context *ctx)
{
- struct cgit_cmd *cmd = cgit_get_cmd(ctx);
-
- if (!cmd && ctx->repo)
- fallback_cmd = "summary";
-
html("<table id='header'>\n");
@@ -654,2 +673,14 @@ void cgit_print_pageheader(struct cgit_context *ctx)
html("</td></tr></table>\n");
+}
+
+void cgit_print_pageheader(struct cgit_context *ctx)
+{
+ struct cgit_cmd *cmd = cgit_get_cmd(ctx);
+
+ if (!cmd && ctx->repo)
+ fallback_cmd = "summary";
+
+ html("<div id='cgit'>");
+ if (!ctx->cfg.noheader)
+ print_header(ctx);
diff --git a/ui-shared.h b/ui-shared.h
index 5a3821f..bff4826 100644
--- a/ui-shared.h
+++ b/ui-shared.h
@@ -3,2 +3,3 @@
+extern char *cgit_httpscheme();
extern char *cgit_hosturl();
diff --git a/ui-snapshot.c b/ui-snapshot.c
index 5372f5d..4136b3e 100644
--- a/ui-snapshot.c
+++ b/ui-snapshot.c
@@ -14,33 +14,12 @@ static int write_compressed_tar_archive(struct archiver_args *args,const char *f
{
- int rw[2];
- pid_t gzpid;
- int stdout2;
- int status;
int rv;
+ struct cgit_filter f;
- stdout2 = chk_non_negative(dup(STDIN_FILENO), "Preserving STDOUT before compressing");
- chk_zero(pipe(rw), "Opening pipe from compressor subprocess");
- gzpid = chk_non_negative(fork(), "Forking compressor subprocess");
- if(gzpid==0) {
- /* child */
- chk_zero(close(rw[1]), "Closing write end of pipe in child");
- chk_zero(close(STDIN_FILENO), "Closing STDIN");
- chk_non_negative(dup2(rw[0],STDIN_FILENO), "Redirecting compressor input to stdin");
- execlp(filter,filter,NULL);
- _exit(-1);
- }
- /* parent */
- chk_zero(close(rw[0]), "Closing read end of pipe");
- chk_non_negative(dup2(rw[1],STDOUT_FILENO), "Redirecting output to compressor");
-
+ f.cmd = xstrdup(filter);
+ f.argv = malloc(2 * sizeof(char *));
+ f.argv[0] = f.cmd;
+ f.argv[1] = NULL;
+ cgit_open_filter(&f);
rv = write_tar_archive(args);
-
- chk_zero(close(STDOUT_FILENO), "Closing STDOUT redirected to compressor");
- chk_non_negative(dup2(stdout2,STDOUT_FILENO), "Restoring uncompressed STDOUT");
- chk_zero(close(stdout2), "Closing uncompressed STDOUT");
- chk_zero(close(rw[1]), "Closing write end of pipe in parent");
- chk_positive(waitpid(gzpid,&status,0), "Waiting on compressor process");
- if(! ( WIFEXITED(status) && WEXITSTATUS(status)==0 ) )
- cgit_print_error("Failed to compress archive");
-
+ cgit_close_filter(&f);
return rv;
diff --git a/ui-stats.c b/ui-stats.c
index 9fc06d3..bdaf9cc 100644
--- a/ui-stats.c
+++ b/ui-stats.c
@@ -156,2 +156,10 @@ int cgit_find_stats_period(const char *expr, struct cgit_period **period)
+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,
diff --git a/ui-stats.h b/ui-stats.h
index 4f13dba..f0761ba 100644
--- a/ui-stats.h
+++ b/ui-stats.h
@@ -23,2 +23,3 @@ 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);
diff --git a/ui-summary.c b/ui-summary.c
index ede4a62..a2c018e 100644
--- a/ui-summary.c
+++ b/ui-summary.c
@@ -68,9 +68,25 @@ void cgit_print_summary()
-void cgit_print_repo_readme()
+void cgit_print_repo_readme(char *path)
{
- if (ctx.repo->readme) {
- html("<div id='summary'>");
- html_include(ctx.repo->readme);
- html("</div>");
- }
+ char *slash, *tmp;
+
+ if (!ctx.repo->readme)
+ return;
+
+ if (path) {
+ slash = strrchr(ctx.repo->readme, '/');
+ if (!slash)
+ return;
+ tmp = xmalloc(slash - ctx.repo->readme + 1 + strlen(path) + 1);
+ strncpy(tmp, ctx.repo->readme, slash - ctx.repo->readme + 1);
+ strcpy(tmp + (slash - ctx.repo->readme + 1), path);
+ } else
+ tmp = ctx.repo->readme;
+ html("<div id='summary'>");
+ if (ctx.repo->about_filter)
+ cgit_open_filter(ctx.repo->about_filter);
+ html_include(tmp);
+ if (ctx.repo->about_filter)
+ cgit_close_filter(ctx.repo->about_filter);
+ html("</div>");
}
diff --git a/ui-summary.h b/ui-summary.h
index 3e13039..c01f560 100644
--- a/ui-summary.h
+++ b/ui-summary.h
@@ -4,3 +4,3 @@
extern void cgit_print_summary();
-extern void cgit_print_repo_readme();
+extern void cgit_print_repo_readme(char *path);
diff --git a/ui-tag.c b/ui-tag.c
index 8c263ab..c2d72af 100644
--- a/ui-tag.c
+++ b/ui-tag.c
@@ -69,3 +69,3 @@ void cgit_print_tag(char *revname)
html_txt(info->tagger);
- if (info->tagger_email) {
+ if (info->tagger_email && !ctx.cfg.noplainemail) {
html(" ");
diff --git a/ui-tree.c b/ui-tree.c
index 553dbaa..f53ab64 100644
--- a/ui-tree.c
+++ b/ui-tree.c
@@ -17,3 +17,3 @@ int header = 0;
-static void print_text_buffer(char *buf, unsigned long size)
+static void print_text_buffer(const char *name, char *buf, unsigned long size)
{
@@ -24,15 +24,32 @@ static void print_text_buffer(char *buf, unsigned long size)
html("<table summary='blob content' class='blob'>\n");
- html("<tr><td class='linenumbers'><pre>");
- idx = 0;
- lineno = 0;
-
- if (size) {
- htmlf(numberfmt, ++lineno);
- while(idx < size - 1) { // skip absolute last newline
- if (buf[idx] == '\n')
- htmlf(numberfmt, ++lineno);
- idx++;
+
+ if (ctx.cfg.enable_tree_linenumbers) {
+ html("<tr><td class='linenumbers'><pre>");
+ idx = 0;
+ lineno = 0;
+
+ if (size) {
+ htmlf(numberfmt, ++lineno);
+ while(idx < size - 1) { // skip absolute last newline
+ if (buf[idx] == '\n')
+ htmlf(numberfmt, ++lineno);
+ idx++;
+ }
}
+ html("</pre></td>\n");
+ }
+ else {
+ html("<tr>\n");
+ }
+
+ if (ctx.repo->source_filter) {
+ html("<td class='lines'><pre><code>");
+ ctx.repo->source_filter->argv[1] = xstrdup(name);
+ cgit_open_filter(ctx.repo->source_filter);
+ write(STDOUT_FILENO, buf, size);
+ cgit_close_filter(ctx.repo->source_filter);
+ html("</code></pre></td></tr></table>\n");
+ return;
}
- html("</pre></td>\n");
+
html("<td class='lines'><pre><code>");
@@ -67,3 +84,3 @@ static void print_binary_buffer(char *buf, unsigned long size)
-static void print_object(const unsigned char *sha1, char *path)
+static void print_object(const unsigned char *sha1, char *path, const char *basename)
{
@@ -95,3 +112,3 @@ static void print_object(const unsigned char *sha1, char *path)
else
- print_text_buffer(buf, size);
+ print_text_buffer(basename, buf, size);
}
@@ -105,2 +122,3 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
char *fullpath;
+ char *class;
enum object_type type;
@@ -137,3 +155,8 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen,
} else {
- cgit_tree_link(name, NULL, "ls-blob", ctx.qry.head,
+ class = strrchr(name, '.');
+ if (class != NULL) {
+ class = fmt("ls-blob %s", class + 1);
+ } else
+ class = "ls-blob";
+ cgit_tree_link(name, NULL, class, ctx.qry.head,
curr_rev, fullpath);
@@ -215,3 +238,3 @@ static int walk_tree(const unsigned char *sha1, const char *base, int baselen,
} else {
- print_object(sha1, buffer);
+ print_object(sha1, buffer, pathname);
return 0;