-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | Makefile | 21 | ||||
-rw-r--r-- | cgit-doc.css | 3 | ||||
-rw-r--r-- | cgit.c | 89 | ||||
-rw-r--r-- | cgit.css | 2 | ||||
-rw-r--r-- | cgit.h | 42 | ||||
-rw-r--r-- | cgitrc.5.txt | 198 | ||||
-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 | 19 | ||||
-rw-r--r-- | shared.c | 59 | ||||
-rw-r--r-- | ui-atom.c | 8 | ||||
-rw-r--r-- | ui-blob.c | 8 | ||||
-rw-r--r-- | ui-commit.c | 20 | ||||
-rw-r--r-- | ui-log.c | 4 | ||||
-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 | 28 | ||||
-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-summary.c | 28 | ||||
-rw-r--r-- | ui-summary.h | 2 | ||||
-rw-r--r-- | ui-tag.c | 2 | ||||
-rw-r--r-- | ui-tree.c | 26 |
28 files changed, 594 insertions, 183 deletions
@@ -3,4 +3,9 @@ cgit cgit.conf VERSION +cgitrc.5 +cgitrc.5.fo +cgitrc.5.html +cgitrc.5.pdf +cgitrc.5.xml *.o *.d @@ -6,5 +6,5 @@ CGIT_CONFIG = /etc/cgitrc CACHE_ROOT = /var/cache/cgit SHA1_HEADER = <openssl/sha.h> -GIT_VER = 1.6.1.1 +GIT_VER = 1.6.3.4 GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 INSTALL = install @@ -101,5 +101,6 @@ 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 all: cgit @@ -150,7 +151,21 @@ uninstall: rm -f $(CGIT_DATA_PATH)/cgit.png -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: curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git 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; +} @@ -18,4 +18,27 @@ 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; +} + void config_cb(const char *name, const char *value) { @@ -32,4 +55,6 @@ void config_cb(const char *name, const char *value) else if (!strcmp(name, "footer")) ctx.cfg.footer = xstrdup(value); + else if (!strcmp(name, "head-include")) + ctx.cfg.head_include = xstrdup(value); else if (!strcmp(name, "header")) ctx.cfg.header = xstrdup(value); @@ -50,4 +75,8 @@ void config_cb(const char *name, const char *value) } else if (!strcmp(name, "nocache")) ctx.cfg.nocache = atoi(value); + else if (!strcmp(name, "noplainemail")) + ctx.cfg.noplainemail = atoi(value); + else if (!strcmp(name, "noheader")) + ctx.cfg.noheader = atoi(value); else if (!strcmp(name, "snapshots")) ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); @@ -72,4 +101,10 @@ void config_cb(const char *name, const char *value) else if (!strcmp(name, "cache-dynamic-ttl")) ctx.cfg.cache_dynamic_ttl = atoi(value); + else if (!strcmp(name, "about-filter")) + ctx.cfg.about_filter = new_filter(value, 0); + else if (!strcmp(name, "commit-filter")) + ctx.cfg.commit_filter = new_filter(value, 0); + else if (!strcmp(name, "embedded")) + ctx.cfg.embedded = atoi(value); else if (!strcmp(name, "max-message-length")) ctx.cfg.max_msg_len = atoi(value); @@ -80,4 +115,6 @@ void config_cb(const char *name, const char *value) else if (!strcmp(name, "max-commit-count")) ctx.cfg.max_commit_count = atoi(value); + else if (!strcmp(name, "source-filter")) + ctx.cfg.source_filter = new_filter(value, 1); else if (!strcmp(name, "summary-log")) ctx.cfg.summary_log = atoi(value); @@ -96,4 +133,6 @@ void config_cb(const char *name, const char *value) else if (!strcmp(name, "local-time")) ctx.cfg.local_time = atoi(value); + else if (!prefixcmp(name, "mimetype.")) + add_mimetype(name + 9, value); else if (!strcmp(name, "repo.group")) ctx.cfg.repo_group = xstrdup(value); @@ -122,4 +161,10 @@ void config_cb(const char *name, const char *value) else if (ctx.repo && !strcmp(name, "repo.module-link")) ctx.repo->module_link= xstrdup(value); + else if (ctx.repo && !strcmp(name, "repo.about-filter")) + ctx.repo->about_filter = new_filter(value, 0); + else if (ctx.repo && !strcmp(name, "repo.commit-filter")) + ctx.repo->commit_filter = new_filter(value, 0); + else if (ctx.repo && !strcmp(name, "repo.source-filter")) + ctx.repo->source_filter = new_filter(value, 1); else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { if (*value == '/') @@ -174,4 +219,9 @@ 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) { @@ -187,5 +237,5 @@ static void prepare_context(struct cgit_context *ctx) 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.max_repo_count = 50; @@ -204,4 +254,14 @@ static void prepare_context(struct cgit_context *ctx) ctx->cfg.summary_log = 10; ctx->cfg.summary_tags = 10; + ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); + ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); + ctx->env.https = xstrdupn(getenv("HTTPS")); + ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); + ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); + ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); + ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); + ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); + ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); + ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); ctx->page.mimetype = "text/html"; ctx->page.charset = PAGE_ENCODING; @@ -210,4 +270,12 @@ static void prepare_context(struct cgit_context *ctx) ctx->page.modified = time(NULL); ctx->page.expires = ctx->page.modified; + ctx->page.etag = NULL; + memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); + if (ctx->env.script_name) + ctx->cfg.script_name = ctx->env.script_name; + if (ctx->env.query_string) + ctx->qry.raw = ctx->env.query_string; + if (!ctx->env.cgit_config) + ctx->env.cgit_config = CGIT_CONFIG; } @@ -289,4 +357,6 @@ static int prepare_repo_cmd(struct cgit_context *ctx) tmp = xstrdup(ctx->qry.head); ctx->qry.head = ctx->repo->defbranch; + ctx->page.status = 404; + ctx->page.statusmsg = "not found"; cgit_print_http_headers(ctx); cgit_print_docstart(ctx); @@ -380,4 +450,7 @@ static void cgit_parse_args(int argc, const char **argv) ctx.cfg.nocache = 1; } + if (!strcmp(argv[i], "--nohttp")) { + ctx.env.no_http = "1"; + } if (!strncmp(argv[i], "--query=", 8)) { ctx.qry.raw = xstrdup(argv[i]+8); @@ -432,5 +505,4 @@ static int calc_ttl() int main(int argc, const char **argv) { - const char *cgit_config_env = getenv("CGIT_CONFIG"); const char *path; char *qry; @@ -442,11 +514,6 @@ int main(int argc, const char **argv) cgit_repolist.repos = NULL; - 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; http_parse_querystring(ctx.qry.raw, querystring_cb); @@ -463,5 +530,5 @@ int main(int argc, const char **argv) * long as PATH_INFO is included in the cache lookup key). */ - path = getenv("PATH_INFO"); + path = ctx.env.path_info; if (!ctx.qry.url && path) { if (path[0] == '/') @@ -473,5 +540,5 @@ int main(int argc, const char **argv) free(qry); } else - ctx.qry.raw = ctx.qry.url; + ctx.qry.raw = xstrdup(ctx.qry.url); cgit_parse_url(ctx.qry.url); } @@ -479,4 +546,6 @@ int main(int argc, const char **argv) ttl = calc_ttl(); ctx.page.expires += ttl*60; + if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) + ctx.cfg.nocache = 1; if (ctx.cfg.nocache) ctx.cfg.cache_size = 0; @@ -156,5 +156,5 @@ table.list td.logmsg { font-family: monospace; white-space: pre; - padding: 1em 0em 2em 0em; + padding: 1em 0.5em 2em 0.5em; } @@ -16,4 +16,5 @@ #include <log-tree.h> #include <archive.h> +#include <string-list.h> #include <xdiff-interface.h> #include <xdiff/xdiff.h> @@ -49,4 +50,13 @@ typedef void (*filepair_fn)(struct diff_filepair *pair); typedef void (*linediff_fn)(char *line, int len); +struct cgit_filter { + char *cmd; + char **argv; + int old_stdout; + int pipe_fh[2]; + int pid; + int exitstatus; +}; + struct cgit_repo { char *url; @@ -65,4 +75,7 @@ struct cgit_repo { int max_stats; time_t mtime; + struct cgit_filter *about_filter; + struct cgit_filter *commit_filter; + struct cgit_filter *source_filter; }; @@ -137,4 +150,5 @@ struct cgit_config { char *favicon; char *footer; + char *head_include; char *header; char *index_header; @@ -156,4 +170,5 @@ struct cgit_config { int cache_root_ttl; int cache_static_ttl; + int embedded; int enable_index_links; int enable_log_filecount; @@ -167,4 +182,6 @@ struct cgit_config { int max_stats; int nocache; + int noplainemail; + int noheader; int renamelimit; int snapshots; @@ -172,4 +189,8 @@ struct cgit_config { int summary_log; int summary_tags; + struct string_list mimetypes; + struct cgit_filter *about_filter; + struct cgit_filter *commit_filter; + struct cgit_filter *source_filter; }; @@ -181,8 +202,25 @@ struct cgit_page { char *charset; 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; }; struct cgit_context { + struct cgit_environment env; struct cgit_query qry; struct cgit_config cfg; @@ -243,4 +281,8 @@ extern const char *cgit_repobasename(const char *reponame); 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); #endif /* CGIT_H */ diff --git a/cgitrc.5.txt b/cgitrc.5.txt index fd299ae..0d18290 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt @@ -1,13 +1,13 @@ -CGITRC -====== +CGITRC(5) +======== 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 repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank @@ -15,7 +15,21 @@ 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 to specify the date and time of the youngest commit in the repository. @@ -24,31 +38,31 @@ agefile hh:mm:ss". Default value: "info/web/last-modified". -cache-root +cache-root:: Path used to store the cgit cache entries. Default value: "/var/cache/cgit". -cache-dynamic-ttl +cache-dynamic-ttl:: Number which specifies the time-to-live, in minutes, for the cached version of repository pages accessed without a fixed SHA1. Default value: "5". -cache-repo-ttl +cache-repo-ttl:: Number which specifies the time-to-live, in minutes, for the cached version of the repository summary page. Default value: "5". -cache-root-ttl +cache-root-ttl:: Number which specifies the time-to-live, in minutes, for the cached version of the repository index page. Default value: "5". -cache-size +cache-size:: The maximum number of entries in the cgit cache. Default value: "0" (i.e. caching is disabled). -cache-static-ttl +cache-static-ttl:: Number which specifies the time-to-live, in minutes, for the cached version of repository pages accessed with a fixed SHA1. Default value: "5". -clone-prefix +clone-prefix:: Space-separated list of common prefixes which, when combined with a repository url, generates valid clone urls for the repository. This @@ -56,42 +70,57 @@ clone-prefix none. -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. Default value: "/cgit.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-index-links:: Flag which, when set to "1", will make cgit generate extra links for each repo in the repository index (specifically, to the "summary", "commit" and "tree" pages). Default value: "0". -enable-log-filecount +enable-log-filecount:: Flag which, when set to "1", will make cgit print the number of modified files for each commit on the repository log page. Default value: "0". -enable-log-linecount +enable-log-linecount:: Flag which, when set to "1", will make cgit print the number of added and removed lines for each commit on the repository log page. Default value: "0". -favicon +favicon:: Url used as link to a shortcut icon for cgit. If specified, it is suggested to use the value "/favicon.ico" since certain browsers will ignore other values. Default value: none. -footer +footer:: The content of the file specified with this option will be included verbatim at the bottom of all pages (i.e. it replaces the standard "generated by..." message. Default value: none. -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 verbatim at the top of all pages. Default value: none. -include +include:: Name of a configfile to include before the rest of the current config- file is parsed. Default value: none. -index-header +index-header:: The content of the file specified with this option will be included verbatim above the repository index. This setting is deprecated, and @@ -99,5 +128,5 @@ index-header value: none. -index-info +index-info:: The content of the file specified with this option will be included verbatim below the heading on the repository index page. This setting @@ -105,39 +134,43 @@ index-info instead). Default value: none. -local-time +local-time:: Flag which, if set to "1", makes cgit print commit and tag times in the servers timezone. Default value: "0". -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 calculated url of the repository index page will be used. Default value: none. -max-commit-count +max-commit-count:: Specifies the number of entries to list per page in "log" view. Default value: "50". -max-message-length +max-message-length:: Specifies the maximum number of commit message characters to display in "log" view. Default value: "80". -max-repo-count +max-repo-count:: Specifies the number of entries to list per page on the repository index page. Default value: "50". -max-repodesc-length +max-repodesc-length:: Specifies the maximum number of repo description characters to display on the repository index page. Default value: "80". -max-stats +max-stats:: Set the default maximum statistics period. Valid values are "week", "month", "quarter" and "year". If unspecified, statistics are disabled. Default value: none. See also: "repo.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 submodule is printed in a directory listing. The arguments for the @@ -145,58 +178,69 @@ module-link value: "./?repo=%s&page=commit&id=%s" -nocache +nocache:: If set to the value "1" caching will be disabled. This settings is deprecated, and will not be honored starting with cgit-1.0. Default value: "0". -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 "-1" uses the compiletime value in git (for further info, look at `man git-diff`). Default value: "-1". -repo.group +repo.group:: A value for the current repository group, which all repositories specified after this setting will inherit. Default value: none. -robots +robots:: Text used as content for the "robots" meta-tag. Default value: "index, nofollow". -root-desc +root-desc:: Text printed below the heading on the repository index page. Default value: "a fast webinterface for the git dscm". -root-readme: +root-readme:: The content of the file specified with this option will be included verbatim below the "about" link on the repository index page. Default value: none. -root-title +root-title:: Text printed as heading on the repository index page. Default value: "Git Repository Browser". -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. +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 +summary-branches:: Specifies the number of branches to display in the repository "summary" view. Default value: "10". -summary-log +summary-log:: Specifies the number of log entries to display in the repository "summary" view. Default value: "10". -summary-tags +summary-tags:: Specifies the number of tags to display in the repository "summary" view. Default value: "10". -virtual-root +virtual-root:: Url which, if specified, will be used as root for all cgit links. It will also cause cgit to generate 'virtual urls', i.e. urls like @@ -208,49 +252,58 @@ virtual-root REPOSITORY SETTINGS ------------------- -repo.clone-url +repo.about-filter:: + Override the default about-filter. Default value: <about-filter>. + +repo.clone-url:: A list of space-separated urls which can be used to clone this repo. Default value: none. -repo.defbranch +repo.commit-filter:: + Override the default commit-filter. Default value: <commit-filter>. + +repo.defbranch:: The name of the default branch for this repository. If no such branch exists in the repository, the first branch name (when sorted) is used as default instead. Default value: "master". -repo.desc +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 `enable-log-filecount'. Default value: none. -repo.enable-log-linecount +repo.enable-log-linecount:: A flag which can be used to disable the global setting `enable-log-linecount'. Default value: none. -repo.max-stats +repo.max-stats:: Override the default maximum statistics period. Valid values are equal to the values specified for the global "max-stats" setting. Default value: none. -repo.name +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: none. -repo.path +repo.path:: An absolute path to the repository directory. For non-bare repositories this is the .git-directory. Default value: none. -repo.readme +repo.readme:: A path (relative to <repo.path>) which specifies a file to include verbatim as the "About" page for this repo. Default value: none. -repo.snapshots +repo.snapshots:: A mask of allowed snapshot-formats for this repo, restricted by the "snapshots" global setting. Default value: <snapshots>. -repo.url +repo.source-filter:: + Override the default source-filter. Default value: <source-filter>. + +repo.url:: The relative url used to access the repository. This must be the first setting specified for each repo. Default value: none. @@ -260,4 +313,5 @@ EXAMPLE CGITRC FILE ------------------- +.... # Enable caching of up to 1000 output entriess cache-size=1000 @@ -312,4 +366,17 @@ 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. ## PS: Any repositories listed when repo.group is unset will not be @@ -369,4 +436,5 @@ repo.enable-log-linecount=0 # Restrict the max statistics period for this repo repo.max-stats=month +.... @@ -40,5 +40,5 @@ static void about_fn(struct cgit_context *ctx) { if (ctx->repo) - cgit_print_repo_readme(); + cgit_print_repo_readme(ctx->qry.path); else cgit_print_site_readme(); 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 e276f018f2c1f0fc962fbe44a36708d1cdebada diff --git a/scan-tree.c b/scan-tree.c index 47f3988..4da21a4 100644 --- a/scan-tree.c +++ b/scan-tree.c @@ -36,17 +36,4 @@ static int is_git_dir(const char *path) } -char *readfile(const char *path) -{ - FILE *f; - static char buf[MAX_PATH]; - - if (!(f = fopen(path, "r"))) - return NULL; - buf[0] = 0; - fgets(buf, MAX_PATH, f); - fclose(f); - return buf; -} - static void add_repo(const char *base, const char *path) { @@ -55,4 +42,5 @@ static void add_repo(const char *base, const char *path) struct passwd *pwd; char *p; + size_t size; if (stat(path, &st)) { @@ -77,9 +65,12 @@ static void add_repo(const char *base, const char *path) repo->name = repo->url; repo->path = xstrdup(path); + p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL; + if (p) + *p = '\0'; repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : ""); p = fmt("%s/description", path); if (!stat(p, &st)) - repo->desc = xstrdup(readfile(p)); + readfile(p, &repo->desc, &size); p = fmt("%s/README.html", path); @@ -63,4 +63,7 @@ struct cgit_repo *cgit_add_repo(const char *url) ret->readme = NULL; 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; } @@ -356,2 +359,58 @@ int cgit_parse_snapshots_mask(const char *str) return rv; } + +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); +} @@ -33,5 +33,5 @@ void add_entry(struct commit *commit, char *host) html("</name>\n"); } - if (info->author_email) { + if (info->author_email && !ctx.cfg.noplainemail) { mail = xstrdup(info->author_email); t = strchr(mail, '<'); @@ -53,5 +53,6 @@ void add_entry(struct commit *commit, char *host) html("</published>\n"); 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); html_attr(cgit_pageurl(ctx.repo->url, "commit", NULL)); @@ -114,5 +115,6 @@ void cgit_print_atom(char *tip, char *path, int max_count) html("</subtitle>\n"); 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); html_attr(cgit_repourl(ctx.repo->url)); @@ -28,5 +28,5 @@ void cgit_print_blob(const char *hex, char *path, const char *head) unsigned char sha1[20]; enum object_type type; - unsigned char *buf; + char *buf; unsigned long size; struct commit *commit; @@ -68,4 +68,10 @@ void cgit_print_blob(const char *hex, char *path, const char *head) buf[size] = '\0'; 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; cgit_print_http_headers(&ctx); diff --git a/ui-commit.c b/ui-commit.c index 41ce70e..d6b73ee 100644 --- a/ui-commit.c +++ b/ui-commit.c @@ -41,6 +41,8 @@ void cgit_print_commit(char *hex) html("<tr><th>author</th><td>"); 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'>"); cgit_print_date(info->author_date, FMT_LONGDATE, ctx.cfg.local_time); @@ -48,6 +50,8 @@ void cgit_print_commit(char *hex) html("<tr><th>committer</th><td>"); 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'>"); cgit_print_date(info->committer_date, FMT_LONGDATE, ctx.cfg.local_time); @@ -90,9 +94,17 @@ void cgit_print_commit(char *hex) html("</table>\n"); 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); html("</div>"); 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>"); if (parents < 3) { @@ -54,4 +54,8 @@ void show_commit_decorations(struct commit *commit) cgit_tag_link(buf, NULL, "tag-deco", ctx.qry.head, buf); } + 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/")) { strncpy(buf, deco->name + 13, sizeof(buf) - 1); @@ -109,5 +109,9 @@ void cgit_print_patch(char *hex) cgit_print_http_headers(&ctx); 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: "); cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time); @@ -18,6 +18,7 @@ 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; type = sha1_object_info(sha1, &size); @@ -32,7 +33,20 @@ static void print_object(const unsigned char *sha1, const char *path) return; } - 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); html_raw(buf, size); @@ -47,6 +47,17 @@ static int cmp_tag_age(const void *a, const void *b) struct refinfo *r1 = *(struct refinfo **)a; 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); } @@ -146,4 +157,10 @@ static int print_tag(struct refinfo *ref) else 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..7c7aa9b 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -19,17 +19,18 @@ 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; } @@ -275,5 +276,10 @@ void cgit_print_repolist() 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 @@ -35,22 +35,21 @@ 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)); } @@ -457,4 +456,9 @@ void cgit_print_age(time_t t, time_t max_relative, char *format) 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) htmlf("Content-Type: %s; charset=%s\n", ctx->page.mimetype, @@ -469,9 +473,19 @@ void cgit_print_http_headers(struct cgit_context *ctx) htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 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); } 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(); html(cgit_doctype); @@ -493,10 +507,13 @@ 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()); html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, 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"); html("<body>\n"); @@ -507,5 +524,11 @@ void cgit_print_docstart(struct cgit_context *ctx) 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) html_include(ctx.cfg.footer); @@ -516,4 +539,5 @@ void cgit_print_docend() html("</div>\n"); } + html("</div> <!-- id=cgit -->\n"); html("</body>\n</html>\n"); } @@ -603,11 +627,6 @@ 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"); html("<tr>\n"); @@ -653,4 +672,16 @@ 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); html("<table class='tabs'><tr><td>\n"); diff --git a/ui-shared.h b/ui-shared.h index 5a3821f..bff4826 100644 --- a/ui-shared.h +++ b/ui-shared.h @@ -2,4 +2,5 @@ #define UI_SHARED_H +extern char *cgit_httpscheme(); extern char *cgit_hosturl(); extern char *cgit_repourl(const char *reponame); diff --git a/ui-snapshot.c b/ui-snapshot.c index 5372f5d..4136b3e 100644 --- a/ui-snapshot.c +++ b/ui-snapshot.c @@ -13,35 +13,14 @@ static int write_compressed_tar_archive(struct archiver_args *args,const char *filter) { - 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-summary.c b/ui-summary.c index ede4a62..a2c018e 100644 --- a/ui-summary.c +++ b/ui-summary.c @@ -67,10 +67,26 @@ 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 @@ -3,5 +3,5 @@ extern void cgit_print_summary(); -extern void cgit_print_repo_readme(); +extern void cgit_print_repo_readme(char *path); #endif /* UI_SUMMARY_H */ @@ -68,5 +68,5 @@ void cgit_print_tag(char *revname) html("<tr><td>Tagged by</td><td>"); html_txt(info->tagger); - if (info->tagger_email) { + if (info->tagger_email && !ctx.cfg.noplainemail) { html(" "); html_txt(info->tagger_email); @@ -16,5 +16,5 @@ char *match_path; 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) { unsigned long lineno, idx; @@ -23,4 +23,14 @@ static void print_text_buffer(char *buf, unsigned long size) html("<table summary='blob content' class='blob'>\n"); + if (ctx.repo->source_filter) { + html("<tr><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("<tr><td class='linenumbers'><pre>"); idx = 0; @@ -66,5 +76,5 @@ 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) { enum object_type type; @@ -94,5 +104,5 @@ static void print_object(const unsigned char *sha1, char *path) print_binary_buffer(buf, size); else - print_text_buffer(buf, size); + print_text_buffer(basename, buf, size); } @@ -104,4 +114,5 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen, char *name; char *fullpath; + char *class; enum object_type type; unsigned long size = 0; @@ -136,5 +147,10 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen, curr_rev, fullpath); } 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); } @@ -214,5 +230,5 @@ static int walk_tree(const unsigned char *sha1, const char *base, int baselen, return READ_TREE_RECURSIVE; } else { - print_object(sha1, buffer); + print_object(sha1, buffer, pathname); return 0; } |