-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
@@ -1,6 +1,11 @@ # Files I don't care to see in git-status/commit cgit cgit.conf VERSION +cgitrc.5 +cgitrc.5.fo +cgitrc.5.html +cgitrc.5.pdf +cgitrc.5.xml *.o *.d @@ -1,156 +1,171 @@ CGIT_VERSION = v0.8.2.1 CGIT_SCRIPT_NAME = cgit.cgi CGIT_SCRIPT_PATH = /var/www/htdocs/cgit CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH) 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 # Define NO_STRCASESTR if you don't have strcasestr. # # Define NEEDS_LIBICONV if linking with libc is not enough (eg. Darwin). # #-include config.mak # # Platform specific tweaks # uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not') uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not') ifeq ($(uname_O),Cygwin) NO_STRCASESTR = YesPlease NEEDS_LIBICONV = YesPlease endif # # Let the user override the above settings. # -include cgit.conf # # Define a way to invoke make in subdirs quietly, shamelessly ripped # from git.git # QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir QUIET_SUBDIR1 = ifneq ($(findstring $(MAKEFLAGS),w),w) PRINT_DIR = --no-print-directory else # "make -w" NO_SUBDIR = : endif ifndef V QUIET_CC = @echo ' ' CC $@; QUIET_MM = @echo ' ' MM $@; QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir endif # # Define a pattern rule for automatic dependency building # %.d: %.c $(QUIET_MM)$(CC) $(CFLAGS) -MM $< | sed -e 's/\($*\)\.o:/\1.o $@:/g' >$@ # # Define a pattern rule for silent object building # %.o: %.c $(QUIET_CC)$(CC) -o $*.o -c $(CFLAGS) $< EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lcrypto OBJECTS = OBJECTS += cache.o OBJECTS += cgit.o OBJECTS += cmd.o OBJECTS += configfile.o OBJECTS += html.o OBJECTS += parsing.o OBJECTS += scan-tree.o OBJECTS += shared.o OBJECTS += ui-atom.o OBJECTS += ui-blob.o OBJECTS += ui-clone.o OBJECTS += ui-commit.o OBJECTS += ui-diff.o OBJECTS += ui-log.o OBJECTS += ui-patch.o OBJECTS += ui-plain.o OBJECTS += ui-refs.o OBJECTS += ui-repolist.o OBJECTS += ui-shared.o OBJECTS += ui-snapshot.o OBJECTS += ui-stats.o OBJECTS += ui-summary.o OBJECTS += ui-tag.o OBJECTS += ui-tree.o ifdef NEEDS_LIBICONV EXTLIBS += -liconv 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 VERSION: force-version @./gen-version.sh "$(CGIT_VERSION)" -include VERSION CFLAGS += -g -Wall -Igit CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER)' CFLAGS += -DCGIT_VERSION='"$(CGIT_VERSION)"' CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"' CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"' CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"' ifdef NO_ICONV CFLAGS += -DNO_ICONV endif ifdef NO_STRCASESTR CFLAGS += -DNO_STRCASESTR endif cgit: $(OBJECTS) libgit $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) cgit.o: VERSION -include $(OBJECTS:.o=.d) libgit: $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a test: all $(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all install: all $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH) $(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) $(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH) $(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css $(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png uninstall: rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) rm -f $(CGIT_DATA_PATH)/cgit.css 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; +} @@ -1,489 +1,558 @@ /* cgit.c: cgi for the git scm * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "cache.h" #include "cmd.h" #include "configfile.h" #include "html.h" #include "ui-shared.h" #include "ui-stats.h" #include "scan-tree.h" const char *cgit_version = CGIT_VERSION; +void add_mimetype(const char *name, const char *value) +{ + struct string_list_item *item; + + item = string_list_insert(xstrdup(name), &ctx.cfg.mimetypes); + item->util = xstrdup(value); +} + +struct cgit_filter *new_filter(const char *cmd, int extra_args) +{ + struct cgit_filter *f; + + if (!cmd || !cmd[0]) + return NULL; + + f = xmalloc(sizeof(struct cgit_filter)); + f->cmd = xstrdup(cmd); + f->argv = xmalloc((2 + extra_args) * sizeof(char *)); + f->argv[0] = f->cmd; + f->argv[1] = NULL; + return f; +} + void config_cb(const char *name, const char *value) { if (!strcmp(name, "root-title")) ctx.cfg.root_title = xstrdup(value); else if (!strcmp(name, "root-desc")) ctx.cfg.root_desc = xstrdup(value); else if (!strcmp(name, "root-readme")) ctx.cfg.root_readme = xstrdup(value); else if (!strcmp(name, "css")) ctx.cfg.css = xstrdup(value); else if (!strcmp(name, "favicon")) ctx.cfg.favicon = xstrdup(value); else if (!strcmp(name, "footer")) ctx.cfg.footer = xstrdup(value); + else if (!strcmp(name, "head-include")) + ctx.cfg.head_include = xstrdup(value); else if (!strcmp(name, "header")) ctx.cfg.header = xstrdup(value); else if (!strcmp(name, "logo")) ctx.cfg.logo = xstrdup(value); else if (!strcmp(name, "index-header")) ctx.cfg.index_header = xstrdup(value); else if (!strcmp(name, "index-info")) ctx.cfg.index_info = xstrdup(value); else if (!strcmp(name, "logo-link")) ctx.cfg.logo_link = xstrdup(value); else if (!strcmp(name, "module-link")) ctx.cfg.module_link = xstrdup(value); else if (!strcmp(name, "virtual-root")) { ctx.cfg.virtual_root = trim_end(value, '/'); if (!ctx.cfg.virtual_root && (!strcmp(value, "/"))) ctx.cfg.virtual_root = ""; } else if (!strcmp(name, "nocache")) ctx.cfg.nocache = atoi(value); + else if (!strcmp(name, "noplainemail")) + ctx.cfg.noplainemail = atoi(value); + else if (!strcmp(name, "noheader")) + ctx.cfg.noheader = atoi(value); else if (!strcmp(name, "snapshots")) ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); else if (!strcmp(name, "enable-index-links")) ctx.cfg.enable_index_links = atoi(value); else if (!strcmp(name, "enable-log-filecount")) ctx.cfg.enable_log_filecount = atoi(value); else if (!strcmp(name, "enable-log-linecount")) ctx.cfg.enable_log_linecount = atoi(value); else if (!strcmp(name, "max-stats")) ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); else if (!strcmp(name, "cache-size")) ctx.cfg.cache_size = atoi(value); else if (!strcmp(name, "cache-root")) ctx.cfg.cache_root = xstrdup(value); else if (!strcmp(name, "cache-root-ttl")) ctx.cfg.cache_root_ttl = atoi(value); else if (!strcmp(name, "cache-repo-ttl")) ctx.cfg.cache_repo_ttl = atoi(value); else if (!strcmp(name, "cache-static-ttl")) ctx.cfg.cache_static_ttl = atoi(value); else if (!strcmp(name, "cache-dynamic-ttl")) ctx.cfg.cache_dynamic_ttl = atoi(value); + else if (!strcmp(name, "about-filter")) + ctx.cfg.about_filter = new_filter(value, 0); + else if (!strcmp(name, "commit-filter")) + ctx.cfg.commit_filter = new_filter(value, 0); + else if (!strcmp(name, "embedded")) + ctx.cfg.embedded = atoi(value); else if (!strcmp(name, "max-message-length")) ctx.cfg.max_msg_len = atoi(value); else if (!strcmp(name, "max-repodesc-length")) ctx.cfg.max_repodesc_len = atoi(value); else if (!strcmp(name, "max-repo-count")) ctx.cfg.max_repo_count = atoi(value); else if (!strcmp(name, "max-commit-count")) ctx.cfg.max_commit_count = atoi(value); + else if (!strcmp(name, "source-filter")) + ctx.cfg.source_filter = new_filter(value, 1); else if (!strcmp(name, "summary-log")) ctx.cfg.summary_log = atoi(value); else if (!strcmp(name, "summary-branches")) ctx.cfg.summary_branches = atoi(value); else if (!strcmp(name, "summary-tags")) ctx.cfg.summary_tags = atoi(value); else if (!strcmp(name, "agefile")) ctx.cfg.agefile = xstrdup(value); else if (!strcmp(name, "renamelimit")) ctx.cfg.renamelimit = atoi(value); else if (!strcmp(name, "robots")) ctx.cfg.robots = xstrdup(value); else if (!strcmp(name, "clone-prefix")) ctx.cfg.clone_prefix = xstrdup(value); else if (!strcmp(name, "local-time")) ctx.cfg.local_time = atoi(value); + else if (!prefixcmp(name, "mimetype.")) + add_mimetype(name + 9, value); else if (!strcmp(name, "repo.group")) ctx.cfg.repo_group = xstrdup(value); else if (!strcmp(name, "repo.url")) ctx.repo = cgit_add_repo(value); else if (!strcmp(name, "repo.name")) ctx.repo->name = xstrdup(value); else if (ctx.repo && !strcmp(name, "repo.path")) ctx.repo->path = trim_end(value, '/'); else if (ctx.repo && !strcmp(name, "repo.clone-url")) ctx.repo->clone_url = xstrdup(value); else if (ctx.repo && !strcmp(name, "repo.desc")) ctx.repo->desc = xstrdup(value); else if (ctx.repo && !strcmp(name, "repo.owner")) ctx.repo->owner = xstrdup(value); else if (ctx.repo && !strcmp(name, "repo.defbranch")) ctx.repo->defbranch = xstrdup(value); else if (ctx.repo && !strcmp(name, "repo.snapshots")) ctx.repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); /* XXX: &? */ else if (ctx.repo && !strcmp(name, "repo.enable-log-filecount")) ctx.repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); else if (ctx.repo && !strcmp(name, "repo.enable-log-linecount")) ctx.repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); else if (ctx.repo && !strcmp(name, "repo.max-stats")) ctx.repo->max_stats = cgit_find_stats_period(value, NULL); else if (ctx.repo && !strcmp(name, "repo.module-link")) ctx.repo->module_link= xstrdup(value); + else if (ctx.repo && !strcmp(name, "repo.about-filter")) + ctx.repo->about_filter = new_filter(value, 0); + else if (ctx.repo && !strcmp(name, "repo.commit-filter")) + ctx.repo->commit_filter = new_filter(value, 0); + else if (ctx.repo && !strcmp(name, "repo.source-filter")) + ctx.repo->source_filter = new_filter(value, 1); else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { if (*value == '/') ctx.repo->readme = xstrdup(value); else ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); } else if (!strcmp(name, "include")) parse_configfile(value, config_cb); } static void querystring_cb(const char *name, const char *value) { if (!value) value = ""; if (!strcmp(name,"r")) { ctx.qry.repo = xstrdup(value); ctx.repo = cgit_get_repoinfo(value); } else if (!strcmp(name, "p")) { ctx.qry.page = xstrdup(value); } else if (!strcmp(name, "url")) { ctx.qry.url = xstrdup(value); cgit_parse_url(value); } else if (!strcmp(name, "qt")) { ctx.qry.grep = xstrdup(value); } else if (!strcmp(name, "q")) { ctx.qry.search = xstrdup(value); } else if (!strcmp(name, "h")) { ctx.qry.head = xstrdup(value); ctx.qry.has_symref = 1; } else if (!strcmp(name, "id")) { ctx.qry.sha1 = xstrdup(value); ctx.qry.has_sha1 = 1; } else if (!strcmp(name, "id2")) { ctx.qry.sha2 = xstrdup(value); ctx.qry.has_sha1 = 1; } else if (!strcmp(name, "ofs")) { ctx.qry.ofs = atoi(value); } else if (!strcmp(name, "path")) { ctx.qry.path = trim_end(value, '/'); } else if (!strcmp(name, "name")) { ctx.qry.name = xstrdup(value); } else if (!strcmp(name, "mimetype")) { ctx.qry.mimetype = xstrdup(value); } else if (!strcmp(name, "s")){ ctx.qry.sort = xstrdup(value); } else if (!strcmp(name, "showmsg")) { ctx.qry.showmsg = atoi(value); } else if (!strcmp(name, "period")) { ctx.qry.period = xstrdup(value); } } +char *xstrdupn(const char *str) +{ + return (str ? xstrdup(str) : NULL); +} + static void prepare_context(struct cgit_context *ctx) { memset(ctx, 0, sizeof(ctx)); ctx->cfg.agefile = "info/web/last-modified"; ctx->cfg.nocache = 0; ctx->cfg.cache_size = 0; ctx->cfg.cache_dynamic_ttl = 5; ctx->cfg.cache_max_create_time = 5; ctx->cfg.cache_repo_ttl = 5; ctx->cfg.cache_root = CGIT_CACHE_ROOT; ctx->cfg.cache_root_ttl = 5; ctx->cfg.cache_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; ctx->cfg.max_commit_count = 50; ctx->cfg.max_lock_attempts = 5; ctx->cfg.max_msg_len = 80; ctx->cfg.max_repodesc_len = 80; ctx->cfg.max_stats = 0; ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; ctx->cfg.renamelimit = -1; ctx->cfg.robots = "index, nofollow"; ctx->cfg.root_title = "Git repository browser"; ctx->cfg.root_desc = "a fast webinterface for the git dscm"; ctx->cfg.script_name = CGIT_SCRIPT_NAME; ctx->cfg.summary_branches = 10; ctx->cfg.summary_log = 10; ctx->cfg.summary_tags = 10; + ctx->env.cgit_config = xstrdupn(getenv("CGIT_CONFIG")); + ctx->env.http_host = xstrdupn(getenv("HTTP_HOST")); + ctx->env.https = xstrdupn(getenv("HTTPS")); + ctx->env.no_http = xstrdupn(getenv("NO_HTTP")); + ctx->env.path_info = xstrdupn(getenv("PATH_INFO")); + ctx->env.query_string = xstrdupn(getenv("QUERY_STRING")); + ctx->env.request_method = xstrdupn(getenv("REQUEST_METHOD")); + ctx->env.script_name = xstrdupn(getenv("SCRIPT_NAME")); + ctx->env.server_name = xstrdupn(getenv("SERVER_NAME")); + ctx->env.server_port = xstrdupn(getenv("SERVER_PORT")); ctx->page.mimetype = "text/html"; ctx->page.charset = PAGE_ENCODING; ctx->page.filename = NULL; ctx->page.size = 0; ctx->page.modified = time(NULL); ctx->page.expires = ctx->page.modified; + ctx->page.etag = NULL; + memset(&ctx->cfg.mimetypes, 0, sizeof(struct string_list)); + if (ctx->env.script_name) + ctx->cfg.script_name = ctx->env.script_name; + if (ctx->env.query_string) + ctx->qry.raw = ctx->env.query_string; + if (!ctx->env.cgit_config) + ctx->env.cgit_config = CGIT_CONFIG; } struct refmatch { char *req_ref; char *first_ref; int match; }; int find_current_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct refmatch *info; info = (struct refmatch *)cb_data; if (!strcmp(refname, info->req_ref)) info->match = 1; if (!info->first_ref) info->first_ref = xstrdup(refname); return info->match; } char *find_default_branch(struct cgit_repo *repo) { struct refmatch info; char *ref; info.req_ref = repo->defbranch; info.first_ref = NULL; info.match = 0; for_each_branch_ref(find_current_ref, &info); if (info.match) ref = info.req_ref; else ref = info.first_ref; if (ref) ref = xstrdup(ref); return ref; } static int prepare_repo_cmd(struct cgit_context *ctx) { char *tmp; unsigned char sha1[20]; int nongit = 0; setenv("GIT_DIR", ctx->repo->path, 1); setup_git_directory_gently(&nongit); if (nongit) { ctx->page.title = fmt("%s - %s", ctx->cfg.root_title, "config error"); tmp = fmt("Not a git repository: '%s'", ctx->repo->path); ctx->repo = NULL; cgit_print_http_headers(ctx); cgit_print_docstart(ctx); cgit_print_pageheader(ctx); cgit_print_error(tmp); cgit_print_docend(); return 1; } ctx->page.title = fmt("%s - %s", ctx->repo->name, ctx->repo->desc); if (!ctx->qry.head) { ctx->qry.nohead = 1; ctx->qry.head = find_default_branch(ctx->repo); ctx->repo->defbranch = ctx->qry.head; } if (!ctx->qry.head) { cgit_print_http_headers(ctx); cgit_print_docstart(ctx); cgit_print_pageheader(ctx); cgit_print_error("Repository seems to be empty"); cgit_print_docend(); return 1; } if (get_sha1(ctx->qry.head, sha1)) { tmp = xstrdup(ctx->qry.head); ctx->qry.head = ctx->repo->defbranch; + ctx->page.status = 404; + ctx->page.statusmsg = "not found"; cgit_print_http_headers(ctx); cgit_print_docstart(ctx); cgit_print_pageheader(ctx); cgit_print_error(fmt("Invalid branch: %s", tmp)); cgit_print_docend(); return 1; } return 0; } static void process_request(void *cbdata) { struct cgit_context *ctx = cbdata; struct cgit_cmd *cmd; cmd = cgit_get_cmd(ctx); if (!cmd) { ctx->page.title = "cgit error"; cgit_print_http_headers(ctx); cgit_print_docstart(ctx); cgit_print_pageheader(ctx); cgit_print_error("Invalid request"); cgit_print_docend(); return; } if (cmd->want_repo && !ctx->repo) { cgit_print_http_headers(ctx); cgit_print_docstart(ctx); cgit_print_pageheader(ctx); cgit_print_error(fmt("No repository selected")); cgit_print_docend(); return; } if (ctx->repo && prepare_repo_cmd(ctx)) return; if (cmd->want_layout) { cgit_print_http_headers(ctx); cgit_print_docstart(ctx); cgit_print_pageheader(ctx); } cmd->fn(ctx); if (cmd->want_layout) cgit_print_docend(); } int cmp_repos(const void *a, const void *b) { const struct cgit_repo *ra = a, *rb = b; return strcmp(ra->url, rb->url); } void print_repo(struct cgit_repo *repo) { printf("repo.url=%s\n", repo->url); printf("repo.name=%s\n", repo->name); printf("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); if (repo->readme) printf("repo.readme=%s\n", repo->readme); printf("\n"); } void print_repolist(struct cgit_repolist *list) { int i; for(i = 0; i < list->count; i++) print_repo(&list->repos[i]); } static void cgit_parse_args(int argc, const char **argv) { int i; int scan = 0; for (i = 1; i < argc; i++) { if (!strncmp(argv[i], "--cache=", 8)) { ctx.cfg.cache_root = xstrdup(argv[i]+8); } if (!strcmp(argv[i], "--nocache")) { ctx.cfg.nocache = 1; } + if (!strcmp(argv[i], "--nohttp")) { + ctx.env.no_http = "1"; + } if (!strncmp(argv[i], "--query=", 8)) { ctx.qry.raw = xstrdup(argv[i]+8); } if (!strncmp(argv[i], "--repo=", 7)) { ctx.qry.repo = xstrdup(argv[i]+7); } if (!strncmp(argv[i], "--page=", 7)) { ctx.qry.page = xstrdup(argv[i]+7); } if (!strncmp(argv[i], "--head=", 7)) { ctx.qry.head = xstrdup(argv[i]+7); ctx.qry.has_symref = 1; } if (!strncmp(argv[i], "--sha1=", 7)) { ctx.qry.sha1 = xstrdup(argv[i]+7); ctx.qry.has_sha1 = 1; } if (!strncmp(argv[i], "--ofs=", 6)) { ctx.qry.ofs = atoi(argv[i]+6); } if (!strncmp(argv[i], "--scan-tree=", 12)) { scan++; scan_tree(argv[i] + 12); } } if (scan) { qsort(cgit_repolist.repos, cgit_repolist.count, sizeof(struct cgit_repo), cmp_repos); print_repolist(&cgit_repolist); exit(0); } } static int calc_ttl() { if (!ctx.repo) return ctx.cfg.cache_root_ttl; if (!ctx.qry.page) return ctx.cfg.cache_repo_ttl; if (ctx.qry.has_symref) return ctx.cfg.cache_dynamic_ttl; if (ctx.qry.has_sha1) return ctx.cfg.cache_static_ttl; return ctx.cfg.cache_repo_ttl; } int main(int argc, const char **argv) { - const char *cgit_config_env = getenv("CGIT_CONFIG"); const char *path; char *qry; int err, ttl; prepare_context(&ctx); cgit_repolist.length = 0; cgit_repolist.count = 0; 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); /* If virtual-root isn't specified in cgitrc, lets pretend * that virtual-root equals SCRIPT_NAME. */ if (!ctx.cfg.virtual_root) ctx.cfg.virtual_root = ctx.cfg.script_name; /* If no url parameter is specified on the querystring, lets * use PATH_INFO as url. This allows cgit to work with virtual * urls without the need for rewriterules in the webserver (as * long as PATH_INFO is included in the cache lookup key). */ - path = getenv("PATH_INFO"); + path = ctx.env.path_info; if (!ctx.qry.url && path) { if (path[0] == '/') path++; ctx.qry.url = xstrdup(path); if (ctx.qry.raw) { qry = ctx.qry.raw; ctx.qry.raw = xstrdup(fmt("%s?%s", path, qry)); free(qry); } else ctx.qry.raw = ctx.qry.url; cgit_parse_url(ctx.qry.url); } ttl = calc_ttl(); ctx.page.expires += ttl*60; + if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) + ctx.cfg.nocache = 1; if (ctx.cfg.nocache) ctx.cfg.cache_size = 0; err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, ctx.qry.raw, ttl, process_request, &ctx); if (err) cgit_print_error(fmt("Error processing page: %s (%d)", strerror(err), err)); return err; } @@ -1,603 +1,603 @@ body, table, form { padding: 0em; margin: 0em; } body { font-family: sans-serif; font-size: 10pt; color: #333; background: white; padding: 4px; } a { color: blue; text-decoration: none; } a:hover { text-decoration: underline; } table { border-collapse: collapse; } table#header { width: 100%; margin-bottom: 1em; } table#header td.logo { width: 96px; } table#header td.main { font-size: 250%; padding-left: 10px; white-space: nowrap; } table#header td.main a { color: #000; } table#header td.form { text-align: right; vertical-align: bottom; padding-right: 1em; padding-bottom: 2px; white-space: nowrap; } table#header td.form form, table#header td.form input, table#header td.form select { font-size: 90%; } table#header td.sub { color: #777; border-top: solid 1px #ccc; padding-left: 10px; } table.tabs { /* border-bottom: solid 2px #ccc; */ border-collapse: collapse; margin-top: 2em; margin-bottom: 0px; width: 100%; } table.tabs td { padding: 0px 1em; vertical-align: bottom; } table.tabs td a { padding: 2px 0.75em; color: #777; font-size: 110%; } table.tabs td a.active { color: #000; background-color: #ccc; } table.tabs td.form { text-align: right; } table.tabs td.form form { padding-bottom: 2px; font-size: 90%; white-space: nowrap; } table.tabs td.form input, table.tabs td.form select { font-size: 90%; } div.content { margin: 0px; padding: 2em; border-top: solid 3px #ccc; border-bottom: solid 3px #ccc; } table.list { width: 100%; border: none; border-collapse: collapse; } table.list tr { background: white; } table.list tr.logheader { background: #eee; } table.list tr:hover { background: #eee; } table.list tr.nohover:hover { background: white; } table.list th { font-weight: bold; /* color: #888; border-top: dashed 1px #888; border-bottom: dashed 1px #888; */ padding: 0.1em 0.5em 0.05em 0.5em; vertical-align: baseline; } table.list td { border: none; padding: 0.1em 0.5em 0.1em 0.5em; } table.list td.logsubject { font-family: monospace; font-weight: bold; } table.list td.logmsg { font-family: monospace; white-space: pre; - padding: 1em 0em 2em 0em; + padding: 1em 0.5em 2em 0.5em; } table.list td a { color: black; } table.list td a:hover { color: #00f; } img { border: none; } input#switch-btn { margin: 2px 0px 0px 0px; } td#sidebar input.txt { width: 100%; margin: 2px 0px 0px 0px; } table#grid { margin: 0px; } td#content { vertical-align: top; padding: 1em 2em 1em 1em; border: none; } div#summary { vertical-align: top; margin-bottom: 1em; } table#downloads { float: right; border-collapse: collapse; border: solid 1px #777; margin-left: 0.5em; margin-bottom: 0.5em; } table#downloads th { background-color: #ccc; } div#blob { border: solid 1px black; } div.error { color: red; font-weight: bold; margin: 1em 2em; } a.ls-blob, a.ls-dir, a.ls-mod { font-family: monospace; } td.ls-size { text-align: right; font-family: monospace; width: 10em; } td.ls-mode { font-family: monospace; width: 10em; } table.blob { margin-top: 0.5em; border-top: solid 1px black; } table.blob td.lines { margin: 0; padding: 0; vertical-align: top; color: black; } table.blob td.linenumbers { margin: 0; padding: 0; vertical-align: top; border-right: 1px solid gray; background-color: #eee; } table.blob pre { padding: 0; margin: 0; } table.blob a.no { color: gray; text-align: right; text-decoration: none; } table.blob a.no a:hover { color: black; } table.bin-blob { margin-top: 0.5em; border: solid 1px black; } table.bin-blob th { font-family: monospace; white-space: pre; border: solid 1px #777; padding: 0.5em 1em; } table.bin-blob td { font-family: monospace; white-space: pre; border-left: solid 1px #777; padding: 0em 1em; } table.nowrap td { white-space: nowrap; } table.commit-info { border-collapse: collapse; margin-top: 1.5em; } table.commit-info th { text-align: left; font-weight: normal; padding: 0.1em 1em 0.1em 0.1em; vertical-align: top; } table.commit-info td { font-weight: normal; padding: 0.1em 1em 0.1em 0.1em; } div.commit-subject { font-weight: bold; font-size: 125%; margin: 1.5em 0em 0.5em 0em; padding: 0em; } div.commit-msg { white-space: pre; font-family: monospace; } div.diffstat-header { font-weight: bold; padding-top: 1.5em; } table.diffstat { border-collapse: collapse; border: solid 1px #aaa; background-color: #eee; } table.diffstat th { font-weight: normal; text-align: left; text-decoration: underline; padding: 0.1em 1em 0.1em 0.1em; font-size: 100%; } table.diffstat td { padding: 0.2em 0.2em 0.1em 0.1em; font-size: 100%; border: none; } table.diffstat td.mode { white-space: nowrap; } table.diffstat td span.modechange { padding-left: 1em; color: red; } table.diffstat td.add a { color: green; } table.diffstat td.del a { color: red; } table.diffstat td.upd a { color: blue; } table.diffstat td.graph { width: 500px; vertical-align: middle; } table.diffstat td.graph table { border: none; } table.diffstat td.graph td { padding: 0px; border: 0px; height: 7pt; } table.diffstat td.graph td.add { background-color: #5c5; } table.diffstat td.graph td.rem { background-color: #c55; } div.diffstat-summary { color: #888; padding-top: 0.5em; } table.diff { width: 100%; } table.diff td { font-family: monospace; white-space: pre; } table.diff td div.head { font-weight: bold; margin-top: 1em; color: black; } table.diff td div.hunk { color: #009; } table.diff td div.add { color: green; } table.diff td div.del { color: red; } .sha1 { font-family: monospace; font-size: 90%; } .left { text-align: left; } .right { text-align: right; } table.list td.repogroup { font-style: italic; color: #888; } a.button { font-size: 80%; padding: 0em 0.5em; } a.primary { font-size: 100%; } a.secondary { font-size: 90%; } td.toplevel-repo { } table.list td.sublevel-repo { padding-left: 1.5em; } div.pager { text-align: center; margin: 1em 0em 0em 0em; } div.pager a { color: #777; margin: 0em 0.5em; } span.age-mins { font-weight: bold; color: #080; } span.age-hours { color: #080; } span.age-days { color: #040; } span.age-weeks { color: #444; } span.age-months { color: #888; } span.age-years { color: #bbb; } div.footer { margin-top: 0.5em; text-align: center; font-size: 80%; color: #ccc; } a.branch-deco { margin: 0px 0.5em; padding: 0px 0.25em; background-color: #88ff88; border: solid 1px #007700; } a.tag-deco { margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ffff88; border: solid 1px #777700; } a.remote-deco { margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ccccff; border: solid 1px #000077; } a.deco { margin: 0px 0.5em; padding: 0px 0.25em; background-color: #ff8888; border: solid 1px #770000; } div.commit-subject a { margin-left: 1em; font-size: 75%; } table.stats { border: solid 1px black; border-collapse: collapse; } table.stats th { text-align: left; padding: 1px 0.5em; background-color: #eee; border: solid 1px black; } table.stats td { text-align: right; padding: 1px 0.5em; border: solid 1px black; } table.stats td.total { font-weight: bold; text-align: left; } table.stats td.sum { color: #c00; font-weight: bold; /* background-color: #eee; */ } table.stats td.left { text-align: left; } table.vgraph { border-collapse: separate; border: solid 1px black; height: 200px; } table.vgraph th { background-color: #eee; font-weight: bold; border: solid 1px white; padding: 1px 0.5em; } table.vgraph td { vertical-align: bottom; padding: 0px 10px; } table.vgraph div.bar { background-color: #eee; } table.hgraph { border: solid 1px black; width: 800px; } table.hgraph th { background-color: #eee; font-weight: bold; border: solid 1px black; padding: 1px 0.5em; } table.hgraph td { vertical-align: center; padding: 2px 2px; } table.hgraph div.bar { background-color: #eee; height: 1em; } @@ -1,246 +1,287 @@ #ifndef CGIT_H #define CGIT_H #include <git-compat-util.h> #include <cache.h> #include <grep.h> #include <object.h> #include <tree.h> #include <commit.h> #include <tag.h> #include <diff.h> #include <diffcore.h> #include <refs.h> #include <revision.h> #include <log-tree.h> #include <archive.h> +#include <string-list.h> #include <xdiff-interface.h> #include <xdiff/xdiff.h> #include <utf8.h> /* * Dateformats used on misc. pages */ #define FMT_LONGDATE "%Y-%m-%d %H:%M:%S (%Z)" #define FMT_SHORTDATE "%Y-%m-%d" #define FMT_ATOMDATE "%Y-%m-%dT%H:%M:%SZ" /* * Limits used for relative dates */ #define TM_MIN 60 #define TM_HOUR (TM_MIN * 60) #define TM_DAY (TM_HOUR * 24) #define TM_WEEK (TM_DAY * 7) #define TM_YEAR (TM_DAY * 365) #define TM_MONTH (TM_YEAR / 12.0) /* * Default encoding */ #define PAGE_ENCODING "UTF-8" typedef void (*configfn)(const char *name, const char *value); 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; char *name; char *path; char *desc; char *owner; char *defbranch; char *group; char *module_link; char *readme; char *clone_url; int snapshots; int enable_log_filecount; int enable_log_linecount; int max_stats; time_t mtime; + struct cgit_filter *about_filter; + struct cgit_filter *commit_filter; + struct cgit_filter *source_filter; }; struct cgit_repolist { int length; int count; struct cgit_repo *repos; }; struct commitinfo { struct commit *commit; char *author; char *author_email; unsigned long author_date; char *committer; char *committer_email; unsigned long committer_date; char *subject; char *msg; char *msg_encoding; }; struct taginfo { char *tagger; char *tagger_email; unsigned long tagger_date; char *msg; }; struct refinfo { const char *refname; struct object *object; union { struct taginfo *tag; struct commitinfo *commit; }; }; struct reflist { struct refinfo **refs; int alloc; int count; }; struct cgit_query { int has_symref; int has_sha1; char *raw; char *repo; char *page; char *search; char *grep; char *head; char *sha1; char *sha2; char *path; char *name; char *mimetype; char *url; char *period; int ofs; int nohead; char *sort; int showmsg; }; struct cgit_config { char *agefile; char *cache_root; char *clone_prefix; char *css; char *favicon; char *footer; + char *head_include; char *header; char *index_header; char *index_info; char *logo; char *logo_link; char *module_link; char *repo_group; char *robots; char *root_title; char *root_desc; char *root_readme; char *script_name; char *virtual_root; int cache_size; int cache_dynamic_ttl; int cache_max_create_time; int cache_repo_ttl; int cache_root_ttl; int cache_static_ttl; + int embedded; int enable_index_links; int enable_log_filecount; int enable_log_linecount; int local_time; int max_repo_count; int max_commit_count; int max_lock_attempts; int max_msg_len; int max_repodesc_len; int max_stats; int nocache; + int noplainemail; + int noheader; int renamelimit; int snapshots; int summary_branches; 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; }; struct cgit_page { time_t modified; time_t expires; size_t size; char *mimetype; 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; struct cgit_repo *repo; struct cgit_page page; }; struct cgit_snapshot_format { const char *suffix; const char *mimetype; write_archive_fn_t write_func; int bit; }; extern const char *cgit_version; extern struct cgit_repolist cgit_repolist; extern struct cgit_context ctx; extern const struct cgit_snapshot_format cgit_snapshot_formats[]; extern struct cgit_repo *cgit_add_repo(const char *url); extern struct cgit_repo *cgit_get_repoinfo(const char *url); extern void cgit_repo_config_cb(const char *name, const char *value); extern int chk_zero(int result, char *msg); extern int chk_positive(int result, char *msg); extern int chk_non_negative(int result, char *msg); extern char *trim_end(const char *str, char c); extern char *strlpart(char *txt, int maxlen); extern char *strrpart(char *txt, int maxlen); extern void cgit_add_ref(struct reflist *list, struct refinfo *ref); extern int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags, void *cb_data); extern void *cgit_free_commitinfo(struct commitinfo *info); extern int cgit_diff_files(const unsigned char *old_sha1, const unsigned char *new_sha1, unsigned long *old_size, unsigned long *new_size, int *binary, linediff_fn fn); extern void cgit_diff_tree(const unsigned char *old_sha1, const unsigned char *new_sha1, filepair_fn fn, const char *prefix); extern void cgit_diff_commit(struct commit *commit, filepair_fn fn); extern char *fmt(const char *format,...); extern struct commitinfo *cgit_parse_commit(struct commit *commit); extern struct taginfo *cgit_parse_tag(struct tag *tag); extern void cgit_parse_url(const char *url); 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); + #endif /* CGIT_H */ diff --git a/cgitrc.5.txt b/cgitrc.5.txt index fd299ae..3c35b02 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt @@ -1,388 +1,461 @@ -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 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. The first line in the file is used as input to the "parse_date" function in libgit. Recommended timestamp-format is "yyyy-mm-dd 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 setting is only used if `repo.clone-url` is unspecified. Default value: 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 will not be supported by cgit-1.0 (use root-readme instead). Default 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 is deprecated, and will not be supported by cgit-1.0 (use root-desc 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 formatstring are the path and SHA1 of the submodule commit. Default 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 +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. -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" 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 '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default value: none. NOTE: cgit has recently learned how to use PATH_INFO to achieve the same kind of virtual urls, so this option will probably be deprecated. 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. EXAMPLE CGITRC FILE ------------------- +.... # Enable caching of up to 1000 output entriess cache-size=1000 # Specify some default clone prefixes clone-prefix=git://foobar.com ssh://foobar.com/pub/git http://foobar.com/git # Specify the css url css=/css/cgit.css # Show extra links for each repository on the index page enable-index-links=1 # Show number of affected files per commit on the log pages enable-log-filecount=1 # Show number of added/removed lines per commit on the log pages enable-log-linecount=1 # Add a cgit favicon favicon=/favicon.ico # Use a custom logo logo=/img/mylogo.png # Enable statistics per week, month and quarter max-stats=quarter # Set the title and heading of the repository index page root-title=foobar.com git repositories # Set a subheading for the repository index page root-desc=tracking the foobar development # Include some more info about foobar.com on the index page root-readme=/var/www/htdocs/about.html # Allow download of tar.gz, tar.bz2 and zip-files 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 ## displayed under a group heading ## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') ## and included like this: ## include=/etc/cgitrepos ## repo.url=foo repo.path=/pub/git/foo.git repo.desc=the master foo repository repo.owner=fooman@foobar.com repo.readme=info/web/about.html repo.url=bar repo.path=/pub/git/bar.git repo.desc=the bars for your foo repo.owner=barman@foobar.com repo.readme=info/web/about.html # The next repositories will be displayed under the 'extras' heading repo.group=extras repo.url=baz repo.path=/pub/git/baz.git repo.desc=a set of extensions for bar users repo.url=wiz repo.path=/pub/git/wiz.git repo.desc=the wizard of foo # Add some mirrored repositories repo.group=mirrors repo.url=git repo.path=/pub/git/git.git repo.desc=the dscm repo.url=linux repo.path=/pub/git/linux.git repo.desc=the kernel # Disable adhoc downloads of this repo repo.snapshots=0 # Disable line-counts for this repo repo.enable-log-linecount=0 # Restrict the max statistics period for this repo repo.max-stats=month +.... BUGS ---- Comments currently cannot appear on the same line as a setting; the comment will be included as part of the value. E.g. this line: robots=index # allow indexing will generate the following html element: <meta name='robots' content='index # allow indexing'/> AUTHOR ------ Lars Hjemli <hjemli@gmail.com> @@ -1,171 +1,171 @@ /* cmd.c: the cgit command dispatcher * * Copyright (C) 2008 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "cmd.h" #include "cache.h" #include "ui-shared.h" #include "ui-atom.h" #include "ui-blob.h" #include "ui-clone.h" #include "ui-commit.h" #include "ui-diff.h" #include "ui-log.h" #include "ui-patch.h" #include "ui-plain.h" #include "ui-refs.h" #include "ui-repolist.h" #include "ui-snapshot.h" #include "ui-stats.h" #include "ui-summary.h" #include "ui-tag.h" #include "ui-tree.h" static void HEAD_fn(struct cgit_context *ctx) { cgit_clone_head(ctx); } static void atom_fn(struct cgit_context *ctx) { cgit_print_atom(ctx->qry.head, ctx->qry.path, 10); } 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(); } static void blob_fn(struct cgit_context *ctx) { cgit_print_blob(ctx->qry.sha1, ctx->qry.path, ctx->qry.head); } static void commit_fn(struct cgit_context *ctx) { cgit_print_commit(ctx->qry.sha1); } static void diff_fn(struct cgit_context *ctx) { cgit_print_diff(ctx->qry.sha1, ctx->qry.sha2, ctx->qry.path); } static void info_fn(struct cgit_context *ctx) { cgit_clone_info(ctx); } static void log_fn(struct cgit_context *ctx) { cgit_print_log(ctx->qry.sha1, ctx->qry.ofs, ctx->cfg.max_commit_count, ctx->qry.grep, ctx->qry.search, ctx->qry.path, 1); } static void ls_cache_fn(struct cgit_context *ctx) { ctx->page.mimetype = "text/plain"; ctx->page.filename = "ls-cache.txt"; cgit_print_http_headers(ctx); cache_ls(ctx->cfg.cache_root); } static void objects_fn(struct cgit_context *ctx) { cgit_clone_objects(ctx); } static void repolist_fn(struct cgit_context *ctx) { cgit_print_repolist(); } static void patch_fn(struct cgit_context *ctx) { cgit_print_patch(ctx->qry.sha1); } static void plain_fn(struct cgit_context *ctx) { cgit_print_plain(ctx); } static void refs_fn(struct cgit_context *ctx) { cgit_print_refs(); } static void snapshot_fn(struct cgit_context *ctx) { cgit_print_snapshot(ctx->qry.head, ctx->qry.sha1, ctx->qry.path, ctx->repo->snapshots, ctx->qry.nohead); } static void stats_fn(struct cgit_context *ctx) { cgit_show_stats(ctx); } static void summary_fn(struct cgit_context *ctx) { cgit_print_summary(); } static void tag_fn(struct cgit_context *ctx) { cgit_print_tag(ctx->qry.sha1); } static void tree_fn(struct cgit_context *ctx) { cgit_print_tree(ctx->qry.sha1, ctx->qry.path); } #define def_cmd(name, want_repo, want_layout) \ {#name, name##_fn, want_repo, want_layout} struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx) { static struct cgit_cmd cmds[] = { def_cmd(HEAD, 1, 0), def_cmd(atom, 1, 0), def_cmd(about, 0, 1), def_cmd(blob, 1, 0), def_cmd(commit, 1, 1), def_cmd(diff, 1, 1), def_cmd(info, 1, 0), def_cmd(log, 1, 1), def_cmd(ls_cache, 0, 0), def_cmd(objects, 1, 0), def_cmd(patch, 1, 0), def_cmd(plain, 1, 0), def_cmd(refs, 1, 1), def_cmd(repolist, 0, 0), def_cmd(snapshot, 1, 0), def_cmd(stats, 1, 1), def_cmd(summary, 1, 1), def_cmd(tag, 1, 1), def_cmd(tree, 1, 1), }; int i; if (ctx->qry.page == NULL) { if (ctx->repo) ctx->qry.page = "summary"; else ctx->qry.page = "repolist"; } for(i = 0; i < sizeof(cmds)/sizeof(*cmds); i++) if (!strcmp(ctx->qry.page, cmds[i].name)) return &cmds[i]; return NULL; } 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 @@ -1,357 +1,395 @@ /* shared.c: global vars + some callback functions * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" struct cgit_repolist cgit_repolist; struct cgit_context ctx; int cgit_cmd; int chk_zero(int result, char *msg) { if (result != 0) die("%s: %s", msg, strerror(errno)); return result; } int chk_positive(int result, char *msg) { if (result <= 0) die("%s: %s", msg, strerror(errno)); return result; } int chk_non_negative(int result, char *msg) { if (result < 0) die("%s: %s",msg, strerror(errno)); return result; } struct cgit_repo *cgit_add_repo(const char *url) { struct cgit_repo *ret; if (++cgit_repolist.count > cgit_repolist.length) { if (cgit_repolist.length == 0) cgit_repolist.length = 8; else cgit_repolist.length *= 2; cgit_repolist.repos = xrealloc(cgit_repolist.repos, cgit_repolist.length * sizeof(struct cgit_repo)); } ret = &cgit_repolist.repos[cgit_repolist.count-1]; ret->url = trim_end(url, '/'); ret->name = ret->url; ret->path = NULL; ret->desc = "[no description]"; ret->owner = NULL; ret->group = ctx.cfg.repo_group; ret->defbranch = "master"; ret->snapshots = ctx.cfg.snapshots; ret->enable_log_filecount = ctx.cfg.enable_log_filecount; ret->enable_log_linecount = ctx.cfg.enable_log_linecount; ret->max_stats = ctx.cfg.max_stats; ret->module_link = ctx.cfg.module_link; 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; } struct cgit_repo *cgit_get_repoinfo(const char *url) { int i; struct cgit_repo *repo; for (i=0; i<cgit_repolist.count; i++) { repo = &cgit_repolist.repos[i]; if (!strcmp(repo->url, url)) return repo; } return NULL; } void *cgit_free_commitinfo(struct commitinfo *info) { free(info->author); free(info->author_email); free(info->committer); free(info->committer_email); free(info->subject); free(info->msg); free(info->msg_encoding); free(info); return NULL; } char *trim_end(const char *str, char c) { int len; char *s, *t; if (str == NULL) return NULL; t = (char *)str; len = strlen(t); while(len > 0 && t[len - 1] == c) len--; if (len == 0) return NULL; c = t[len]; t[len] = '\0'; s = xstrdup(t); t[len] = c; return s; } char *strlpart(char *txt, int maxlen) { char *result; if (!txt) return txt; if (strlen(txt) <= maxlen) return txt; result = xmalloc(maxlen + 1); memcpy(result, txt, maxlen - 3); result[maxlen-1] = result[maxlen-2] = result[maxlen-3] = '.'; result[maxlen] = '\0'; return result; } char *strrpart(char *txt, int maxlen) { char *result; if (!txt) return txt; if (strlen(txt) <= maxlen) return txt; result = xmalloc(maxlen + 1); memcpy(result + 3, txt + strlen(txt) - maxlen + 4, maxlen - 3); result[0] = result[1] = result[2] = '.'; return result; } void cgit_add_ref(struct reflist *list, struct refinfo *ref) { size_t size; if (list->count >= list->alloc) { list->alloc += (list->alloc ? list->alloc : 4); size = list->alloc * sizeof(struct refinfo *); list->refs = xrealloc(list->refs, size); } list->refs[list->count++] = ref; } struct refinfo *cgit_mk_refinfo(const char *refname, const unsigned char *sha1) { struct refinfo *ref; ref = xmalloc(sizeof (struct refinfo)); ref->refname = xstrdup(refname); ref->object = parse_object(sha1); switch (ref->object->type) { case OBJ_TAG: ref->tag = cgit_parse_tag((struct tag *)ref->object); break; case OBJ_COMMIT: ref->commit = cgit_parse_commit((struct commit *)ref->object); break; } return ref; } int cgit_refs_cb(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct reflist *list = (struct reflist *)cb_data; struct refinfo *info = cgit_mk_refinfo(refname, sha1); if (info) cgit_add_ref(list, info); return 0; } void cgit_diff_tree_cb(struct diff_queue_struct *q, struct diff_options *options, void *data) { int i; for (i = 0; i < q->nr; i++) { if (q->queue[i]->status == 'U') continue; ((filepair_fn)data)(q->queue[i]); } } static int load_mmfile(mmfile_t *file, const unsigned char *sha1) { enum object_type type; if (is_null_sha1(sha1)) { file->ptr = (char *)""; file->size = 0; } else { file->ptr = read_sha1_file(sha1, &type, (unsigned long *)&file->size); } return 1; } /* * Receive diff-buffers from xdiff and concatenate them as * needed across multiple callbacks. * * This is basically a copy of xdiff-interface.c/xdiff_outf(), * ripped from git and modified to use globals instead of * a special callback-struct. */ char *diffbuf = NULL; int buflen = 0; int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) { int i; for (i = 0; i < nbuf; i++) { if (mb[i].ptr[mb[i].size-1] != '\n') { /* Incomplete line */ diffbuf = xrealloc(diffbuf, buflen + mb[i].size); memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); buflen += mb[i].size; continue; } /* we have a complete line */ if (!diffbuf) { ((linediff_fn)priv)(mb[i].ptr, mb[i].size); continue; } diffbuf = xrealloc(diffbuf, buflen + mb[i].size); memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size); ((linediff_fn)priv)(diffbuf, buflen + mb[i].size); free(diffbuf); diffbuf = NULL; buflen = 0; } if (diffbuf) { ((linediff_fn)priv)(diffbuf, buflen); free(diffbuf); diffbuf = NULL; buflen = 0; } return 0; } int cgit_diff_files(const unsigned char *old_sha1, const unsigned char *new_sha1, unsigned long *old_size, unsigned long *new_size, int *binary, linediff_fn fn) { mmfile_t file1, file2; xpparam_t diff_params; xdemitconf_t emit_params; xdemitcb_t emit_cb; if (!load_mmfile(&file1, old_sha1) || !load_mmfile(&file2, new_sha1)) return 1; *old_size = file1.size; *new_size = file2.size; if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) || (file2.ptr && buffer_is_binary(file2.ptr, file2.size))) { *binary = 1; return 0; } memset(&diff_params, 0, sizeof(diff_params)); memset(&emit_params, 0, sizeof(emit_params)); memset(&emit_cb, 0, sizeof(emit_cb)); diff_params.flags = XDF_NEED_MINIMAL; emit_params.ctxlen = 3; emit_params.flags = XDL_EMIT_FUNCNAMES; emit_cb.outf = filediff_cb; emit_cb.priv = fn; xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb); return 0; } void cgit_diff_tree(const unsigned char *old_sha1, const unsigned char *new_sha1, filepair_fn fn, const char *prefix) { struct diff_options opt; int ret; int prefixlen; diff_setup(&opt); opt.output_format = DIFF_FORMAT_CALLBACK; opt.detect_rename = 1; opt.rename_limit = ctx.cfg.renamelimit; DIFF_OPT_SET(&opt, RECURSIVE); opt.format_callback = cgit_diff_tree_cb; opt.format_callback_data = fn; if (prefix) { opt.nr_paths = 1; opt.paths = &prefix; prefixlen = strlen(prefix); opt.pathlens = &prefixlen; } diff_setup_done(&opt); if (old_sha1 && !is_null_sha1(old_sha1)) ret = diff_tree_sha1(old_sha1, new_sha1, "", &opt); else ret = diff_root_tree_sha1(new_sha1, "", &opt); diffcore_std(&opt); diff_flush(&opt); } void cgit_diff_commit(struct commit *commit, filepair_fn fn) { unsigned char *old_sha1 = NULL; if (commit->parents) old_sha1 = commit->parents->item->object.sha1; cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL); } int cgit_parse_snapshots_mask(const char *str) { const struct cgit_snapshot_format *f; static const char *delim = " \t,:/|;"; int tl, sl, rv = 0; /* favor legacy setting */ if(atoi(str)) return 1; for(;;) { str += strspn(str,delim); tl = strcspn(str,delim); if (!tl) break; for (f = cgit_snapshot_formats; f->suffix; f++) { sl = strlen(f->suffix); if((tl == sl && !strncmp(f->suffix, str, tl)) || (tl == sl-1 && !strncmp(f->suffix+1, str, tl-1))) { rv |= f->bit; break; } } str += tl; } 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); +} @@ -1,129 +1,131 @@ /* ui-atom.c: functions for atom feeds * * Copyright (C) 2008 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "html.h" #include "ui-shared.h" void add_entry(struct commit *commit, char *host) { char delim = '&'; char *hex; char *mail, *t, *t2; struct commitinfo *info; info = cgit_parse_commit(commit); hex = sha1_to_hex(commit->object.sha1); html("<entry>\n"); html("<title>"); html_txt(info->subject); html("</title>\n"); html("<updated>"); cgit_print_date(info->author_date, FMT_ATOMDATE, ctx.cfg.local_time); html("</updated>\n"); html("<author>\n"); if (info->author) { html("<name>"); html_txt(info->author); html("</name>\n"); } - if (info->author_email) { + if (info->author_email && !ctx.cfg.noplainemail) { mail = xstrdup(info->author_email); t = strchr(mail, '<'); if (t) t++; else t = mail; t2 = strchr(t, '>'); if (t2) *t2 = '\0'; html("<email>"); html_txt(t); html("</email>\n"); free(mail); } html("</author>\n"); html("<published>"); cgit_print_date(info->author_date, FMT_ATOMDATE, ctx.cfg.local_time); 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)); if (ctx.cfg.virtual_root) delim = '?'; htmlf("%cid=%s", delim, hex); html("'/>\n"); } htmlf("<id>%s</id>\n", hex); html("<content type='text'>\n"); html_txt(info->msg); html("</content>\n"); html("<content type='xhtml'>\n"); html("<div xmlns='http://www.w3.org/1999/xhtml'>\n"); html("<pre>\n"); html_txt(info->msg); html("</pre>\n"); html("</div>\n"); html("</content>\n"); html("</entry>\n"); cgit_free_commitinfo(info); } void cgit_print_atom(char *tip, char *path, int max_count) { char *host; const char *argv[] = {NULL, tip, NULL, NULL, NULL}; struct commit *commit; struct rev_info rev; int argc = 2; if (!tip) argv[1] = ctx.qry.head; if (path) { argv[argc++] = "--"; argv[argc++] = path; } init_revisions(&rev, NULL); rev.abbrev = DEFAULT_ABBREV; rev.commit_format = CMIT_FMT_DEFAULT; rev.verbose_header = 1; rev.show_root_diff = 0; rev.max_count = max_count; setup_revisions(argc, argv, &rev, NULL); prepare_revision_walk(&rev); host = cgit_hosturl(); ctx.page.mimetype = "text/xml"; ctx.page.charset = "utf-8"; cgit_print_http_headers(&ctx); html("<feed xmlns='http://www.w3.org/2005/Atom'>\n"); html("<title>"); html_txt(ctx.repo->name); html("</title>\n"); html("<subtitle>"); html_txt(ctx.repo->desc); 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)); html("'/>\n"); } while ((commit = get_revision(&rev)) != NULL) { add_entry(commit, host); free(commit->buffer); commit->buffer = NULL; free_commit_list(commit->parents); commit->parents = NULL; } html("</feed>\n"); } @@ -1,73 +1,79 @@ /* ui-blob.c: show blob content * * Copyright (C) 2008 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "html.h" #include "ui-shared.h" static char *match_path; static unsigned char *matched_sha1; static int walk_tree(const unsigned char *sha1, const char *base,int baselen, const char *pathname, unsigned mode, int stage, void *cbdata) { if(strncmp(base,match_path,baselen) || strcmp(match_path+baselen,pathname) ) return READ_TREE_RECURSIVE; memmove(matched_sha1,sha1,20); return 0; } 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; const char *paths[] = {path, NULL}; if (hex) { if (get_sha1_hex(hex, sha1)){ cgit_print_error(fmt("Bad hex value: %s", hex)); return; } } else { if (get_sha1(head,sha1)) { cgit_print_error(fmt("Bad ref: %s", head)); return; } } type = sha1_object_info(sha1, &size); if((!hex) && type == OBJ_COMMIT && path) { commit = lookup_commit_reference(sha1); match_path = path; matched_sha1 = sha1; read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL); type = sha1_object_info(sha1,&size); } if (type == OBJ_BAD) { cgit_print_error(fmt("Bad object name: %s", hex)); return; } buf = read_sha1_file(sha1, &type, &size); if (!buf) { cgit_print_error(fmt("Error reading object %s", hex)); return; } 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); write(htmlfd, buf, size); } diff --git a/ui-commit.c b/ui-commit.c index 41ce70e..d6b73ee 100644 --- a/ui-commit.c +++ b/ui-commit.c @@ -1,106 +1,118 @@ /* ui-commit.c: generate commit view * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "html.h" #include "ui-shared.h" #include "ui-diff.h" #include "ui-log.h" void cgit_print_commit(char *hex) { struct commit *commit, *parent; struct commitinfo *info; struct commit_list *p; unsigned char sha1[20]; char *tmp; int parents = 0; if (!hex) hex = ctx.qry.head; if (get_sha1(hex, sha1)) { cgit_print_error(fmt("Bad object id: %s", hex)); return; } commit = lookup_commit_reference(sha1); if (!commit) { cgit_print_error(fmt("Bad commit reference: %s", hex)); return; } info = cgit_parse_commit(commit); load_ref_decorations(); html("<table summary='commit info' class='commit-info'>\n"); 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); html("</td></tr>\n"); 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); html("</td></tr>\n"); html("<tr><th>commit</th><td colspan='2' class='sha1'>"); tmp = sha1_to_hex(commit->object.sha1); cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp); html(" ("); cgit_patch_link("patch", NULL, NULL, NULL, tmp); html(")</td></tr>\n"); html("<tr><th>tree</th><td colspan='2' class='sha1'>"); tmp = xstrdup(hex); cgit_tree_link(sha1_to_hex(commit->tree->object.sha1), NULL, NULL, ctx.qry.head, tmp, NULL); html("</td></tr>\n"); for (p = commit->parents; p ; p = p->next) { parent = lookup_commit_reference(p->item->object.sha1); if (!parent) { html("<tr><td colspan='3'>"); cgit_print_error("Error reading parent commit"); html("</td></tr>"); continue; } html("<tr><th>parent</th>" "<td colspan='2' class='sha1'>"); cgit_commit_link(sha1_to_hex(p->item->object.sha1), NULL, NULL, ctx.qry.head, sha1_to_hex(p->item->object.sha1)); html(" ("); cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, sha1_to_hex(p->item->object.sha1), NULL); html(")</td></tr>"); parents++; } if (ctx.repo->snapshots) { html("<tr><th>download</th><td colspan='2' class='sha1'>"); cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, hex, ctx.repo->snapshots); html("</td></tr>"); } 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) { if (parents) tmp = sha1_to_hex(commit->parents->item->object.sha1); else tmp = NULL; cgit_print_diff(ctx.qry.sha1, tmp, NULL); } cgit_free_commitinfo(info); } @@ -1,230 +1,234 @@ /* ui-log.c: functions for log output * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "html.h" #include "ui-shared.h" int files, add_lines, rem_lines; void count_lines(char *line, int size) { if (size <= 0) return; if (line[0] == '+') add_lines++; else if (line[0] == '-') rem_lines++; } void inspect_files(struct diff_filepair *pair) { unsigned long old_size = 0; unsigned long new_size = 0; int binary = 0; files++; if (ctx.repo->enable_log_linecount) cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, &binary, count_lines); } void show_commit_decorations(struct commit *commit) { struct name_decoration *deco; static char buf[1024]; buf[sizeof(buf) - 1] = 0; deco = lookup_decoration(&name_decoration, &commit->object); while (deco) { if (!prefixcmp(deco->name, "refs/heads/")) { strncpy(buf, deco->name + 11, sizeof(buf) - 1); cgit_log_link(buf, NULL, "branch-deco", buf, NULL, NULL, 0, NULL, NULL, ctx.qry.showmsg); } else if (!prefixcmp(deco->name, "tag: refs/tags/")) { strncpy(buf, deco->name + 15, sizeof(buf) - 1); 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); cgit_log_link(buf, NULL, "remote-deco", NULL, sha1_to_hex(commit->object.sha1), NULL, 0, NULL, NULL, ctx.qry.showmsg); } else { strncpy(buf, deco->name, sizeof(buf) - 1); cgit_commit_link(buf, NULL, "deco", ctx.qry.head, sha1_to_hex(commit->object.sha1)); } deco = deco->next; } } void print_commit(struct commit *commit) { struct commitinfo *info; char *tmp; int cols = 2; info = cgit_parse_commit(commit); htmlf("<tr%s><td>", ctx.qry.showmsg ? " class='logheader'" : ""); tmp = fmt("id=%s", sha1_to_hex(commit->object.sha1)); tmp = cgit_pageurl(ctx.repo->url, "commit", tmp); html_link_open(tmp, NULL, NULL); cgit_print_age(commit->date, TM_WEEK * 2, FMT_SHORTDATE); html_link_close(); htmlf("</td><td%s>", ctx.qry.showmsg ? " class='logsubject'" : ""); cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, sha1_to_hex(commit->object.sha1)); show_commit_decorations(commit); html("</td><td>"); html_txt(info->author); if (ctx.repo->enable_log_filecount) { files = 0; add_lines = 0; rem_lines = 0; cgit_diff_commit(commit, inspect_files); html("</td><td>"); htmlf("%d", files); if (ctx.repo->enable_log_linecount) { html("</td><td>"); htmlf("-%d/+%d", rem_lines, add_lines); } } html("</td></tr>\n"); if (ctx.qry.showmsg) { if (ctx.repo->enable_log_filecount) { cols++; if (ctx.repo->enable_log_linecount) cols++; } htmlf("<tr class='nohover'><td/><td colspan='%d' class='logmsg'>", cols); html_txt(info->msg); html("</td></tr>\n"); } cgit_free_commitinfo(info); } static const char *disambiguate_ref(const char *ref) { unsigned char sha1[20]; const char *longref; longref = fmt("refs/heads/%s", ref); if (get_sha1(longref, sha1) == 0) return longref; return ref; } void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, char *path, int pager) { struct rev_info rev; struct commit *commit; const char *argv[] = {NULL, NULL, NULL, NULL, NULL}; int argc = 2; int i, columns = 3; if (!tip) tip = ctx.qry.head; argv[1] = disambiguate_ref(tip); if (grep && pattern && (!strcmp(grep, "grep") || !strcmp(grep, "author") || !strcmp(grep, "committer"))) argv[argc++] = fmt("--%s=%s", grep, pattern); if (path) { argv[argc++] = "--"; argv[argc++] = path; } init_revisions(&rev, NULL); rev.abbrev = DEFAULT_ABBREV; rev.commit_format = CMIT_FMT_DEFAULT; rev.verbose_header = 1; rev.show_root_diff = 0; setup_revisions(argc, argv, &rev, NULL); load_ref_decorations(); rev.show_decorations = 1; rev.grep_filter.regflags |= REG_ICASE; compile_grep_patterns(&rev.grep_filter); prepare_revision_walk(&rev); if (pager) html("<table class='list nowrap'>"); html("<tr class='nohover'><th class='left'>Age</th>" "<th class='left'>Commit message"); if (pager) { html(" ("); cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, ctx.qry.search, ctx.qry.showmsg ? 0 : 1); html(")"); } html("</th><th class='left'>Author</th>"); if (ctx.repo->enable_log_filecount) { html("<th class='left'>Files</th>"); columns++; if (ctx.repo->enable_log_linecount) { html("<th class='left'>Lines</th>"); columns++; } } html("</tr>\n"); if (ofs<0) ofs = 0; for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; i++) { free(commit->buffer); commit->buffer = NULL; free_commit_list(commit->parents); commit->parents = NULL; } for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; i++) { print_commit(commit); free(commit->buffer); commit->buffer = NULL; free_commit_list(commit->parents); commit->parents = NULL; } if (pager) { htmlf("</table><div class='pager'>", columns); if (ofs > 0) { cgit_log_link("[prev]", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.path, ofs - cnt, ctx.qry.grep, ctx.qry.search, ctx.qry.showmsg); html(" "); } if ((commit = get_revision(&rev)) != NULL) { cgit_log_link("[next]", NULL, NULL, ctx.qry.head, ctx.qry.sha1, ctx.qry.path, ofs + cnt, ctx.qry.grep, ctx.qry.search, ctx.qry.showmsg); } html("</div>"); } else if ((commit = get_revision(&rev)) != NULL) { html("<tr class='nohover'><td colspan='3'>"); cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, NULL, 0, NULL, NULL, ctx.qry.showmsg); html("</td></tr>\n"); } } @@ -1,125 +1,129 @@ /* ui-patch.c: generate patch view * * Copyright (C) 2007 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "html.h" #include "ui-shared.h" static void print_line(char *line, int len) { char c = line[len-1]; line[len-1] = '\0'; htmlf("%s\n", line); line[len-1] = c; } static void header(unsigned char *sha1, char *path1, int mode1, unsigned char *sha2, char *path2, int mode2) { char *abbrev1, *abbrev2; int subproject; subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2)); htmlf("diff --git a/%s b/%s\n", path1, path2); if (is_null_sha1(sha1)) path1 = "dev/null"; if (is_null_sha1(sha2)) path2 = "dev/null"; if (mode1 == 0) htmlf("new file mode %.6o\n", mode2); if (mode2 == 0) htmlf("deleted file mode %.6o\n", mode1); if (!subproject) { abbrev1 = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV)); abbrev2 = xstrdup(find_unique_abbrev(sha2, DEFAULT_ABBREV)); htmlf("index %s..%s", abbrev1, abbrev2); free(abbrev1); free(abbrev2); if (mode1 != 0 && mode2 != 0) { htmlf(" %.6o", mode1); if (mode2 != mode1) htmlf("..%.6o", mode2); } htmlf("\n--- a/%s\n", path1); htmlf("+++ b/%s\n", path2); } } static void filepair_cb(struct diff_filepair *pair) { unsigned long old_size = 0; unsigned long new_size = 0; int binary = 0; header(pair->one->sha1, pair->one->path, pair->one->mode, pair->two->sha1, pair->two->path, pair->two->mode); if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) { if (S_ISGITLINK(pair->one->mode)) print_line(fmt("-Subproject %s", sha1_to_hex(pair->one->sha1)), 52); if (S_ISGITLINK(pair->two->mode)) print_line(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); return; } if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, &binary, print_line)) html("Error running diff"); if (binary) html("Binary files differ\n"); } void cgit_print_patch(char *hex) { struct commit *commit; struct commitinfo *info; unsigned char sha1[20], old_sha1[20]; char *patchname; if (!hex) hex = ctx.qry.head; if (get_sha1(hex, sha1)) { cgit_print_error(fmt("Bad object id: %s", hex)); return; } commit = lookup_commit_reference(sha1); if (!commit) { cgit_print_error(fmt("Bad commit reference: %s", hex)); return; } info = cgit_parse_commit(commit); if (commit->parents && commit->parents->item) hashcpy(old_sha1, commit->parents->item->object.sha1); else hashclr(old_sha1); patchname = fmt("%s.patch", sha1_to_hex(sha1)); ctx.page.mimetype = "text/plain"; ctx.page.filename = patchname; 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); htmlf("Subject: %s\n\n", info->subject); if (info->msg && *info->msg) { htmlf("%s", info->msg); if (info->msg[strlen(info->msg) - 1] != '\n') html("\n"); } html("---\n"); cgit_diff_tree(old_sha1, sha1, filepair_cb, NULL); html("--\n"); htmlf("cgit %s\n", CGIT_VERSION); cgit_free_commitinfo(info); } @@ -1,79 +1,93 @@ /* ui-plain.c: functions for output of plain blobs by path * * Copyright (C) 2008 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "html.h" #include "ui-shared.h" char *curr_rev; char *match_path; int match; 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); if (type == OBJ_BAD) { html_status(404, "Not found", 0); return; } buf = read_sha1_file(sha1, &type, &size); if (!buf) { html_status(404, "Not found", 0); 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); match = 1; } static int walk_tree(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage, void *cbdata) { if (S_ISDIR(mode)) return READ_TREE_RECURSIVE; if (S_ISREG(mode)) print_object(sha1, pathname); return 0; } void cgit_print_plain(struct cgit_context *ctx) { const char *rev = ctx->qry.sha1; unsigned char sha1[20]; struct commit *commit; const char *paths[] = {ctx->qry.path, NULL}; if (!rev) rev = ctx->qry.head; curr_rev = xstrdup(rev); if (get_sha1(rev, sha1)) { html_status(404, "Not found", 0); return; } commit = lookup_commit_reference(sha1); if (!commit || parse_commit(commit)) { html_status(404, "Not found", 0); return; } match_path = ctx->qry.path; read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL); if (!match) html_status(404, "Not found", 0); } @@ -1,228 +1,245 @@ /* ui-refs.c: browse symbolic refs * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "html.h" #include "ui-shared.h" static int header; static int cmp_age(int age1, int age2) { if (age1 != 0 && age2 != 0) return age2 - age1; if (age1 == 0 && age2 == 0) return 0; if (age1 == 0) return +1; return -1; } static int cmp_ref_name(const void *a, const void *b) { struct refinfo *r1 = *(struct refinfo **)a; struct refinfo *r2 = *(struct refinfo **)b; return strcmp(r1->refname, r2->refname); } static int cmp_branch_age(const void *a, const void *b) { struct refinfo *r1 = *(struct refinfo **)a; struct refinfo *r2 = *(struct refinfo **)b; return cmp_age(r1->commit->committer_date, r2->commit->committer_date); } 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); } static int print_branch(struct refinfo *ref) { struct commitinfo *info = ref->commit; char *name = (char *)ref->refname; if (!info) return 1; html("<tr><td>"); cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, ctx.qry.showmsg); html("</td><td>"); if (ref->object->type == OBJ_COMMIT) { cgit_commit_link(info->subject, NULL, NULL, name, NULL); html("</td><td>"); html_txt(info->author); html("</td><td colspan='2'>"); cgit_print_age(info->commit->date, -1, NULL); } else { html("</td><td></td><td>"); cgit_object_link(ref->object); } html("</td></tr>\n"); return 0; } static void print_tag_header() { html("<tr class='nohover'><th class='left'>Tag</th>" "<th class='left'>Download</th>" "<th class='left'>Author</th>" "<th class='left' colspan='2'>Age</th></tr>\n"); header = 1; } static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) { const struct cgit_snapshot_format* f; char *filename; const char *basename; if (!ref || strlen(ref) < 2) return; basename = cgit_repobasename(repo->url); if (prefixcmp(ref, basename) != 0) { if ((ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1])) ref++; if (isdigit(ref[0])) ref = xstrdup(fmt("%s-%s", basename, ref)); } for (f = cgit_snapshot_formats; f->suffix; f++) { if (!(repo->snapshots & f->bit)) continue; filename = fmt("%s%s", ref, f->suffix); cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); html(" "); } } static int print_tag(struct refinfo *ref) { struct tag *tag; struct taginfo *info; char *name = (char *)ref->refname; if (ref->object->type == OBJ_TAG) { tag = (struct tag *)ref->object; info = ref->tag; if (!tag || !info) return 1; html("<tr><td>"); cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); html("</td><td>"); if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT)) print_tag_downloads(ctx.repo, name); else cgit_object_link(tag->tagged); html("</td><td>"); if (info->tagger) html(info->tagger); html("</td><td colspan='2'>"); if (info->tagger_date > 0) cgit_print_age(info->tagger_date, -1, NULL); html("</td></tr>\n"); } else { if (!header) print_tag_header(); html("<tr><td>"); cgit_tag_link(name, NULL, NULL, ctx.qry.head, name); html("</td><td>"); if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT)) print_tag_downloads(ctx.repo, name); 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"); } return 0; } static void print_refs_link(char *path) { html("<tr class='nohover'><td colspan='4'>"); cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); html("</td></tr>"); } void cgit_print_branches(int maxcount) { struct reflist list; int i; html("<tr class='nohover'><th class='left'>Branch</th>" "<th class='left'>Commit message</th>" "<th class='left'>Author</th>" "<th class='left' colspan='2'>Age</th></tr>\n"); list.refs = NULL; list.alloc = list.count = 0; for_each_branch_ref(cgit_refs_cb, &list); if (maxcount == 0 || maxcount > list.count) maxcount = list.count; if (maxcount < list.count) { qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age); qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name); } for(i=0; i<maxcount; i++) print_branch(list.refs[i]); if (maxcount < list.count) print_refs_link("heads"); } void cgit_print_tags(int maxcount) { struct reflist list; int i; header = 0; list.refs = NULL; list.alloc = list.count = 0; for_each_tag_ref(cgit_refs_cb, &list); if (list.count == 0) return; qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age); if (!maxcount) maxcount = list.count; else if (maxcount > list.count) maxcount = list.count; print_tag_header(); for(i=0; i<maxcount; i++) print_tag(list.refs[i]); if (maxcount < list.count) print_refs_link("tags"); } void cgit_print_refs() { html("<table class='list nowrap'>"); if (ctx.qry.path && !strncmp(ctx.qry.path, "heads", 5)) cgit_print_branches(0); else if (ctx.qry.path && !strncmp(ctx.qry.path, "tags", 4)) cgit_print_tags(0); else { cgit_print_branches(0); html("<tr class='nohover'><td colspan='4'> </td></tr>"); cgit_print_tags(0); } html("</table>"); } diff --git a/ui-repolist.c b/ui-repolist.c index 2c13d50..25f076f 100644 --- a/ui-repolist.c +++ b/ui-repolist.c @@ -1,278 +1,283 @@ /* ui-repolist.c: functions for generating the repolist page * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ /* This is needed for strcasestr to be defined by <string.h> */ #define _GNU_SOURCE 1 #include <string.h> #include <time.h> #include "cgit.h" #include "html.h" #include "ui-shared.h" time_t read_agefile(char *path) { FILE *f; static char buf[64], buf2[64]; if (!(f = fopen(path, "r"))) return -1; if (fgets(buf, sizeof(buf), f) == NULL) return -1; fclose(f); if (parse_date(buf, buf2, sizeof(buf2))) return strtoul(buf2, NULL, 10); else return 0; } static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime) { char *path; struct stat s; struct cgit_repo *r = (struct cgit_repo *)repo; if (repo->mtime != -1) { *mtime = repo->mtime; return 1; } path = fmt("%s/%s", repo->path, ctx.cfg.agefile); if (stat(path, &s) == 0) { *mtime = read_agefile(path); r->mtime = *mtime; return 1; } path = fmt("%s/refs/heads/%s", repo->path, repo->defbranch); if (stat(path, &s) == 0) *mtime = s.st_mtime; else *mtime = 0; r->mtime = *mtime; return (r->mtime != 0); } static void print_modtime(struct cgit_repo *repo) { time_t t; if (get_repo_modtime(repo, &t)) cgit_print_age(t, -1, NULL); } int is_match(struct cgit_repo *repo) { if (!ctx.qry.search) return 1; if (repo->url && strcasestr(repo->url, ctx.qry.search)) return 1; if (repo->name && strcasestr(repo->name, ctx.qry.search)) return 1; if (repo->desc && strcasestr(repo->desc, ctx.qry.search)) return 1; if (repo->owner && strcasestr(repo->owner, ctx.qry.search)) return 1; return 0; } int is_in_url(struct cgit_repo *repo) { if (!ctx.qry.url) return 1; if (repo->url && !prefixcmp(repo->url, ctx.qry.url)) return 1; return 0; } void print_sort_header(const char *title, const char *sort) { htmlf("<th class='left'><a href='./?s=%s", sort); if (ctx.qry.search) { html("&q="); html_url_arg(ctx.qry.search); } htmlf("'>%s</a></th>", title); } void print_header(int columns) { html("<tr class='nohover'>"); print_sort_header("Name", "name"); print_sort_header("Description", "desc"); print_sort_header("Owner", "owner"); print_sort_header("Idle", "idle"); if (ctx.cfg.enable_index_links) html("<th class='left'>Links</th>"); html("</tr>\n"); } void print_pager(int items, int pagelen, char *search) { int i; html("<div class='pager'>"); for(i = 0; i * pagelen < items; i++) cgit_index_link(fmt("[%d]", i+1), fmt("Page %d", i+1), NULL, search, i * pagelen); html("</div>"); } static int cmp(const char *s1, const char *s2) { if (s1 && s2) return strcmp(s1, s2); if (s1 && !s2) return -1; if (s2 && !s1) return 1; return 0; } static int sort_name(const void *a, const void *b) { const struct cgit_repo *r1 = a; const struct cgit_repo *r2 = b; return cmp(r1->name, r2->name); } static int sort_desc(const void *a, const void *b) { const struct cgit_repo *r1 = a; const struct cgit_repo *r2 = b; return cmp(r1->desc, r2->desc); } static int sort_owner(const void *a, const void *b) { const struct cgit_repo *r1 = a; const struct cgit_repo *r2 = b; return cmp(r1->owner, r2->owner); } static int sort_idle(const void *a, const void *b) { const struct cgit_repo *r1 = a; const struct cgit_repo *r2 = b; time_t t1, t2; t1 = t2 = 0; get_repo_modtime(r1, &t1); get_repo_modtime(r2, &t2); return t2 - t1; } struct sortcolumn { const char *name; int (*fn)(const void *a, const void *b); }; struct sortcolumn sortcolumn[] = { {"name", sort_name}, {"desc", sort_desc}, {"owner", sort_owner}, {"idle", sort_idle}, {NULL, NULL} }; int sort_repolist(char *field) { struct sortcolumn *column; for (column = &sortcolumn[0]; column->name; column++) { if (strcmp(field, column->name)) continue; qsort(cgit_repolist.repos, cgit_repolist.count, sizeof(struct cgit_repo), column->fn); return 1; } return 0; } void cgit_print_repolist() { int i, columns = 4, hits = 0, header = 0; char *last_group = NULL; int sorted = 0; if (ctx.cfg.enable_index_links) columns++; ctx.page.title = ctx.cfg.root_title; cgit_print_http_headers(&ctx); cgit_print_docstart(&ctx); cgit_print_pageheader(&ctx); if (ctx.cfg.index_header) html_include(ctx.cfg.index_header); if(ctx.qry.sort) sorted = sort_repolist(ctx.qry.sort); html("<table summary='repository list' class='list nowrap'>"); for (i=0; i<cgit_repolist.count; i++) { ctx.repo = &cgit_repolist.repos[i]; if (!(is_match(ctx.repo) && is_in_url(ctx.repo))) continue; hits++; if (hits <= ctx.qry.ofs) continue; if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count) continue; if (!header++) print_header(columns); 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'>", columns); html_txt(ctx.repo->group); html("</td></tr>"); last_group = ctx.repo->group; } htmlf("<tr><td class='%s'>", !sorted && ctx.repo->group ? "sublevel-repo" : "toplevel-repo"); cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); html("</td><td>"); html_link_open(cgit_repourl(ctx.repo->url), NULL, NULL); html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc); html_link_close(); html("</td><td>"); html_txt(ctx.repo->owner); html("</td><td>"); print_modtime(ctx.repo); html("</td>"); if (ctx.cfg.enable_index_links) { html("<td>"); cgit_summary_link("summary", NULL, "button", NULL); cgit_log_link("log", NULL, "button", NULL, NULL, NULL, 0, NULL, NULL, ctx.qry.showmsg); cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL); html("</td>"); } html("</tr>\n"); } html("</table>"); if (!hits) cgit_print_error("No repositories found"); else if (hits > ctx.cfg.max_repo_count) print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search); cgit_print_docend(); } 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 de77bbf..cf06511 100644 --- a/ui-shared.c +++ b/ui-shared.c @@ -1,739 +1,770 @@ /* ui-shared.c: common web output functions * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "cmd.h" #include "html.h" const char cgit_doctype[] = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n" " \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"; static char *http_date(time_t t) { static char day[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static char month[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Now", "Dec"}; struct tm *tm = gmtime(&t); return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], tm->tm_mday, month[tm->tm_mon], 1900+tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec); } void cgit_print_error(char *msg) { html("<div class='error'>"); html_txt(msg); html("</div>\n"); } -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)); } char *cgit_rooturl() { if (ctx.cfg.virtual_root) return fmt("%s/", ctx.cfg.virtual_root); else return ctx.cfg.script_name; } char *cgit_repourl(const char *reponame) { if (ctx.cfg.virtual_root) { return fmt("%s/%s/", ctx.cfg.virtual_root, reponame); } else { return fmt("?r=%s", reponame); } } char *cgit_fileurl(const char *reponame, const char *pagename, const char *filename, const char *query) { char *tmp; char *delim; if (ctx.cfg.virtual_root) { tmp = fmt("%s/%s/%s/%s", ctx.cfg.virtual_root, reponame, pagename, (filename ? filename:"")); delim = "?"; } else { tmp = fmt("?url=%s/%s/%s", reponame, pagename, (filename ? filename : "")); delim = "&"; } if (query) tmp = fmt("%s%s%s", tmp, delim, query); return tmp; } char *cgit_pageurl(const char *reponame, const char *pagename, const char *query) { return cgit_fileurl(reponame,pagename,0,query); } const char *cgit_repobasename(const char *reponame) { /* I assume we don't need to store more than one repo basename */ static char rvbuf[1024]; int p; const char *rv; strncpy(rvbuf,reponame,sizeof(rvbuf)); if(rvbuf[sizeof(rvbuf)-1]) die("cgit_repobasename: truncated repository name '%s'", reponame); p = strlen(rvbuf)-1; /* strip trailing slashes */ while(p && rvbuf[p]=='/') rvbuf[p--]=0; /* strip trailing .git */ if(p>=3 && !strncmp(&rvbuf[p-3],".git",4)) { p -= 3; rvbuf[p--] = 0; } /* strip more trailing slashes if any */ while( p && rvbuf[p]=='/') rvbuf[p--]=0; /* find last slash in the remaining string */ rv = strrchr(rvbuf,'/'); if(rv) return ++rv; return rvbuf; } char *cgit_currurl() { if (!ctx.cfg.virtual_root) return ctx.cfg.script_name; else if (ctx.qry.page) return fmt("%s/%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo, ctx.qry.page); else if (ctx.qry.repo) return fmt("%s/%s/", ctx.cfg.virtual_root, ctx.qry.repo); else return fmt("%s/", ctx.cfg.virtual_root); } static void site_url(char *page, char *search, int ofs) { char *delim = "?"; if (ctx.cfg.virtual_root) { html_attr(ctx.cfg.virtual_root); if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') html("/"); } else html(ctx.cfg.script_name); if (page) { htmlf("?p=%s", page); delim = "&"; } if (search) { html(delim); html("q="); html_attr(search); delim = "&"; } if (ofs) { html(delim); htmlf("ofs=%d", ofs); } } static void site_link(char *page, char *name, char *title, char *class, char *search, int ofs) { html("<a"); if (title) { html(" title='"); html_attr(title); html("'"); } if (class) { html(" class='"); html_attr(class); html("'"); } html(" href='"); site_url(page, search, ofs); html("'>"); html_txt(name); html("</a>"); } void cgit_index_link(char *name, char *title, char *class, char *pattern, int ofs) { site_link(NULL, name, title, class, pattern, ofs); } static char *repolink(char *title, char *class, char *page, char *head, char *path) { char *delim = "?"; html("<a"); if (title) { html(" title='"); html_attr(title); html("'"); } if (class) { html(" class='"); html_attr(class); html("'"); } html(" href='"); if (ctx.cfg.virtual_root) { html_url_path(ctx.cfg.virtual_root); if (ctx.cfg.virtual_root[strlen(ctx.cfg.virtual_root) - 1] != '/') html("/"); html_url_path(ctx.repo->url); if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') html("/"); if (page) { html_url_path(page); html("/"); if (path) html_url_path(path); } } else { html(ctx.cfg.script_name); html("?url="); html_url_arg(ctx.repo->url); if (ctx.repo->url[strlen(ctx.repo->url) - 1] != '/') html("/"); if (page) { html_url_arg(page); html("/"); if (path) html_url_arg(path); } delim = "&"; } if (head && strcmp(head, ctx.repo->defbranch)) { html(delim); html("h="); html_url_arg(head); delim = "&"; } return fmt("%s", delim); } static void reporevlink(char *page, char *name, char *title, char *class, char *head, char *rev, char *path) { char *delim; delim = repolink(title, class, page, head, path); if (rev && strcmp(rev, ctx.qry.head)) { html(delim); html("id="); html_url_arg(rev); } html("'>"); html_txt(name); html("</a>"); } void cgit_summary_link(char *name, char *title, char *class, char *head) { reporevlink(NULL, name, title, class, head, NULL, NULL); } void cgit_tag_link(char *name, char *title, char *class, char *head, char *rev) { reporevlink("tag", name, title, class, head, rev, NULL); } void cgit_tree_link(char *name, char *title, char *class, char *head, char *rev, char *path) { reporevlink("tree", name, title, class, head, rev, path); } void cgit_plain_link(char *name, char *title, char *class, char *head, char *rev, char *path) { reporevlink("plain", name, title, class, head, rev, path); } void cgit_log_link(char *name, char *title, char *class, char *head, char *rev, char *path, int ofs, char *grep, char *pattern, int showmsg) { char *delim; delim = repolink(title, class, "log", head, path); if (rev && strcmp(rev, ctx.qry.head)) { html(delim); html("id="); html_url_arg(rev); delim = "&"; } if (grep && pattern) { html(delim); html("qt="); html_url_arg(grep); delim = "&"; html(delim); html("q="); html_url_arg(pattern); } if (ofs > 0) { html(delim); html("ofs="); htmlf("%d", ofs); delim = "&"; } if (showmsg) { html(delim); html("showmsg=1"); } html("'>"); html_txt(name); html("</a>"); } void cgit_commit_link(char *name, char *title, char *class, char *head, char *rev) { if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { name[ctx.cfg.max_msg_len] = '\0'; name[ctx.cfg.max_msg_len - 1] = '.'; name[ctx.cfg.max_msg_len - 2] = '.'; name[ctx.cfg.max_msg_len - 3] = '.'; } reporevlink("commit", name, title, class, head, rev, NULL); } void cgit_refs_link(char *name, char *title, char *class, char *head, char *rev, char *path) { reporevlink("refs", name, title, class, head, rev, path); } void cgit_snapshot_link(char *name, char *title, char *class, char *head, char *rev, char *archivename) { reporevlink("snapshot", name, title, class, head, rev, archivename); } void cgit_diff_link(char *name, char *title, char *class, char *head, char *new_rev, char *old_rev, char *path) { char *delim; delim = repolink(title, class, "diff", head, path); if (new_rev && strcmp(new_rev, ctx.qry.head)) { html(delim); html("id="); html_url_arg(new_rev); delim = "&"; } if (old_rev) { html(delim); html("id2="); html_url_arg(old_rev); } html("'>"); html_txt(name); html("</a>"); } void cgit_patch_link(char *name, char *title, char *class, char *head, char *rev) { reporevlink("patch", name, title, class, head, rev, NULL); } void cgit_stats_link(char *name, char *title, char *class, char *head, char *path) { reporevlink("stats", name, title, class, head, NULL, path); } void cgit_object_link(struct object *obj) { char *page, *shortrev, *fullrev, *name; fullrev = sha1_to_hex(obj->sha1); shortrev = xstrdup(fullrev); shortrev[10] = '\0'; if (obj->type == OBJ_COMMIT) { cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, ctx.qry.head, fullrev); return; } else if (obj->type == OBJ_TREE) page = "tree"; else if (obj->type == OBJ_TAG) page = "tag"; else page = "blob"; name = fmt("%s %s...", typename(obj->type), shortrev); reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); } void cgit_print_date(time_t secs, char *format, int local_time) { char buf[64]; struct tm *time; if (!secs) return; if(local_time) time = localtime(&secs); else time = gmtime(&secs); strftime(buf, sizeof(buf)-1, format, time); html_txt(buf); } void cgit_print_age(time_t t, time_t max_relative, char *format) { time_t now, secs; if (!t) return; time(&now); secs = now - t; if (secs > max_relative && max_relative >= 0) { cgit_print_date(t, format, ctx.cfg.local_time); return; } if (secs < TM_HOUR * 2) { htmlf("<span class='age-mins'>%.0f min.</span>", secs * 1.0 / TM_MIN); return; } if (secs < TM_DAY * 2) { htmlf("<span class='age-hours'>%.0f hours</span>", secs * 1.0 / TM_HOUR); return; } if (secs < TM_WEEK * 2) { htmlf("<span class='age-days'>%.0f days</span>", secs * 1.0 / TM_DAY); return; } if (secs < TM_MONTH * 2) { htmlf("<span class='age-weeks'>%.0f weeks</span>", secs * 1.0 / TM_WEEK); return; } if (secs < TM_YEAR * 2) { htmlf("<span class='age-months'>%.0f months</span>", secs * 1.0 / TM_MONTH); return; } htmlf("<span class='age-years'>%.0f years</span>", secs * 1.0 / TM_YEAR); } 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, ctx->page.charset); else if (ctx->page.mimetype) htmlf("Content-Type: %s\n", ctx->page.mimetype); if (ctx->page.size) htmlf("Content-Length: %ld\n", ctx->page.size); if (ctx->page.filename) htmlf("Content-Disposition: inline; filename=\"%s\"\n", ctx->page.filename); 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); html("<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'>\n"); html("<head>\n"); html("<title>"); html_txt(ctx->page.title); html("</title>\n"); htmlf("<meta name='generator' content='cgit %s'/>\n", cgit_version); if (ctx->cfg.robots && *ctx->cfg.robots) htmlf("<meta name='robots' content='%s'/>\n", ctx->cfg.robots); html("<link rel='stylesheet' type='text/css' href='"); html_attr(ctx->cfg.css); html("'/>\n"); if (ctx->cfg.favicon) { html("<link rel='shortcut icon' href='"); html_attr(ctx->cfg.favicon); html("'/>\n"); } 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"); if (ctx->cfg.header) html_include(ctx->cfg.header); } 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); else { htmlf("<div class='footer'>generated by cgit %s at ", cgit_version); cgit_print_date(time(NULL), FMT_LONGDATE, ctx.cfg.local_time); html("</div>\n"); } + html("</div> <!-- id=cgit -->\n"); html("</body>\n</html>\n"); } int print_branch_option(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { char *name = (char *)refname; html_option(name, name, ctx.qry.head); return 0; } int print_archive_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data) { struct tag *tag; struct taginfo *info; struct object *obj; char buf[256], *url; unsigned char fileid[20]; int *header = (int *)cb_data; if (prefixcmp(refname, "refs/archives")) return 0; strncpy(buf, refname+14, sizeof(buf)); obj = parse_object(sha1); if (!obj) return 1; if (obj->type == OBJ_TAG) { tag = lookup_tag(sha1); if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) return 0; hashcpy(fileid, tag->tagged->sha1); } else if (obj->type != OBJ_BLOB) { return 0; } else { hashcpy(fileid, sha1); } if (!*header) { html("<h1>download</h1>\n"); *header = 1; } url = cgit_pageurl(ctx.qry.repo, "blob", fmt("id=%s&path=%s", sha1_to_hex(fileid), buf)); html_link_open(url, NULL, "menu"); html_txt(strlpart(buf, 20)); html_link_close(); return 0; } void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page) { char *url; if (!ctx.cfg.virtual_root) { url = fmt("%s/%s", ctx.qry.repo, page); if (ctx.qry.path) url = fmt("%s/%s", url, ctx.qry.path); html_hidden("url", url); } if (incl_head && ctx.qry.head && ctx.repo->defbranch && strcmp(ctx.qry.head, ctx.repo->defbranch)) html_hidden("h", ctx.qry.head); if (ctx.qry.sha1) html_hidden("id", ctx.qry.sha1); if (ctx.qry.sha2) html_hidden("id2", ctx.qry.sha2); if (ctx.qry.showmsg) html_hidden("showmsg", "1"); if (incl_search) { if (ctx.qry.grep) html_hidden("qt", ctx.qry.grep); if (ctx.qry.search) html_hidden("q", ctx.qry.search); } } const char *fallback_cmd = "repolist"; char *hc(struct cgit_cmd *cmd, const char *page) { return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); } -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"); html("<td class='logo' rowspan='2'><a href='"); if (ctx->cfg.logo_link) html_attr(ctx->cfg.logo_link); else html_attr(cgit_rooturl()); html("'><img src='"); html_attr(ctx->cfg.logo); html("' alt='cgit logo'/></a></td>\n"); html("<td class='main'>"); if (ctx->repo) { cgit_index_link("index", NULL, NULL, NULL, 0); html(" : "); cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); html("</td><td class='form'>"); html("<form method='get' action=''>\n"); cgit_add_hidden_formfields(0, 1, ctx->qry.page); html("<select name='h' onchange='this.form.submit();'>\n"); for_each_branch_ref(print_branch_option, ctx->qry.head); html("</select> "); html("<input type='submit' name='' value='switch'/>"); html("</form>"); } else html_txt(ctx->cfg.root_title); html("</td></tr>\n"); html("<tr><td class='sub'>"); if (ctx->repo) { html_txt(ctx->repo->desc); html("</td><td class='sub right'>"); html_txt(ctx->repo->owner); } else { if (ctx->cfg.root_desc) html_txt(ctx->cfg.root_desc); else if (ctx->cfg.index_info) html_include(ctx->cfg.index_info); } 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"); if (ctx->repo) { cgit_summary_link("summary", NULL, hc(cmd, "summary"), ctx->qry.head); cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, ctx->qry.sha1, NULL); cgit_log_link("log", NULL, hc(cmd, "log"), ctx->qry.head, NULL, NULL, 0, NULL, NULL, ctx->qry.showmsg); cgit_tree_link("tree", NULL, hc(cmd, "tree"), ctx->qry.head, ctx->qry.sha1, NULL); cgit_commit_link("commit", NULL, hc(cmd, "commit"), ctx->qry.head, ctx->qry.sha1); cgit_diff_link("diff", NULL, hc(cmd, "diff"), ctx->qry.head, ctx->qry.sha1, ctx->qry.sha2, NULL); if (ctx->repo->max_stats) cgit_stats_link("stats", NULL, hc(cmd, "stats"), ctx->qry.head, NULL); if (ctx->repo->readme) reporevlink("about", "about", NULL, hc(cmd, "about"), ctx->qry.head, NULL, NULL); html("</td><td class='form'>"); html("<form class='right' method='get' action='"); if (ctx->cfg.virtual_root) html_url_path(cgit_fileurl(ctx->qry.repo, "log", ctx->qry.path, NULL)); html("'>\n"); cgit_add_hidden_formfields(1, 0, "log"); html("<select name='qt'>\n"); html_option("grep", "log msg", ctx->qry.grep); html_option("author", "author", ctx->qry.grep); html_option("committer", "committer", ctx->qry.grep); html("</select>\n"); html("<input class='txt' type='text' size='10' name='q' value='"); html_attr(ctx->qry.search); html("'/>\n"); html("<input type='submit' value='search'/>\n"); html("</form>\n"); } else { site_link(NULL, "index", NULL, hc(cmd, "repolist"), NULL, 0); if (ctx->cfg.root_readme) site_link("about", "about", NULL, hc(cmd, "about"), NULL, 0); html("</td><td class='form'>"); html("<form method='get' action='"); html_attr(cgit_rooturl()); html("'>\n"); html("<input type='text' name='q' size='10' value='"); html_attr(ctx->qry.search); html("'/>\n"); html("<input type='submit' value='search'/>\n"); html("</form>"); } html("</td></tr></table>\n"); html("<div class='content'>"); } void cgit_print_filemode(unsigned short mode) { if (S_ISDIR(mode)) html("d"); else if (S_ISLNK(mode)) html("l"); else if (S_ISGITLINK(mode)) html("m"); else html("-"); html_fileperm(mode >> 6); html_fileperm(mode >> 3); html_fileperm(mode); } void cgit_print_snapshot_links(const char *repo, const char *head, const char *hex, int snapshots) { const struct cgit_snapshot_format* f; char *filename; for (f = cgit_snapshot_formats; f->suffix; f++) { if (!(snapshots & f->bit)) continue; filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, f->suffix); cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); html("<br/>"); } } diff --git a/ui-shared.h b/ui-shared.h index 5a3821f..bff4826 100644 --- a/ui-shared.h +++ b/ui-shared.h @@ -1,49 +1,50 @@ #ifndef UI_SHARED_H #define UI_SHARED_H +extern char *cgit_httpscheme(); extern char *cgit_hosturl(); extern char *cgit_repourl(const char *reponame); extern char *cgit_fileurl(const char *reponame, const char *pagename, const char *filename, const char *query); extern char *cgit_pageurl(const char *reponame, const char *pagename, const char *query); extern void cgit_index_link(char *name, char *title, char *class, char *pattern, int ofs); extern void cgit_summary_link(char *name, char *title, char *class, char *head); extern void cgit_tag_link(char *name, char *title, char *class, char *head, char *rev); extern void cgit_tree_link(char *name, char *title, char *class, char *head, char *rev, char *path); extern void cgit_plain_link(char *name, char *title, char *class, char *head, char *rev, char *path); extern void cgit_log_link(char *name, char *title, char *class, char *head, char *rev, char *path, int ofs, char *grep, char *pattern, int showmsg); extern void cgit_commit_link(char *name, char *title, char *class, char *head, char *rev); extern void cgit_patch_link(char *name, char *title, char *class, char *head, char *rev); extern void cgit_refs_link(char *name, char *title, char *class, char *head, char *rev, char *path); extern void cgit_snapshot_link(char *name, char *title, char *class, char *head, char *rev, char *archivename); extern void cgit_diff_link(char *name, char *title, char *class, char *head, char *new_rev, char *old_rev, char *path); extern void cgit_stats_link(char *name, char *title, char *class, char *head, char *path); extern void cgit_object_link(struct object *obj); extern void cgit_print_error(char *msg); extern void cgit_print_date(time_t secs, char *format, int local_time); extern void cgit_print_age(time_t t, time_t max_relative, char *format); extern void cgit_print_http_headers(struct cgit_context *ctx); extern void cgit_print_docstart(struct cgit_context *ctx); extern void cgit_print_docend(); extern void cgit_print_pageheader(struct cgit_context *ctx); extern void cgit_print_filemode(unsigned short mode); extern void cgit_print_snapshot_links(const char *repo, const char *head, const char *hex, int snapshots); extern void cgit_add_hidden_formfields(int incl_head, int incl_search, char *page); #endif /* UI_SHARED_H */ diff --git a/ui-snapshot.c b/ui-snapshot.c index 5372f5d..4136b3e 100644 --- a/ui-snapshot.c +++ b/ui-snapshot.c @@ -1,205 +1,184 @@ /* ui-snapshot.c: generate snapshot of a commit * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "html.h" #include "ui-shared.h" 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; } static int write_tar_gzip_archive(struct archiver_args *args) { return write_compressed_tar_archive(args,"gzip"); } static int write_tar_bzip2_archive(struct archiver_args *args) { return write_compressed_tar_archive(args,"bzip2"); } const struct cgit_snapshot_format cgit_snapshot_formats[] = { { ".zip", "application/x-zip", write_zip_archive, 0x1 }, { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x2 }, { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x4 }, { ".tar", "application/x-tar", write_tar_archive, 0x8 }, {} }; static const struct cgit_snapshot_format *get_format(const char *filename) { const struct cgit_snapshot_format *fmt; int fl, sl; fl = strlen(filename); for(fmt = cgit_snapshot_formats; fmt->suffix; fmt++) { sl = strlen(fmt->suffix); if (sl >= fl) continue; if (!strcmp(fmt->suffix, filename + fl - sl)) return fmt; } return NULL; } static int make_snapshot(const struct cgit_snapshot_format *format, const char *hex, const char *prefix, const char *filename) { struct archiver_args args; struct commit *commit; unsigned char sha1[20]; if(get_sha1(hex, sha1)) { cgit_print_error(fmt("Bad object id: %s", hex)); return 1; } commit = lookup_commit_reference(sha1); if(!commit) { cgit_print_error(fmt("Not a commit reference: %s", hex)); return 1; } memset(&args, 0, sizeof(args)); if (prefix) { args.base = fmt("%s/", prefix); args.baselen = strlen(prefix) + 1; } else { args.base = ""; args.baselen = 0; } args.tree = commit->tree; args.time = commit->date; ctx.page.mimetype = xstrdup(format->mimetype); ctx.page.filename = xstrdup(filename); cgit_print_http_headers(&ctx); format->write_func(&args); return 0; } /* Try to guess the requested revision from the requested snapshot name. * First the format extension is stripped, e.g. "cgit-0.7.2.tar.gz" become * "cgit-0.7.2". If this is a valid commit object name we've got a winner. * Otherwise, if the snapshot name has a prefix matching the result from * repo_basename(), we strip the basename and any following '-' and '_' * characters ("cgit-0.7.2" -> "0.7.2") and check the resulting name once * more. If this still isn't a valid commit object name, we check if pre- * pending a 'v' to the remaining snapshot name ("0.7.2" -> "v0.7.2") gives * us something valid. */ static const char *get_ref_from_filename(const char *url, const char *filename, const struct cgit_snapshot_format *format) { const char *reponame; unsigned char sha1[20]; char *snapshot; snapshot = xstrdup(filename); snapshot[strlen(snapshot) - strlen(format->suffix)] = '\0'; fprintf(stderr, "snapshot=%s\n", snapshot); if (get_sha1(snapshot, sha1) == 0) return snapshot; reponame = cgit_repobasename(url); fprintf(stderr, "reponame=%s\n", reponame); if (prefixcmp(snapshot, reponame) == 0) { snapshot += strlen(reponame); while (snapshot && (*snapshot == '-' || *snapshot == '_')) snapshot++; } if (get_sha1(snapshot, sha1) == 0) return snapshot; snapshot = fmt("v%s", snapshot); if (get_sha1(snapshot, sha1) == 0) return snapshot; return NULL; } void show_error(char *msg) { ctx.page.mimetype = "text/html"; cgit_print_http_headers(&ctx); cgit_print_docstart(&ctx); cgit_print_pageheader(&ctx); cgit_print_error(msg); cgit_print_docend(); } void cgit_print_snapshot(const char *head, const char *hex, const char *filename, int snapshots, int dwim) { const struct cgit_snapshot_format* f; char *prefix = NULL; if (!filename) { show_error("No snapshot name specified"); return; } f = get_format(filename); if (!f) { show_error(xstrdup(fmt("Unsupported snapshot format: %s", filename))); return; } if (!hex && dwim) { hex = get_ref_from_filename(ctx.repo->url, filename, f); if (hex == NULL) { html_status(404, "Not found", 0); return; } prefix = xstrdup(filename); prefix[strlen(filename) - strlen(f->suffix)] = '\0'; } if (!hex) hex = head; if (!prefix) prefix = xstrdup(cgit_repobasename(ctx.repo->url)); make_snapshot(f, hex, prefix, filename); free(prefix); } diff --git a/ui-summary.c b/ui-summary.c index ede4a62..a2c018e 100644 --- a/ui-summary.c +++ b/ui-summary.c @@ -1,76 +1,92 @@ /* ui-summary.c: functions for generating repo summary page * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "html.h" #include "ui-log.h" #include "ui-refs.h" int urls = 0; static void print_url(char *base, char *suffix) { if (!base || !*base) return; if (urls++ == 0) { html("<tr class='nohover'><td colspan='4'> </td></tr>"); html("<tr><th class='left' colspan='4'>Clone</th></tr>\n"); } if (suffix && *suffix) base = fmt("%s/%s", base, suffix); html("<tr><td colspan='4'><a href='"); html_url_path(base); html("'>"); html_txt(base); html("</a></td></tr>\n"); } static void print_urls(char *txt, char *suffix) { char *h = txt, *t, c; while (h && *h) { while (h && *h == ' ') h++; t = h; while (t && *t && *t != ' ') t++; c = *t; *t = 0; print_url(h, suffix); *t = c; h = t; } } void cgit_print_summary() { html("<table summary='repository info' class='list nowrap'>"); cgit_print_branches(ctx.cfg.summary_branches); html("<tr class='nohover'><td colspan='4'> </td></tr>"); cgit_print_tags(ctx.cfg.summary_tags); if (ctx.cfg.summary_log > 0) { html("<tr class='nohover'><td colspan='4'> </td></tr>"); cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL, NULL, NULL, 0); } if (ctx.repo->clone_url) print_urls(ctx.repo->clone_url, NULL); else if (ctx.cfg.clone_prefix) print_urls(ctx.cfg.clone_prefix, ctx.repo->url); html("</table>"); } -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 @@ -1,7 +1,7 @@ #ifndef UI_SUMMARY_H #define UI_SUMMARY_H extern void cgit_print_summary(); -extern void cgit_print_repo_readme(); +extern void cgit_print_repo_readme(char *path); #endif /* UI_SUMMARY_H */ @@ -1,92 +1,92 @@ /* ui-tag.c: display a tag * * Copyright (C) 2007 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "html.h" #include "ui-shared.h" static void print_tag_content(char *buf) { char *p; if (!buf) return; html("<div class='commit-subject'>"); p = strchr(buf, '\n'); if (p) *p = '\0'; html_txt(buf); html("</div>"); if (p) { html("<div class='commit-msg'>"); html_txt(++p); html("</div>"); } } void cgit_print_tag(char *revname) { unsigned char sha1[20]; struct object *obj; struct tag *tag; struct taginfo *info; if (!revname) revname = ctx.qry.head; if (get_sha1(fmt("refs/tags/%s", revname), sha1)) { cgit_print_error(fmt("Bad tag reference: %s", revname)); return; } obj = parse_object(sha1); if (!obj) { cgit_print_error(fmt("Bad object id: %s", sha1_to_hex(sha1))); return; } if (obj->type == OBJ_TAG) { tag = lookup_tag(sha1); if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) { cgit_print_error(fmt("Bad tag object: %s", revname)); return; } html("<table class='commit-info'>\n"); htmlf("<tr><td>Tag name</td><td>"); html_txt(revname); htmlf(" (%s)</td></tr>\n", sha1_to_hex(sha1)); if (info->tagger_date > 0) { html("<tr><td>Tag date</td><td>"); cgit_print_date(info->tagger_date, FMT_LONGDATE, ctx.cfg.local_time); html("</td></tr>\n"); } if (info->tagger) { 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); } html("</td></tr>\n"); } html("<tr><td>Tagged object</td><td>"); cgit_object_link(tag->tagged); html("</td></tr>\n"); html("</table>\n"); print_tag_content(info->msg); } else { html("<table class='commit-info'>\n"); htmlf("<tr><td>Tag name</td><td>"); html_txt(revname); html("</td></tr>\n"); html("<tr><td>Tagged object</td><td>"); cgit_object_link(obj); html("</td></tr>\n"); html("</table>\n"); } return; } @@ -1,262 +1,278 @@ /* ui-tree.c: functions for tree output * * Copyright (C) 2006 Lars Hjemli * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include <ctype.h> #include "cgit.h" #include "html.h" #include "ui-shared.h" char *curr_rev; 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; const char *numberfmt = "<a class='no' id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a>\n"; 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; 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"); html("<td class='lines'><pre><code>"); html_txt(buf); html("</code></pre></td></tr></table>\n"); } #define ROWLEN 32 static void print_binary_buffer(char *buf, unsigned long size) { unsigned long ofs, idx; static char ascii[ROWLEN + 1]; html("<table summary='blob content' class='bin-blob'>\n"); html("<tr><th>ofs</th><th>hex dump</th><th>ascii</th></tr>"); for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { htmlf("<tr><td class='right'>%04x</td><td class='hex'>", ofs); for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) htmlf("%*s%02x", idx == 16 ? 4 : 1, "", buf[idx] & 0xff); html(" </td><td class='hex'>"); for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; ascii[idx] = '\0'; html_txt(ascii); html("</td></tr>\n"); } html("</table>\n"); } -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; char *buf; unsigned long size; type = sha1_object_info(sha1, &size); if (type == OBJ_BAD) { cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(sha1))); return; } buf = read_sha1_file(sha1, &type, &size); if (!buf) { cgit_print_error(fmt("Error reading object %s", sha1_to_hex(sha1))); return; } html(" ("); cgit_plain_link("plain", NULL, NULL, ctx.qry.head, curr_rev, path); htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); if (buffer_is_binary(buf, size)) print_binary_buffer(buf, size); else - print_text_buffer(buf, size); + print_text_buffer(basename, buf, size); } static int ls_item(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned int mode, int stage, void *cbdata) { char *name; char *fullpath; + char *class; enum object_type type; unsigned long size = 0; name = xstrdup(pathname); fullpath = fmt("%s%s%s", ctx.qry.path ? ctx.qry.path : "", ctx.qry.path ? "/" : "", name); if (!S_ISGITLINK(mode)) { type = sha1_object_info(sha1, &size); if (type == OBJ_BAD) { htmlf("<tr><td colspan='3'>Bad object: %s %s</td></tr>", name, sha1_to_hex(sha1)); return 0; } } html("<tr><td class='ls-mode'>"); cgit_print_filemode(mode); html("</td><td>"); if (S_ISGITLINK(mode)) { htmlf("<a class='ls-mod' href='"); html_attr(fmt(ctx.repo->module_link, name, sha1_to_hex(sha1))); html("'>"); html_txt(name); html("</a>"); } else if (S_ISDIR(mode)) { cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, 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); } htmlf("</td><td class='ls-size'>%li</td>", size); html("<td>"); cgit_log_link("log", NULL, "button", ctx.qry.head, curr_rev, fullpath, 0, NULL, NULL, ctx.qry.showmsg); if (ctx.repo->max_stats) cgit_stats_link("stats", NULL, "button", ctx.qry.head, fullpath); html("</td></tr>\n"); free(name); return 0; } static void ls_head() { html("<table summary='tree listing' class='list'>\n"); html("<tr class='nohover'>"); html("<th class='left'>Mode</th>"); html("<th class='left'>Name</th>"); html("<th class='right'>Size</th>"); html("<th/>"); html("</tr>\n"); header = 1; } static void ls_tail() { if (!header) return; html("</table>\n"); header = 0; } static void ls_tree(const unsigned char *sha1, char *path) { struct tree *tree; tree = parse_tree_indirect(sha1); if (!tree) { cgit_print_error(fmt("Not a tree object: %s", sha1_to_hex(sha1))); return; } ls_head(); read_tree_recursive(tree, "", 0, 1, NULL, ls_item, NULL); ls_tail(); } static int walk_tree(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage, void *cbdata) { static int state; static char buffer[PATH_MAX]; char *url; if (state == 0) { memcpy(buffer, base, baselen); strcpy(buffer+baselen, pathname); url = cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s&path=%s", curr_rev, buffer)); html("/"); cgit_tree_link(xstrdup(pathname), NULL, NULL, ctx.qry.head, curr_rev, buffer); if (strcmp(match_path, buffer)) return READ_TREE_RECURSIVE; if (S_ISDIR(mode)) { state = 1; ls_head(); return READ_TREE_RECURSIVE; } else { - print_object(sha1, buffer); + print_object(sha1, buffer, pathname); return 0; } } ls_item(sha1, base, baselen, pathname, mode, stage, NULL); return 0; } /* * Show a tree or a blob * rev: the commit pointing at the root tree object * path: path to tree or blob */ void cgit_print_tree(const char *rev, char *path) { unsigned char sha1[20]; struct commit *commit; const char *paths[] = {path, NULL}; if (!rev) rev = ctx.qry.head; curr_rev = xstrdup(rev); if (get_sha1(rev, sha1)) { cgit_print_error(fmt("Invalid revision name: %s", rev)); return; } commit = lookup_commit_reference(sha1); if (!commit || parse_commit(commit)) { cgit_print_error(fmt("Invalid commit reference: %s", rev)); return; } html("path: <a href='"); html_attr(cgit_pageurl(ctx.qry.repo, "tree", fmt("h=%s", rev))); html("'>root</a>"); if (path == NULL) { ls_tree(commit->tree->object.sha1, NULL); return; } match_path = path; read_tree_recursive(commit->tree, NULL, 0, 0, paths, walk_tree, NULL); ls_tail(); } |