-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | cgit.c | 41 | ||||
-rw-r--r-- | cgit.css | 24 | ||||
-rwxr-xr-x | tests/t0104-tree.sh | 2 | ||||
-rwxr-xr-x | tests/t0107-snapshot.sh | 2 | ||||
-rw-r--r-- | ui-log.c | 35 | ||||
-rw-r--r-- | ui-patch.c | 4 | ||||
-rw-r--r-- | ui-refs.c | 2 | ||||
-rw-r--r-- | ui-snapshot.c | 4 | ||||
-rw-r--r-- | ui-tree.c | 6 |
10 files changed, 93 insertions, 31 deletions
@@ -1,152 +1,152 @@ CGIT_VERSION = v0.8.1 CGIT_SCRIPT_NAME = cgit.cgi CGIT_SCRIPT_PATH = /var/www/htdocs/cgit CGIT_CONFIG = /etc/cgitrc CACHE_ROOT = /var/cache/cgit SHA1_HEADER = <openssl/sha.h> GIT_VER = 1.6.1 GIT_URL = http://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.bz2 # 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-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 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) libgit.a - $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) xdiff/lib.a + $(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 mkdir -p $(DESTDIR)$(CGIT_SCRIPT_PATH) install cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) install -m 0644 cgit.css $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.css install -m 0644 cgit.png $(DESTDIR)$(CGIT_SCRIPT_PATH)/cgit.png uninstall: rm -f $(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME) rm -f $(CGIT_SCRIPT_PATH)/cgit.css rm -f $(CGIT_SCRIPT_PATH)/cgit.png clean: rm -f cgit VERSION *.o *.d get-git: curl $(GIT_URL) | tar -xj && rm -rf git && mv git-$(GIT_VER) git @@ -1,476 +1,477 @@ /* 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 "scan-tree.h" const char *cgit_version = CGIT_VERSION; 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, "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, "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, "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, "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, "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 (!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.module-link")) ctx.repo->module_link= xstrdup(value); else if (ctx.repo && !strcmp(name, "repo.readme") && value != NULL) { if (*value == '/') ctx.repo->readme = xstrdup(value); else ctx.repo->readme = xstrdup(fmt("%s/%s", ctx.repo->path, value)); } else if (!strcmp(name, "include")) parse_configfile(value, config_cb); } static void querystring_cb(const char *name, const char *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); } } 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.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.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->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; } 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; 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"; ctx->repo = NULL; 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 (!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); ctx.repo = NULL; http_parse_querystring(ctx.qry.raw, querystring_cb); - /* If virtual-root isn't specified in cgitrc and no url - * parameter is specified on the querystring, lets pretend - * that virtualroot equals SCRIPT_NAME and 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). + /* If virtual-root isn't specified in cgitrc, lets pretend + * that virtual-root equals SCRIPT_NAME. */ - if (!ctx.cfg.virtual_root && !ctx.qry.url) { + if (!ctx.cfg.virtual_root) ctx.cfg.virtual_root = ctx.cfg.script_name; - path = getenv("PATH_INFO"); - if (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); - } + + /* 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"); + 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.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,473 +1,497 @@ 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; } 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.no { border-right: solid 1px black; color: black; background-color: #eee; text-align: right; } table.blob td.no a { color: black; } table.blob td.no a:hover { color: black; text-decoration: none; } table.blob td.txt { white-space: pre; font-family: monospace; padding-left: 0.5em; } 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; +} diff --git a/tests/t0104-tree.sh b/tests/t0104-tree.sh index 0d62cc8..33f4eb0 100755 --- a/tests/t0104-tree.sh +++ b/tests/t0104-tree.sh @@ -1,33 +1,33 @@ #!/bin/sh . ./setup.sh prepare_tests "Check content on tree page" run_test 'generate bar/tree' 'cgit_url "bar/tree" >trash/tmp' run_test 'find file-1' 'grep -e "file-1" trash/tmp' run_test 'find file-50' 'grep -e "file-50" trash/tmp' run_test 'generate bar/tree/file-50' 'cgit_url "bar/tree/file-50" >trash/tmp' run_test 'find line 1' ' grep -e "<a id=.n1. name=.n1. href=.#n1.>1</a>" trash/tmp ' run_test 'no line 2' ' - grep -e "<a id=.n2. name=.n2. href=.#n2.>2</a>" trash/tmp + ! grep -e "<a id=.n2. name=.n2. href=.#n2.>2</a>" trash/tmp ' run_test 'generate foo+bar/tree' 'cgit_url "foo%2bbar/tree" >trash/tmp' run_test 'verify a+b link' ' grep -e "/foo+bar/tree/a+b" trash/tmp ' run_test 'generate foo+bar/tree?h=1+2' 'cgit_url "foo%2bbar/tree&h=1%2b2" >trash/tmp' run_test 'verify a+b?h=1+2 link' ' grep -e "/foo+bar/tree/a+b?h=1%2b2" trash/tmp ' tests_done diff --git a/tests/t0107-snapshot.sh b/tests/t0107-snapshot.sh index 5f1b862..8ab4912 100755 --- a/tests/t0107-snapshot.sh +++ b/tests/t0107-snapshot.sh @@ -1,39 +1,39 @@ #!/bin/sh . ./setup.sh prepare_tests "Verify snapshot" run_test 'get foo/snapshot/master.tar.gz' ' cgit_url "foo/snapshot/master.tar.gz" >trash/tmp ' run_test 'check html headers' ' head -n 1 trash/tmp | - grep -e "Content-Type: application/x-tar" && + grep -e "Content-Type: application/x-gzip" && head -n 2 trash/tmp | grep -e "Content-Disposition: inline; filename=.master.tar.gz." ' run_test 'strip off the header lines' ' tail -n +6 trash/tmp > trash/master.tar.gz ' run_test 'verify gzip format' 'gunzip --test trash/master.tar.gz' run_test 'untar' ' rm -rf trash/master && tar -xf trash/master.tar.gz -C trash ' run_test 'count files' ' c=$(ls -1 trash/master/ | wc -l) && test $c = 5 ' run_test 'verify untarred file-5' ' grep -e "^5$" trash/master/file-5 && test $(cat trash/master/file-5 | wc -l) = 1 ' tests_done @@ -1,189 +1,224 @@ /* 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) { files++; if (ctx.repo->enable_log_linecount) cgit_diff_files(pair->one->sha1, pair->two->sha1, 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/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("toggle", 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,118 +1,118 @@ /* 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) { 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, print_line)) html("Error running diff"); } 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 %s\n", info->author, info->author_email); html("Date: "); - cgit_print_date(info->author_date, "%a, %d %b %Y %H:%M:%S %z%n", ctx.cfg.local_time); + 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,228 +1,228 @@ /* 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; return cmp_age(r1->tag->tagger_date, r2->tag->tagger_date); } 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>"); html_txt(name); html("</td><td>"); - if (ctx.repo->snapshots && (tag->tagged->type == OBJ_COMMIT)) + if (ctx.repo->snapshots && (ref->object->type == OBJ_COMMIT)) print_tag_downloads(ctx.repo, name); else cgit_object_link(ref->object); 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-snapshot.c b/ui-snapshot.c index f98f5d7..f25613e 100644 --- a/ui-snapshot.c +++ b/ui-snapshot.c @@ -1,194 +1,194 @@ /* 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; 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"); 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"); 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-tar", write_tar_gzip_archive, 0x2 }, - { ".tar.bz2", "application/x-tar", write_tar_bzip2_archive, 0x4 }, + { ".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 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; f = get_format(filename); if (!f) { ctx.page.mimetype = "text/html"; cgit_print_http_headers(&ctx); cgit_print_docstart(&ctx); cgit_print_pageheader(&ctx); cgit_print_error(fmt("Unsupported snapshot format: %s", filename)); cgit_print_docend(); 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); } @@ -1,223 +1,225 @@ /* 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 "cgit.h" #include "html.h" #include "ui-shared.h" char *curr_rev; char *match_path; int header = 0; static void print_object(const unsigned char *sha1, char *path) { enum object_type type; char *buf; unsigned long size, lineno, start, idx; const char *linefmt = "<tr><td class='no'><a id='n%1$d' name='n%1$d' href='#n%1$d'>%1$d</a></td><td class='txt'>"; 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", sha1_to_hex(sha1)); html("<table summary='blob content' class='blob'>\n"); idx = 0; start = 0; lineno = 0; while(idx < size) { if (buf[idx] == '\n') { buf[idx] = '\0'; htmlf(linefmt, ++lineno); html_txt(buf + start); html("</td></tr>\n"); start = idx + 1; } idx++; } - htmlf(linefmt, ++lineno); - html_txt(buf + start); + if (start < idx) { + htmlf(linefmt, ++lineno); + html_txt(buf + start); + } html("</td></tr>\n"); html("</table>\n"); } 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; 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, 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); 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); 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(); } |