-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | Makefile | 21 | ||||
-rw-r--r-- | cache.h | 2 | ||||
-rw-r--r-- | cgit-doc.css | 3 | ||||
-rw-r--r-- | cgit.c | 345 | ||||
-rw-r--r-- | cgit.css | 10 | ||||
-rw-r--r-- | cgit.h | 52 | ||||
-rw-r--r-- | cgitrc.5.txt | 244 | ||||
-rw-r--r-- | cmd.c | 2 | ||||
-rwxr-xr-x | filters/commit-links.sh | 12 | ||||
-rwxr-xr-x | filters/syntax-highlighting.sh | 39 | ||||
m--------- | git | 0 | ||||
-rw-r--r-- | scan-tree.c | 45 | ||||
-rw-r--r-- | scan-tree.h | 2 | ||||
-rw-r--r-- | shared.c | 62 | ||||
-rw-r--r-- | ui-atom.c | 8 | ||||
-rw-r--r-- | ui-blob.c | 8 | ||||
-rw-r--r-- | ui-commit.c | 22 | ||||
-rw-r--r-- | ui-log.c | 6 | ||||
-rw-r--r-- | ui-patch.c | 6 | ||||
-rw-r--r-- | ui-plain.c | 18 | ||||
-rw-r--r-- | ui-refs.c | 19 | ||||
-rw-r--r-- | ui-repolist.c | 65 | ||||
-rw-r--r-- | ui-shared.c | 81 | ||||
-rw-r--r-- | ui-shared.h | 1 | ||||
-rw-r--r-- | ui-snapshot.c | 35 | ||||
-rw-r--r-- | ui-stats.c | 8 | ||||
-rw-r--r-- | ui-stats.h | 1 | ||||
-rw-r--r-- | ui-summary.c | 28 | ||||
-rw-r--r-- | ui-summary.h | 2 | ||||
-rw-r--r-- | ui-tag.c | 2 | ||||
-rw-r--r-- | ui-tree.c | 55 |
32 files changed, 942 insertions, 267 deletions
@@ -4,2 +4,7 @@ cgit.conf VERSION +cgitrc.5 +cgitrc.5.fo +cgitrc.5.html +cgitrc.5.pdf +cgitrc.5.xml *.o @@ -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: @@ -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; +} @@ -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) @@ -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; @@ -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 +.... @@ -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); @@ -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); +} @@ -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); @@ -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>"); @@ -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; @@ -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: "); @@ -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); @@ -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; @@ -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, @@ -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); @@ -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(" "); @@ -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; |