-rw-r--r-- | .gitignore | 5 | ||||
-rw-r--r-- | Makefile | 21 | ||||
-rw-r--r-- | cgit-doc.css | 3 | ||||
-rw-r--r-- | cgit.c | 87 | ||||
-rw-r--r-- | cgit.css | 2 | ||||
-rw-r--r-- | cgit.h | 41 | ||||
-rw-r--r-- | cgitrc.5.txt | 187 | ||||
-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-- | shared.c | 38 | ||||
-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 | 9 | ||||
-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 |
27 files changed, 553 insertions, 151 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.3.4 GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 @@ -102,3 +102,4 @@ endif -.PHONY: all libgit test install uninstall clean force-version get-git +.PHONY: all libgit test install uninstall clean force-version get-git \ + doc man-doc html-doc clean-doc @@ -151,5 +152,19 @@ uninstall: -clean: +doc: man-doc html-doc pdf-doc + +man-doc: cgitrc.5.txt + a2x -f manpage cgitrc.5.txt + +html-doc: cgitrc.5.txt + a2x -f xhtml --stylesheet=cgit-doc.css cgitrc.5.txt + +pdf-doc: cgitrc.5.txt + a2x -f pdf cgitrc.5.txt + +clean: clean-doc rm -f cgit VERSION *.o *.d +clean-doc: + rm -f cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo + get-git: diff --git a/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,2 +19,25 @@ 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) @@ -33,2 +56,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,2 +76,6 @@ 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")) @@ -73,2 +102,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 +116,4 @@ void config_cb(const char *name, const char *value) 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")) @@ -97,2 +134,4 @@ void config_cb(const char *name, const char *value) ctx.cfg.local_time = atoi(value); + else if (!prefixcmp(name, "mimetype.")) + add_mimetype(name + 9, value); else if (!strcmp(name, "repo.group")) @@ -123,2 +162,8 @@ void config_cb(const char *name, const char *value) 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) { @@ -175,2 +220,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) @@ -188,3 +238,3 @@ static void prepare_context(struct cgit_context *ctx) ctx->cfg.css = "/cgit.css"; - ctx->cfg.logo = "/git-logo.png"; + ctx->cfg.logo = "/cgit.png"; ctx->cfg.local_time = 0; @@ -205,2 +255,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 +271,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 +358,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); @@ -381,2 +451,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)) { @@ -433,3 +506,2 @@ int main(int argc, const char **argv) { - const char *cgit_config_env = getenv("CGIT_CONFIG"); const char *path; @@ -443,9 +515,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 +531,3 @@ int main(int argc, const char **argv) */ - path = getenv("PATH_INFO"); + path = ctx.env.path_info; if (!ctx.qry.url && path) { @@ -480,2 +547,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; } @@ -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 { @@ -66,2 +76,5 @@ struct cgit_repo { time_t mtime; + struct cgit_filter *about_filter; + struct cgit_filter *commit_filter; + struct cgit_filter *source_filter; }; @@ -138,2 +151,3 @@ struct cgit_config { char *footer; + char *head_include; char *header; @@ -157,2 +171,3 @@ struct cgit_config { int cache_static_ttl; + int embedded; int enable_index_links; @@ -168,2 +183,4 @@ struct cgit_config { int nocache; + int noplainemail; + int noheader; int renamelimit; @@ -173,2 +190,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 +203,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 +223,3 @@ struct cgit_page { struct cgit_context { + struct cgit_environment env; struct cgit_query qry; @@ -244,2 +282,5 @@ 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); + diff --git a/cgitrc.5.txt b/cgitrc.5.txt index fd299ae..3c35b02 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,3 @@ cache-root-ttl -cache-size +cache-size:: The maximum number of entries in the cgit cache. Default value: "0" @@ -46,3 +60,3 @@ cache-size -cache-static-ttl +cache-static-ttl:: Number which specifies the time-to-live, in minutes, for the cached @@ -51,3 +65,3 @@ cache-static-ttl -clone-prefix +clone-prefix:: Space-separated list of common prefixes which, when combined with a @@ -57,3 +71,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 +81,8 @@ 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 @@ -66,3 +91,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 +96,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 +101,3 @@ enable-log-linecount -favicon +favicon:: Url used as link to a shortcut icon for cgit. If specified, it is @@ -81,3 +106,3 @@ favicon -footer +footer:: The content of the file specified with this option will be included @@ -86,3 +111,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 +119,3 @@ header -include +include:: Name of a configfile to include before the rest of the current config- @@ -94,3 +123,3 @@ include -index-header +index-header:: The content of the file specified with this option will be included @@ -100,3 +129,3 @@ index-header -index-info +index-info:: The content of the file specified with this option will be included @@ -106,3 +135,3 @@ index-info -local-time +local-time:: Flag which, if set to "1", makes cgit print commit and tag times in the @@ -110,7 +139,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 +148,3 @@ logo-link -max-commit-count +max-commit-count:: Specifies the number of entries to list per page in "log" view. Default @@ -123,3 +152,3 @@ max-commit-count -max-message-length +max-message-length:: Specifies the maximum number of commit message characters to display in @@ -127,3 +156,3 @@ max-message-length -max-repo-count +max-repo-count:: Specifies the number of entries to list per page on the repository @@ -131,3 +160,3 @@ max-repo-count -max-repodesc-length +max-repodesc-length:: Specifies the maximum number of repo description characters to display @@ -135,3 +164,3 @@ max-repodesc-length -max-stats +max-stats:: Set the default maximum statistics period. Valid values are "week", @@ -140,3 +169,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 +179,3 @@ module-link -nocache +nocache:: If set to the value "1" caching will be disabled. This settings is @@ -151,3 +184,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,3 +197,3 @@ renamelimit -repo.group +repo.group:: A value for the current repository group, which all repositories @@ -160,3 +201,3 @@ repo.group -robots +robots:: Text used as content for the "robots" meta-tag. Default value: @@ -164,3 +205,3 @@ robots -root-desc +root-desc:: Text printed below the heading on the repository index page. Default @@ -168,3 +209,3 @@ root-desc -root-readme: +root-readme:: The content of the file specified with this option will be included @@ -173,3 +214,3 @@ root-readme: -root-title +root-title:: Text printed as heading on the repository index page. Default value: @@ -177,3 +218,3 @@ root-title -snapshots +snapshots:: Text which specifies the default (and allowed) set of snapshot formats @@ -187,3 +228,11 @@ snapshots -summary-branches +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 +240,3 @@ summary-branches -summary-log +summary-log:: Specifies the number of log entries to display in the repository @@ -195,3 +244,3 @@ summary-log -summary-tags +summary-tags:: Specifies the number of tags to display in the repository "summary" @@ -199,3 +248,3 @@ summary-tags -virtual-root +virtual-root:: Url which, if specified, will be used as root for all cgit links. It @@ -209,3 +258,6 @@ 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. @@ -213,3 +265,6 @@ repo.clone-url -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 @@ -218,6 +273,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 +280,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 +284,3 @@ repo.enable-log-linecount -repo.max-stats +repo.max-stats:: Override the default maximum statistics period. Valid values are equal @@ -234,6 +289,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 +296,3 @@ repo.owner -repo.path +repo.path:: An absolute path to the repository directory. For non-bare repositories @@ -245,3 +300,3 @@ repo.path -repo.readme +repo.readme:: A path (relative to <repo.path>) which specifies a file to include @@ -249,3 +304,3 @@ repo.readme -repo.snapshots +repo.snapshots:: A mask of allowed snapshot-formats for this repo, restricted by the @@ -253,3 +308,6 @@ repo.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 @@ -261,2 +319,3 @@ EXAMPLE CGITRC FILE +.... # Enable caching of up to 1000 output entriess @@ -313,2 +372,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 +442,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 e276f018f2c1f0fc962fbe44a36708d1cdebada @@ -64,2 +64,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 +360,36 @@ 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); +} @@ -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..d6b73ee 100644 --- a/ui-commit.c +++ b/ui-commit.c @@ -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/")) { @@ -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 2c13d50..25f076f 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -275,4 +275,9 @@ void cgit_print_site_readme() { - if (ctx.cfg.root_readme) - html_include(ctx.cfg.root_readme); + if (!ctx.cfg.root_readme) + return; + if (ctx.cfg.about_filter) + cgit_open_filter(ctx.cfg.about_filter); + html_include(ctx.cfg.root_readme); + if (ctx.cfg.about_filter) + cgit_close_filter(ctx.cfg.about_filter); } diff --git a/ui-shared.c b/ui-shared.c index 40060ba..07d5dd4 100644 --- a/ui-shared.c +++ b/ui-shared.c @@ -36,20 +36,19 @@ void cgit_print_error(char *msg) -char *cgit_hosturl() +char *cgit_httpscheme() { - char *host, *port; + if (ctx.env.https && !strcmp(ctx.env.https, "on")) + return "https://"; + else + return "http://"; +} - host = getenv("HTTP_HOST"); - if (host) { - host = xstrdup(host); - } else { - host = getenv("SERVER_NAME"); - if (!host) - return NULL; - port = getenv("SERVER_PORT"); - if (port && atoi(port) != 80) - host = xstrdup(fmt("%s:%d", host, atoi(port))); - else - host = xstrdup(host); - } - return host; +char *cgit_hosturl() +{ + if (ctx.env.http_host) + return ctx.env.http_host; + if (!ctx.env.server_name) + return NULL; + if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80) + return ctx.env.server_name; + return xstrdup(fmt("%s:%s", ctx.env.server_name, ctx.env.server_port)); } @@ -458,2 +457,7 @@ void cgit_print_http_headers(struct cgit_context *ctx) { + if (ctx->env.no_http && !strcmp(ctx->env.no_http, "1")) + return; + + if (ctx->page.status) + htmlf("Status: %d %s\n", ctx->page.status, ctx->page.statusmsg); if (ctx->page.mimetype && ctx->page.charset) @@ -470,3 +474,7 @@ void cgit_print_http_headers(struct cgit_context *ctx) htmlf("Expires: %s\n", http_date(ctx->page.expires)); + if (ctx->page.etag) + htmlf("ETag: \"%s\"\n", ctx->page.etag); html("\n"); + if (ctx->env.request_method && !strcmp(ctx->env.request_method, "HEAD")) + exit(0); } @@ -475,2 +483,8 @@ void cgit_print_docstart(struct cgit_context *ctx) { + if (ctx->cfg.embedded) { + if (ctx->cfg.header) + html_include(ctx->cfg.header); + return; + } + char *host = cgit_hosturl(); @@ -494,3 +508,4 @@ void cgit_print_docstart(struct cgit_context *ctx) if (host && ctx->repo) { - html("<link rel='alternate' title='Atom feed' href='http://"); + html("<link rel='alternate' title='Atom feed' href='"); + html(cgit_httpscheme()); html_attr(cgit_hosturl()); @@ -498,4 +513,6 @@ void cgit_print_docstart(struct cgit_context *ctx) fmt("h=%s", ctx->qry.head))); - html("' type='application/atom+xml'/>"); + html("' type='application/atom+xml'/>\n"); } + if (ctx->cfg.head_include) + html_include(ctx->cfg.head_include); html("</head>\n"); @@ -508,3 +525,9 @@ void cgit_print_docend() { - html("</div>"); + html("</div> <!-- class=content -->\n"); + if (ctx.cfg.embedded) { + html("</div> <!-- id=cgit -->\n"); + if (ctx.cfg.footer) + html_include(ctx.cfg.footer); + return; + } if (ctx.cfg.footer) @@ -517,2 +540,3 @@ void cgit_print_docend() } + html("</div> <!-- id=cgit -->\n"); html("</body>\n</html>\n"); @@ -604,9 +628,4 @@ char *hc(struct cgit_cmd *cmd, const char *page) -void cgit_print_pageheader(struct cgit_context *ctx) +static void print_header(struct cgit_context *ctx) { - struct cgit_cmd *cmd = cgit_get_cmd(ctx); - - if (!cmd && ctx->repo) - fallback_cmd = "summary"; - html("<table id='header'>\n"); @@ -654,2 +673,14 @@ void cgit_print_pageheader(struct cgit_context *ctx) html("</td></tr></table>\n"); +} + +void cgit_print_pageheader(struct cgit_context *ctx) +{ + struct cgit_cmd *cmd = cgit_get_cmd(ctx); + + if (!cmd && ctx->repo) + fallback_cmd = "summary"; + + html("<div id='cgit'>"); + if (!ctx->cfg.noheader) + print_header(ctx); diff --git a/ui-shared.h b/ui-shared.h index 5a3821f..bff4826 100644 --- a/ui-shared.h +++ b/ui-shared.h @@ -3,2 +3,3 @@ +extern char *cgit_httpscheme(); extern char *cgit_hosturl(); diff --git a/ui-snapshot.c b/ui-snapshot.c index 5372f5d..4136b3e 100644 --- a/ui-snapshot.c +++ b/ui-snapshot.c @@ -14,33 +14,12 @@ static int write_compressed_tar_archive(struct archiver_args *args,const char *f { - int rw[2]; - pid_t gzpid; - int stdout2; - int status; int rv; + struct cgit_filter f; - stdout2 = chk_non_negative(dup(STDIN_FILENO), "Preserving STDOUT before compressing"); - chk_zero(pipe(rw), "Opening pipe from compressor subprocess"); - gzpid = chk_non_negative(fork(), "Forking compressor subprocess"); - if(gzpid==0) { - /* child */ - chk_zero(close(rw[1]), "Closing write end of pipe in child"); - chk_zero(close(STDIN_FILENO), "Closing STDIN"); - chk_non_negative(dup2(rw[0],STDIN_FILENO), "Redirecting compressor input to stdin"); - execlp(filter,filter,NULL); - _exit(-1); - } - /* parent */ - chk_zero(close(rw[0]), "Closing read end of pipe"); - chk_non_negative(dup2(rw[1],STDOUT_FILENO), "Redirecting output to compressor"); - + f.cmd = xstrdup(filter); + f.argv = malloc(2 * sizeof(char *)); + f.argv[0] = f.cmd; + f.argv[1] = NULL; + cgit_open_filter(&f); rv = write_tar_archive(args); - - chk_zero(close(STDOUT_FILENO), "Closing STDOUT redirected to compressor"); - chk_non_negative(dup2(stdout2,STDOUT_FILENO), "Restoring uncompressed STDOUT"); - chk_zero(close(stdout2), "Closing uncompressed STDOUT"); - chk_zero(close(rw[1]), "Closing write end of pipe in parent"); - chk_positive(waitpid(gzpid,&status,0), "Waiting on compressor process"); - if(! ( WIFEXITED(status) && WEXITSTATUS(status)==0 ) ) - cgit_print_error("Failed to compress archive"); - + cgit_close_filter(&f); return rv; diff --git a/ui-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,2 +24,12 @@ 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>"); @@ -67,3 +77,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 +105,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 +115,3 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen, char *fullpath; + char *class; enum object_type type; @@ -137,3 +148,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 +231,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; |