-rw-r--r-- | Makefile | 22 | ||||
-rw-r--r-- | cgit.c | 82 | ||||
-rw-r--r-- | cgit.css | 148 | ||||
-rw-r--r-- | cgit.h | 23 | ||||
-rw-r--r-- | cgitrc.5.txt | 58 | ||||
-rw-r--r-- | cmd.c | 48 | ||||
-rw-r--r-- | cmd.h | 3 | ||||
-rwxr-xr-x | filters/commit-links.sh | 16 | ||||
-rwxr-xr-x | filters/syntax-highlighting.sh | 29 | ||||
m--------- | git | 0 | ||||
-rw-r--r-- | html.c | 70 | ||||
-rw-r--r-- | html.h | 18 | ||||
-rw-r--r-- | scan-tree.c | 73 | ||||
-rw-r--r-- | scan-tree.h | 3 | ||||
-rw-r--r-- | shared.c | 88 | ||||
-rw-r--r-- | ui-atom.c | 4 | ||||
-rw-r--r-- | ui-blob.c | 35 | ||||
-rw-r--r-- | ui-blob.h | 1 | ||||
-rw-r--r-- | ui-commit.c | 46 | ||||
-rw-r--r-- | ui-commit.h | 2 | ||||
-rw-r--r-- | ui-diff.c | 92 | ||||
-rw-r--r-- | ui-log.c | 53 | ||||
-rw-r--r-- | ui-patch.c | 8 | ||||
-rw-r--r-- | ui-patch.h | 2 | ||||
-rw-r--r-- | ui-plain.c | 70 | ||||
-rw-r--r-- | ui-refs.c | 4 | ||||
-rw-r--r-- | ui-shared.c | 270 | ||||
-rw-r--r-- | ui-shared.h | 71 | ||||
-rw-r--r-- | ui-snapshot.c | 14 | ||||
-rw-r--r-- | ui-ssdiff.c | 369 | ||||
-rw-r--r-- | ui-ssdiff.h | 13 | ||||
-rw-r--r-- | ui-stats.c | 8 | ||||
-rw-r--r-- | ui-summary.c | 26 | ||||
-rw-r--r-- | ui-tag.c | 24 | ||||
-rw-r--r-- | ui-tree.c | 23 |
35 files changed, 1511 insertions, 305 deletions
@@ -1,25 +1,28 @@ CGIT_VERSION = v0.8.3.3 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.4.3 +GIT_VER = 1.7.2.2 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 NO_OPENSSL to disable linking with OpenSSL and use bundled SHA1 +# implementation (slower). +# # 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') @@ -59,46 +62,47 @@ 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 +EXTLIBS = git/libgit.a git/xdiff/lib.a -lz -lpthread 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-ssdiff.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 \ doc man-doc html-doc clean-doc @@ -114,35 +118,43 @@ 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 +ifdef NO_OPENSSL + CFLAGS += -DNO_OPENSSL + GIT_OPTIONS += NO_OPENSSL=1 +else + EXTLIBS += -lcrypto +endif cgit: $(OBJECTS) libgit $(QUIET_CC)$(CC) $(CFLAGS) $(LDFLAGS) -o cgit $(OBJECTS) $(EXTLIBS) cgit.o: VERSION --include $(OBJECTS:.o=.d) +ifneq "$(MAKECMDGOALS)" "clean" + -include $(OBJECTS:.o=.d) +endif libgit: - $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 libgit.a - $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 xdiff/lib.a + $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) libgit.a + $(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) NO_CURL=1 $(GIT_OPTIONS) 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: @@ -1,36 +1,37 @@ /* cgit.c: cgi for the git scm * * Copyright (C) 2006 Lars Hjemli + * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com> * * 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 = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name)); 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); @@ -51,32 +52,37 @@ void repo_config(struct cgit_repo *repo, const char *name, const char *value) else if (!strcmp(name, "desc")) repo->desc = xstrdup(value); else if (!strcmp(name, "owner")) repo->owner = xstrdup(value); else if (!strcmp(name, "defbranch")) repo->defbranch = xstrdup(value); else if (!strcmp(name, "snapshots")) repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); else if (!strcmp(name, "enable-log-filecount")) repo->enable_log_filecount = ctx.cfg.enable_log_filecount * atoi(value); else if (!strcmp(name, "enable-log-linecount")) repo->enable_log_linecount = ctx.cfg.enable_log_linecount * atoi(value); + else if (!strcmp(name, "enable-remote-branches")) + repo->enable_remote_branches = atoi(value); + else if (!strcmp(name, "enable-subject-links")) + repo->enable_subject_links = atoi(value); else if (!strcmp(name, "max-stats")) repo->max_stats = cgit_find_stats_period(value, NULL); else if (!strcmp(name, "module-link")) repo->module_link= xstrdup(value); else if (!strcmp(name, "section")) repo->section = xstrdup(value); else if (!strcmp(name, "readme") && value != NULL) { - if (*value == '/') + char *colon; + if (*value == '/' || ((colon = strchr(value, ':')) != NULL && colon != value && *(colon + 1) != '\0')) repo->readme = xstrdup(value); else repo->readme = xstrdup(fmt("%s/%s", repo->path, value)); } else if (ctx.cfg.enable_filter_overrides) { if (!strcmp(name, "about-filter")) repo->about_filter = new_filter(value, 0); else if (!strcmp(name, "commit-filter")) repo->commit_filter = new_filter(value, 0); else if (!strcmp(name, "source-filter")) repo->source_filter = new_filter(value, 1); } } @@ -122,102 +128,123 @@ void config_cb(const char *name, const char *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-filter-overrides")) ctx.cfg.enable_filter_overrides = atoi(value); + else if (!strcmp(name, "enable-gitweb-owner")) + ctx.cfg.enable_gitweb_owner = atoi(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, "enable-remote-branches")) + ctx.cfg.enable_remote_branches = atoi(value); + else if (!strcmp(name, "enable-subject-links")) + ctx.cfg.enable_subject_links = atoi(value); else if (!strcmp(name, "enable-tree-linenumbers")) ctx.cfg.enable_tree_linenumbers = 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); + ctx.cfg.cache_root = xstrdup(expand_macros(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-scanrc-ttl")) ctx.cfg.cache_scanrc_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-atom-items")) + ctx.cfg.max_atom_items = 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-blob-size")) + ctx.cfg.max_blob_size = 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, "project-list")) + ctx.cfg.project_list = xstrdup(expand_macros(value)); else if (!strcmp(name, "scan-path")) if (!ctx.cfg.nocache && ctx.cfg.cache_size) - process_cached_repolist(value); + process_cached_repolist(expand_macros(value)); + else if (ctx.cfg.project_list) + scan_projects(expand_macros(value), + ctx.cfg.project_list, repo_config); else - scan_tree(value, repo_config); + scan_tree(expand_macros(value), repo_config); 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, "side-by-side-diffs")) + ctx.cfg.ssdiff = 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, "remove-suffix")) + ctx.cfg.remove_suffix = 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, "include")) - parse_configfile(value, config_cb); + parse_configfile(expand_macros(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")) { + if (*value == '/') + value++; 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; @@ -229,65 +256,79 @@ static void querystring_cb(const char *name, const char *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); + } else if (!strcmp(name, "ss")) { + ctx.qry.ssdiff = atoi(value); + } else if (!strcmp(name, "all")) { + ctx.qry.show_all = atoi(value); + } else if (!strcmp(name, "context")) { + ctx.qry.context = atoi(value); + } else if (!strcmp(name, "ignorews")) { + ctx.qry.ignorews = atoi(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_scanrc_ttl = 15; ctx->cfg.cache_static_ttl = -1; ctx->cfg.css = "/cgit.css"; ctx->cfg.logo = "/cgit.png"; ctx->cfg.local_time = 0; + ctx->cfg.enable_gitweb_owner = 1; ctx->cfg.enable_tree_linenumbers = 1; 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_blob_size = 0; ctx->cfg.max_stats = 0; ctx->cfg.module_link = "./?repo=%s&page=commit&id=%s"; + ctx->cfg.project_list = NULL; ctx->cfg.renamelimit = -1; + ctx->cfg.remove_suffix = 0; 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.section = ""; ctx->cfg.summary_branches = 10; ctx->cfg.summary_log = 10; ctx->cfg.summary_tags = 10; + ctx->cfg.max_atom_items = 10; + ctx->cfg.ssdiff = 0; 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; @@ -401,24 +442,30 @@ static void process_request(void *cbdata) 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_vpath is set, assume ctx->qry.path contains a "virtual" + * in-project path limit to be made available at ctx->qry.vpath. + * Otherwise, no path limit is in effect (ctx->qry.vpath = NULL). + */ + ctx->qry.vpath = cmd->want_vpath ? ctx->qry.path : NULL; + 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; @@ -532,49 +579,60 @@ static int generate_cached_repolist(const char *path, const char *cached_rc) locked_rc = xstrdup(fmt("%s.lock", cached_rc)); f = fopen(locked_rc, "wx"); if (!f) { /* Inform about the error unless the lockfile already existed, * since that only means we've got concurrent requests. */ if (errno != EEXIST) fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", locked_rc, strerror(errno), errno); return errno; } idx = cgit_repolist.count; - scan_tree(path, repo_config); + if (ctx.cfg.project_list) + scan_projects(path, ctx.cfg.project_list, repo_config); + else + scan_tree(path, repo_config); print_repolist(f, &cgit_repolist, idx); if (rename(locked_rc, cached_rc)) fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", locked_rc, cached_rc, strerror(errno), errno); fclose(f); return 0; } static void process_cached_repolist(const char *path) { struct stat st; char *cached_rc; time_t age; + unsigned long hash; - cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, - hash_str(path))); + hash = hash_str(path); + if (ctx.cfg.project_list) + hash += hash_str(ctx.cfg.project_list); + cached_rc = xstrdup(fmt("%s/rc-%8x", ctx.cfg.cache_root, hash)); if (stat(cached_rc, &st)) { /* Nothing is cached, we need to scan without forking. And * if we fail to generate a cached repolist, we need to * invoke scan_tree manually. */ - if (generate_cached_repolist(path, cached_rc)) - scan_tree(path, repo_config); + if (generate_cached_repolist(path, cached_rc)) { + if (ctx.cfg.project_list) + scan_projects(path, ctx.cfg.project_list, + repo_config); + else + scan_tree(path, repo_config); + } return; } parse_configfile(cached_rc, config_cb); /* If the cached configfile hasn't expired, lets exit now */ age = time(NULL) - st.st_mtime; if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) return; /* The cached repolist has been parsed, but it was old. So lets * rescan the specified path and generate a new cached repolist @@ -665,25 +723,25 @@ static int calc_ttl() int main(int argc, const char **argv) { const char *path; char *qry; int err, ttl; prepare_context(&ctx); cgit_repolist.length = 0; cgit_repolist.count = 0; cgit_repolist.repos = NULL; cgit_parse_args(argc, argv); - parse_configfile(ctx.env.cgit_config, config_cb); + parse_configfile(expand_macros(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 @@ -55,25 +55,25 @@ 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-bottom: solid 3px #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 { @@ -93,28 +93,34 @@ table.tabs td.form { 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.path { + margin: 0px; + padding: 5px 2em 2px 2em; + color: #000; + background-color: #eee; +} + 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; @@ -149,28 +155,44 @@ table.list td { table.list td.logsubject { font-family: monospace; font-weight: bold; } table.list td.logmsg { font-family: monospace; white-space: pre; padding: 1em 0.5em 2em 0.5em; } +table.list td.lognotes-label { + text-align:right; + vertical-align:top; +} + +table.list td.lognotes { + font-family: monospace; + white-space: pre; + padding: 0em 0.5em 2em 0.5em; +} + table.list td a { color: black; } +table.list td a.ls-dir { + font-weight: bold; + color: #00f; +} + table.list td a:hover { color: #00f; } img { border: none; } input#switch-btn { margin: 2px 0px 0px 0px; } @@ -306,24 +328,42 @@ table.commit-info td { 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.notes-header { + font-weight: bold; + padding-top: 1.5em; +} + +div.notes { + white-space: pre; + font-family: monospace; + border: solid 1px #ee9; + background-color: #ffd; + padding: 0.3em 2em 0.3em 1em; + float: left; +} + +div.notes-footer { + clear: left; +} + 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 { @@ -511,25 +551,28 @@ 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 { +div.commit-subject a.branch-deco, +div.commit-subject a.tag-deco, +div.commit-subject a.remote-deco, +div.commit-subject a.deco { 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; @@ -592,12 +635,111 @@ table.hgraph th { padding: 1px 0.5em; } table.hgraph td { vertical-align: center; padding: 2px 2px; } table.hgraph div.bar { background-color: #eee; height: 1em; } + +table.ssdiff { + width: 100%; +} + +table.ssdiff td { + font-size: 75%; + font-family: monospace; + white-space: pre; + padding: 1px 4px 1px 4px; + border-left: solid 1px #aaa; + border-right: solid 1px #aaa; +} + +table.ssdiff td.add { + color: black; + background: #cfc; + min-width: 50%; +} + +table.ssdiff td.add_dark { + color: black; + background: #aca; + min-width: 50%; +} + +table.ssdiff span.add { + background: #cfc; + font-weight: bold; +} + +table.ssdiff td.del { + color: black; + background: #fcc; + min-width: 50%; +} + +table.ssdiff td.del_dark { + color: black; + background: #caa; + min-width: 50%; +} + +table.ssdiff span.del { + background: #fcc; + font-weight: bold; +} + +table.ssdiff td.changed { + color: black; + background: #ffc; + min-width: 50%; +} + +table.ssdiff td.changed_dark { + color: black; + background: #cca; + min-width: 50%; +} + +table.ssdiff td.lineno { + color: black; + background: #eee; + text-align: right; + width: 3em; + min-width: 3em; +} + +table.ssdiff td.hunk { + color: #black; + background: #ccf; + border-top: solid 1px #aaa; + border-bottom: solid 1px #aaa; +} + +table.ssdiff td.head { + border-top: solid 1px #aaa; + border-bottom: solid 1px #aaa; +} + +table.ssdiff td.head div.head { + font-weight: bold; + color: black; +} + +table.ssdiff td.foot { + border-top: solid 1px #aaa; + border-left: none; + border-right: none; + border-bottom: none; +} + +table.ssdiff td.space { + border: none; +} + +table.ssdiff td.space div { + min-height: 3em; +}
\ No newline at end of file @@ -10,24 +10,25 @@ #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> +#include <notes.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 @@ -63,24 +64,26 @@ struct cgit_repo { char *name; char *path; char *desc; char *owner; char *defbranch; char *module_link; char *readme; char *section; char *clone_url; int snapshots; int enable_log_filecount; int enable_log_linecount; + int enable_remote_branches; + int enable_subject_links; int max_stats; time_t mtime; struct cgit_filter *about_filter; struct cgit_filter *commit_filter; struct cgit_filter *source_filter; }; typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name, const char *value); struct cgit_repolist { int length; @@ -134,75 +137,88 @@ struct cgit_query { char *head; char *sha1; char *sha2; char *path; char *name; char *mimetype; char *url; char *period; int ofs; int nohead; char *sort; int showmsg; + int ssdiff; + int show_all; + int context; + int ignorews; + char *vpath; }; 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 *project_list; char *robots; char *root_title; char *root_desc; char *root_readme; char *script_name; char *section; 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_scanrc_ttl; int cache_static_ttl; int embedded; int enable_filter_overrides; + int enable_gitweb_owner; int enable_index_links; int enable_log_filecount; int enable_log_linecount; + int enable_remote_branches; + int enable_subject_links; int enable_tree_linenumbers; int local_time; + int max_atom_items; int max_repo_count; int max_commit_count; int max_lock_attempts; int max_msg_len; int max_repodesc_len; + int max_blob_size; int max_stats; int nocache; int noplainemail; int noheader; int renamelimit; + int remove_suffix; int snapshots; int summary_branches; int summary_log; int summary_tags; + int ssdiff; 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; @@ -259,36 +275,39 @@ 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); + int *binary, int context, int ignorews, + linediff_fn fn); extern void cgit_diff_tree(const unsigned char *old_sha1, const unsigned char *new_sha1, - filepair_fn fn, const char *prefix); + filepair_fn fn, const char *prefix, int ignorews); 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); extern int readfile(const char *path, char **buf, size_t *size); +extern char *expand_macros(const char *txt); + #endif /* CGIT_H */ diff --git a/cgitrc.5.txt b/cgitrc.5.txt index 0c13485..c643fae 100644 --- a/cgitrc.5.txt +++ b/cgitrc.5.txt @@ -86,39 +86,55 @@ css:: Url which specifies the css document to include in all cgit pages. Default value: "/cgit.css". embedded:: Flag which, when set to "1", will make cgit generate a html fragment suitable for embedding in other html pages. Default value: none. See also: "noheader". enable-filter-overrides:: Flag which, when set to "1", allows all filter settings to be overridden in repository-specific cgitrc files. Default value: none. +enable-gitweb-owner:: + If set to "1" and scan-path is enabled, we first check each repository + for the git config value "gitweb.owner" to determine the owner. + Default value: "1". See also: scan-path. + 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:: 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:: 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". +enable-remote-branches:: + Flag which, when set to "1", will make cgit display remote branches + in the summary and refs views. Default value: "0". See also: + "repo.enable-remote-branches". + +enable-subject-links:: + Flag which, when set to "1", will make cgit use the subject of the + parent commit as link text when generating links to parent commits + in commit view. Default value: "0". See also: + "repo.enable-subject-links". + enable-tree-linenumbers:: Flag which, when set to "1", will make cgit generate linenumber links for plaintext blobs printed in the tree view. Default value: "1". favicon:: Url used as link to a shortcut icon for cgit. If specified, it is suggested to use the value "/favicon.ico" since certain browsers will ignore other values. Default value: none. 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 @@ -152,40 +168,48 @@ local-time:: Flag which, if set to "1", makes cgit print commit and tag times in the servers timezone. Default value: "0". logo:: Url which specifies the source of an image which will be used as a logo on all cgit pages. Default value: "/cgit.png". 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-atom-items:: + Specifies the number of items to display in atom feeds view. Default + value: "10". + max-commit-count:: Specifies the number of entries to list per page in "log" view. Default value: "50". max-message-length:: Specifies the maximum number of commit message characters to display in "log" view. Default value: "80". max-repo-count:: Specifies the number of entries to list per page on the repository index page. Default value: "50". max-repodesc-length:: Specifies the maximum number of repo description characters to display on the repository index page. Default value: "80". +max-blob-size:: + Specifies the maximum size of a blob to display HTML for in KBytes. + Default value: "0" (limit disabled). + 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". 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 @@ -196,24 +220,34 @@ 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". 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". +project-list:: + A list of subdirectories inside of scan-path, relative to it, that + should loaded as git repositories. This must be defined prior to + scan-path. Default value: none. See also: scan-path. + +remove-suffix:: + If set to "1" and scan-path is enabled, if any repositories are found + with a suffix of ".git", this suffix will be removed for the url and + name. Default value: "0". See also: scan-path. + 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:: Legacy alias for "section". This option is deprecated and will not be supported in cgit-1.0. robots:: Text used as content for the "robots" meta-tag. Default value: "index, nofollow". @@ -225,31 +259,38 @@ root-desc:: 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:: Text printed as heading on the repository index page. Default value: "Git Repository Browser". scan-path:: A path which will be scanned for repositories. If caching is enabled, the result will be cached as a cgitrc include-file in the cache - directory. Default value: none. See also: cache-scanrc-ttl. + directory. If project-list has been defined prior to scan-path, + scan-path loads only the directories listed in the file pointed to by + project-list. Default value: none. See also: cache-scanrc-ttl, + project-list. section:: The name of the current repository section - all repositories defined after this option will inherit the current section name. Default value: none. +side-by-side-diffs:: + If set to "1" shows side-by-side diffs instead of unidiffs per + default. Default value: "0". + snapshots:: Text which specifies the default set of snapshot formats generated by cgit. The value is a space-separated list of zero or more of the values "tar", "tar.gz", "tar.bz2" and "zip". Default value: none. source-filter:: Specifies a command which will be invoked to format plaintext blobs in the tree view. The command will get the blob content on its STDIN and the name of the blob as its only command line argument. The STDOUT from the command will be included verbatim as the blob contents, i.e. this can be used to implement e.g. syntax highlighting. Default value: none. @@ -295,43 +336,53 @@ repo.defbranch:: repo.desc:: The value to show as repository description. Default value: none. 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:: A flag which can be used to disable the global setting `enable-log-linecount'. Default value: none. +repo.enable-remote-branches:: + Flag which, when set to "1", will make cgit display remote branches + in the summary and refs views. Default value: <enable-remote-branches>. + +repo.enable-subject-links:: + A flag which can be used to override the global setting + `enable-subject-links'. Default value: none. + 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:: The value to show as repository name. Default value: <repo.url>. repo.owner:: A value used to identify the owner of the repository. Default value: none. repo.path:: An absolute path to the repository directory. For non-bare repositories this is the .git-directory. Default value: none. 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. + verbatim as the "About" page for this repo. You may also specify a + git refspec by head or by hash by prepending the refspec followed by + a colon. For example, "master:docs/readme.mkd" Default value: none. repo.snapshots:: A mask of allowed snapshot-formats for this repo, restricted by the "snapshots" global setting. Default value: <snapshots>. repo.section:: Override the current section name for this repository. Default value: none. repo.source-filter:: Override the default source-filter. Default value: none. See also: "enable-filter-overrides". @@ -404,25 +455,25 @@ 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.gif=image/gif 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 section is unset will not be ## displayed under a section heading @@ -490,12 +541,13 @@ 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> +Jason A. Donenfeld <Jason@zx2c4.com> @@ -24,43 +24,43 @@ #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); + cgit_print_atom(ctx->qry.head, ctx->qry.path, ctx->cfg.max_atom_items); } static void about_fn(struct cgit_context *ctx) { if (ctx->repo) 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); + cgit_print_commit(ctx->qry.sha1, ctx->qry.path); } 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); } @@ -81,25 +81,25 @@ static void ls_cache_fn(struct cgit_context *ctx) 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); + cgit_print_patch(ctx->qry.sha1, ctx->qry.path); } static void plain_fn(struct cgit_context *ctx) { cgit_print_plain(ctx); } static void refs_fn(struct cgit_context *ctx) { cgit_print_refs(); } @@ -120,49 +120,49 @@ static void summary_fn(struct cgit_context *ctx) } 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} +#define def_cmd(name, want_repo, want_layout, want_vpath) \ + {#name, name##_fn, want_repo, want_layout, want_vpath} 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), + def_cmd(HEAD, 1, 0, 0), + def_cmd(atom, 1, 0, 0), + def_cmd(about, 0, 1, 0), + def_cmd(blob, 1, 0, 0), + def_cmd(commit, 1, 1, 1), + def_cmd(diff, 1, 1, 1), + def_cmd(info, 1, 0, 0), + def_cmd(log, 1, 1, 1), + def_cmd(ls_cache, 0, 0, 0), + def_cmd(objects, 1, 0, 0), + def_cmd(patch, 1, 0, 1), + def_cmd(plain, 1, 0, 0), + def_cmd(refs, 1, 1, 0), + def_cmd(repolist, 0, 0, 0), + def_cmd(snapshot, 1, 0, 0), + def_cmd(stats, 1, 1, 1), + def_cmd(summary, 1, 1, 0), + def_cmd(tag, 1, 1, 0), + def_cmd(tree, 1, 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)) @@ -1,15 +1,16 @@ #ifndef CMD_H #define CMD_H typedef void (*cgit_cmd_fn)(struct cgit_context *ctx); struct cgit_cmd { const char *name; cgit_cmd_fn fn; unsigned int want_repo:1, - want_layout:1; + want_layout:1, + want_vpath:1; }; extern struct cgit_cmd *cgit_get_cmd(struct cgit_context *ctx); #endif /* CMD_H */ diff --git a/filters/commit-links.sh b/filters/commit-links.sh index 165a533..110c609 100755 --- a/filters/commit-links.sh +++ b/filters/commit-links.sh @@ -1,12 +1,14 @@ #!/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. +# This script can be used to generate links in commit messages. # # 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 -' +# This expression generates links to commits referenced by their SHA1. +regex=$regex' +s|\b([0-9a-fA-F]{8,40})\b|<a href="./?id=\1">\1</a>|g' +# This expression generates links to a fictional bugtracker. +regex=$regex' +s| #([0-9]+)\b|<a href="http://bugs.example.com/?bug=\1">#\1</a>|g' + +sed -re "$regex" diff --git a/filters/syntax-highlighting.sh b/filters/syntax-highlighting.sh index 999ad0c..6b1c576 100755 --- a/filters/syntax-highlighting.sh +++ b/filters/syntax-highlighting.sh @@ -1,39 +1,34 @@ #!/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. # +# This script requires a shell supporting the ${var##pattern} syntax. +# It is supported by at least dash and bash, however busybox environments +# might have to use an external call to sed instead. +# # 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 +# store filename and extension in local vars +BASENAME="$1" +EXTENSION="${BASENAME##*.}" + +# map Makefile and Makefile.* to .mk +[ "${BASENAME%%.*}" == "Makefile" ] && EXTENSION=mk + +exec highlight --force -f -I -X -S $EXTENSION 2>/dev/null diff --git a/git b/git -Subproject 7fb6bcff2dece2ff9fbc5ebfe526d9b2a7e764c +Subproject 8c67c392e1620fc3b749aa9e0b8da13bd84226f @@ -4,24 +4,50 @@ * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> #include <errno.h> +/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */ +static const char* url_escape_table[256] = { + "%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09", + "%0a", "%0b", "%0c", "%0d", "%0e", "%0f", "%10", "%11", "%12", "%13", + "%14", "%15", "%16", "%17", "%18", "%19", "%1a", "%1b", "%1c", "%1d", + "%1e", "%1f", "%20", 0, "%22", "%23", 0, "%25", "%26", "%27", 0, 0, 0, + "%2b", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%3c", "%3d", + "%3e", "%3f", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, "%5c", 0, "%5e", 0, "%60", 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "%7b", + "%7c", "%7d", 0, "%7f", "%80", "%81", "%82", "%83", "%84", "%85", + "%86", "%87", "%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f", + "%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97", "%98", "%99", + "%9a", "%9b", "%9c", "%9d", "%9e", "%9f", "%a0", "%a1", "%a2", "%a3", + "%a4", "%a5", "%a6", "%a7", "%a8", "%a9", "%aa", "%ab", "%ac", "%ad", + "%ae", "%af", "%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7", + "%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf", "%c0", "%c1", + "%c2", "%c3", "%c4", "%c5", "%c6", "%c7", "%c8", "%c9", "%ca", "%cb", + "%cc", "%cd", "%ce", "%cf", "%d0", "%d1", "%d2", "%d3", "%d4", "%d5", + "%d6", "%d7", "%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df", + "%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7", "%e8", "%e9", + "%ea", "%eb", "%ec", "%ed", "%ee", "%ef", "%f0", "%f1", "%f2", "%f3", + "%f4", "%f5", "%f6", "%f7", "%f8", "%f9", "%fa", "%fb", "%fc", "%fd", + "%fe", "%ff" +}; + int htmlfd = STDOUT_FILENO; char *fmt(const char *format, ...) { static char buf[8][1024]; static int bufidx; int len; va_list args; bufidx++; bufidx &= 7; @@ -54,145 +80,147 @@ void htmlf(const char *format, ...) vsnprintf(buf, sizeof(buf), format, args); va_end(args); html(buf); } void html_status(int code, const char *msg, int more_headers) { htmlf("Status: %d %s\n", code, msg); if (!more_headers) html("\n"); } -void html_txt(char *txt) +void html_txt(const char *txt) { - char *t = txt; + const char *t = txt; while(t && *t){ int c = *t; if (c=='<' || c=='>' || c=='&') { write(htmlfd, txt, t - txt); if (c=='>') html(">"); else if (c=='<') html("<"); else if (c=='&') html("&"); txt = t+1; } t++; } if (t!=txt) html(txt); } -void html_ntxt(int len, char *txt) +void html_ntxt(int len, const char *txt) { - char *t = txt; + const char *t = txt; while(t && *t && len--){ int c = *t; if (c=='<' || c=='>' || c=='&') { write(htmlfd, txt, t - txt); if (c=='>') html(">"); else if (c=='<') html("<"); else if (c=='&') html("&"); txt = t+1; } t++; } if (t!=txt) write(htmlfd, txt, t - txt); if (len<0) html("..."); } -void html_attr(char *txt) +void html_attr(const char *txt) { - char *t = txt; + const char *t = txt; while(t && *t){ int c = *t; if (c=='<' || c=='>' || c=='\'' || c=='\"') { write(htmlfd, txt, t - txt); if (c=='>') html(">"); else if (c=='<') html("<"); else if (c=='\'') html("'"); else if (c=='"') html("""); txt = t+1; } t++; } if (t!=txt) html(txt); } -void html_url_path(char *txt) +void html_url_path(const char *txt) { - char *t = txt; + const char *t = txt; while(t && *t){ int c = *t; - if (c=='"' || c=='#' || c=='\'' || c=='?') { + const char *e = url_escape_table[c]; + if (e && c!='+' && c!='&' && c!='+') { write(htmlfd, txt, t - txt); - write(htmlfd, fmt("%%%2x", c), 3); + write(htmlfd, e, 3); txt = t+1; } t++; } if (t!=txt) html(txt); } -void html_url_arg(char *txt) +void html_url_arg(const char *txt) { - char *t = txt; + const char *t = txt; while(t && *t){ int c = *t; - if (c=='"' || c=='#' || c=='%' || c=='&' || c=='\'' || c=='+' || c=='?') { + const char *e = url_escape_table[c]; + if (e) { write(htmlfd, txt, t - txt); - write(htmlfd, fmt("%%%2x", c), 3); + write(htmlfd, e, 3); txt = t+1; } t++; } if (t!=txt) html(txt); } -void html_hidden(char *name, char *value) +void html_hidden(const char *name, const char *value) { html("<input type='hidden' name='"); html_attr(name); html("' value='"); html_attr(value); html("'/>"); } -void html_option(char *value, char *text, char *selected_value) +void html_option(const char *value, const char *text, const char *selected_value) { html("<option value='"); html_attr(value); html("'"); if (selected_value && !strcmp(selected_value, value)) html(" selected='selected'"); html(">"); html_txt(text); html("</option>\n"); } -void html_link_open(char *url, char *title, char *class) +void html_link_open(const char *url, const char *title, const char *class) { html("<a href='"); html_attr(url); if (title) { html("' title='"); html_attr(title); } if (class) { html("' class='"); html_attr(class); } html("'>"); @@ -249,32 +277,32 @@ char *convert_query_hexchar(char *txt) d1 = hextoint(*(txt+1)); d2 = hextoint(*(txt+2)); if (d1<0 || d2<0) { memmove(txt, txt+3, n-3); return txt-1; } else { *txt = d1 * 16 + d2; memmove(txt+1, txt+3, n-2); return txt; } } -int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)) +int http_parse_querystring(const char *txt_, void (*fn)(const char *name, const char *value)) { - char *t, *value = NULL, c; + char *t, *txt, *value = NULL, c; - if (!txt) + if (!txt_) return 0; - t = txt = strdup(txt); + t = txt = strdup(txt_); if (t == NULL) { printf("Out of memory\n"); exit(1); } while((c=*t) != '\0') { if (c=='=') { *t = '\0'; value = t+1; } else if (c=='+') { *t = ' '; } else if (c=='%') { t = convert_query_hexchar(t); @@ -1,24 +1,24 @@ #ifndef HTML_H #define HTML_H extern int htmlfd; extern void html_raw(const char *txt, size_t size); extern void html(const char *txt); extern void htmlf(const char *format,...); extern void html_status(int code, const char *msg, int more_headers); -extern void html_txt(char *txt); -extern void html_ntxt(int len, char *txt); -extern void html_attr(char *txt); -extern void html_url_path(char *txt); -extern void html_url_arg(char *txt); -extern void html_hidden(char *name, char *value); -extern void html_option(char *value, char *text, char *selected_value); -extern void html_link_open(char *url, char *title, char *class); +extern void html_txt(const char *txt); +extern void html_ntxt(int len, const char *txt); +extern void html_attr(const char *txt); +extern void html_url_path(const char *txt); +extern void html_url_arg(const char *txt); +extern void html_hidden(const char *name, const char *value); +extern void html_option(const char *value, const char *text, const char *selected_value); +extern void html_link_open(const char *url, const char *title, const char *class); extern void html_link_close(void); extern void html_fileperm(unsigned short mode); extern int html_include(const char *filename); -extern int http_parse_querystring(char *txt, void (*fn)(const char *name, const char *value)); +extern int http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value)); #endif /* HTML_H */ diff --git a/scan-tree.c b/scan-tree.c index dbca797..e987824 100644 --- a/scan-tree.c +++ b/scan-tree.c @@ -1,12 +1,21 @@ +/* scan-tree.c + * + * Copyright (C) 2008-2009 Lars Hjemli + * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com> + * + * Licensed under GNU General Public License v2 + * (see COPYING for full license text) + */ + #include "cgit.h" #include "configfile.h" #include "html.h" #define MAX_PATH 4096 /* return 1 if path contains a objects/ directory and a HEAD file */ static int is_git_dir(const char *path) { struct stat st; static char buf[MAX_PATH]; @@ -29,62 +38,82 @@ static int is_git_dir(const char *path) fprintf(stderr, "Error checking path %s: %s (%d)\n", path, strerror(errno), errno); return 0; } if (!S_ISREG(st.st_mode)) return 0; return 1; } struct cgit_repo *repo; repo_config_fn config_fn; +char *owner; static void repo_config(const char *name, const char *value) { config_fn(repo, name, value); } +static int git_owner_config(const char *key, const char *value, void *cb) +{ + if (!strcmp(key, "gitweb.owner")) + owner = xstrdup(value); + return 0; +} + static void add_repo(const char *base, const char *path, repo_config_fn fn) { struct stat st; struct passwd *pwd; char *p; size_t size; if (stat(path, &st)) { fprintf(stderr, "Error accessing %s: %s (%d)\n", path, strerror(errno), errno); return; } - if ((pwd = getpwuid(st.st_uid)) == NULL) { - fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", - path, strerror(errno), errno); + if (!stat(fmt("%s/noweb", path), &st)) return; - } + + owner = NULL; + if (ctx.cfg.enable_gitweb_owner) + git_config_from_file(git_owner_config, fmt("%s/config", path), NULL); if (base == path) p = fmt("%s", path); else p = fmt("%s", path + strlen(base) + 1); if (!strcmp(p + strlen(p) - 5, "/.git")) p[strlen(p) - 5] = '\0'; repo = cgit_add_repo(xstrdup(p)); + if (ctx.cfg.remove_suffix) + if ((p = strrchr(repo->url, '.')) && !strcmp(p, ".git")) + *p = '\0'; repo->name = repo->url; repo->path = xstrdup(path); - p = (pwd && pwd->pw_gecos) ? strchr(pwd->pw_gecos, ',') : NULL; - if (p) - *p = '\0'; - repo->owner = (pwd ? xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name) : ""); + while (!owner) { + if ((pwd = getpwuid(st.st_uid)) == NULL) { + fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n", + path, strerror(errno), errno); + break; + } + if (pwd->pw_gecos) + if ((p = strchr(pwd->pw_gecos, ','))) + *p = '\0'; + owner = xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name); + } + repo->owner = owner; p = fmt("%s/description", path); if (!stat(p, &st)) readfile(p, &repo->desc, &size); p = fmt("%s/README.html", path); if (!stat(p, &st)) repo->readme = "README.html"; p = fmt("%s/cgitrc", path); if (!stat(p, &st)) { config_fn = fn; @@ -131,16 +160,44 @@ static void scan_path(const char *base, const char *path, repo_config_fn fn) fprintf(stderr, "Error checking path %s: %s (%d)\n", buf, strerror(errno), errno); free(buf); continue; } if (S_ISDIR(st.st_mode)) scan_path(base, buf, fn); free(buf); } closedir(dir); } +#define lastc(s) s[strlen(s) - 1] + +void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn) +{ + char line[MAX_PATH * 2], *z; + FILE *projects; + int err; + + projects = fopen(projectsfile, "r"); + if (!projects) { + fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n", + projectsfile, strerror(errno), errno); + } + while (fgets(line, sizeof(line), projects) != NULL) { + for (z = &lastc(line); + strlen(line) && strchr("\n\r", *z); + z = &lastc(line)) + *z = '\0'; + if (strlen(line)) + scan_path(path, fmt("%s/%s", path, line), fn); + } + if ((err = ferror(projects))) { + fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n", + projectsfile, strerror(err), err); + } + fclose(projects); +} + void scan_tree(const char *path, repo_config_fn fn) { scan_path(path, path, fn); } diff --git a/scan-tree.h b/scan-tree.h index 11539f4..1afbd4b 100644 --- a/scan-tree.h +++ b/scan-tree.h @@ -1,3 +1,2 @@ - - +extern void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn); extern void scan_tree(const char *path, repo_config_fn fn); @@ -1,25 +1,24 @@ /* 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)); @@ -50,24 +49,26 @@ struct cgit_repo *cgit_add_repo(const char *url) ret = &cgit_repolist.repos[cgit_repolist.count-1]; memset(ret, 0, sizeof(struct cgit_repo)); ret->url = trim_end(url, '/'); ret->name = ret->url; ret->path = NULL; ret->desc = "[no description]"; ret->owner = NULL; ret->section = ctx.cfg.section; 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->enable_remote_branches = ctx.cfg.enable_remote_branches; + ret->enable_subject_links = ctx.cfg.enable_subject_links; 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) { @@ -253,25 +254,26 @@ int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf) } 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) + unsigned long *new_size, int *binary, int context, + int ignorews, 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; @@ -280,74 +282,79 @@ int cgit_diff_files(const unsigned char *old_sha1, *binary = 1; if (file1.size) free(file1.ptr); if (file2.size) free(file2.ptr); 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; + if (ignorews) + diff_params.flags |= XDF_IGNORE_WHITESPACE; + emit_params.ctxlen = context > 0 ? context : 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); if (file1.size) free(file1.ptr); if (file2.size) free(file2.ptr); return 0; } void cgit_diff_tree(const unsigned char *old_sha1, const unsigned char *new_sha1, - filepair_fn fn, const char *prefix) + filepair_fn fn, const char *prefix, int ignorews) { 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); + if (ignorews) + DIFF_XDL_SET(&opt, IGNORE_WHITESPACE); 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); + cgit_diff_tree(old_sha1, commit->object.sha1, fn, NULL, + ctx.qry.ignorews); } 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(;;) { @@ -421,12 +428,83 @@ int readfile(const char *path, char **buf, size_t *size) } if (!S_ISREG(st.st_mode)) { close(fd); return EISDIR; } *buf = xmalloc(st.st_size + 1); *size = read_in_full(fd, *buf, st.st_size); e = errno; (*buf)[*size] = '\0'; close(fd); return (*size == st.st_size ? 0 : e); } + +int is_token_char(char c) +{ + return isalnum(c) || c == '_'; +} + +/* Replace name with getenv(name), return pointer to zero-terminating char + */ +char *expand_macro(char *name, int maxlength) +{ + char *value; + int len; + + len = 0; + value = getenv(name); + if (value) { + len = strlen(value); + if (len > maxlength) + len = maxlength; + strncpy(name, value, len); + } + return name + len; +} + +#define EXPBUFSIZE (1024 * 8) + +/* Replace all tokens prefixed by '$' in the specified text with the + * value of the named environment variable. + * NB: the return value is a static buffer, i.e. it must be strdup'd + * by the caller. + */ +char *expand_macros(const char *txt) +{ + static char result[EXPBUFSIZE]; + char *p, *start; + int len; + + p = result; + start = NULL; + while (p < result + EXPBUFSIZE - 1 && txt && *txt) { + *p = *txt; + if (start) { + if (!is_token_char(*txt)) { + if (p - start > 0) { + *p = '\0'; + len = result + EXPBUFSIZE - start - 1; + p = expand_macro(start, len) - 1; + } + start = NULL; + txt--; + } + p++; + txt++; + continue; + } + if (*txt == '$') { + start = p; + txt++; + continue; + } + p++; + txt++; + } + *p = '\0'; + if (start && p - start > 0) { + len = result + EXPBUFSIZE - start - 1; + p = expand_macro(start, len); + *p = '\0'; + } + return result; +} @@ -76,25 +76,27 @@ void add_entry(struct commit *commit, char *host) 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) + if (ctx.qry.show_all) + argv[1] = "--all"; + else 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; @@ -1,39 +1,72 @@ /* ui-blob.c: show blob content * * Copyright (C) 2008 Lars Hjemli + * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com> * * 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 found_path; 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); + found_path = 1; return 0; } -void cgit_print_blob(const char *hex, char *path, const char *head) +int cgit_print_file(char *path, const char *head) { + unsigned char sha1[20]; + enum object_type type; + char *buf; + unsigned long size; + struct commit *commit; + const char *paths[] = {path, NULL}; + if (get_sha1(head, sha1)) + return -1; + type = sha1_object_info(sha1, &size); + if(type == OBJ_COMMIT && path) { + commit = lookup_commit_reference(sha1); + match_path = path; + matched_sha1 = sha1; + found_path = 0; + read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); + if (!found_path) + return -1; + type = sha1_object_info(sha1, &size); + } + if (type == OBJ_BAD) + return -1; + buf = read_sha1_file(sha1, &type, &size); + if (!buf) + return -1; + buf[size] = '\0'; + write(htmlfd, buf, size); + return 0; +} +void cgit_print_blob(const char *hex, char *path, const char *head) +{ unsigned char sha1[20]; enum object_type type; 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; } @@ -1,6 +1,7 @@ #ifndef UI_BLOB_H #define UI_BLOB_H +extern int cgit_print_file(char *path, const char *head); extern void cgit_print_blob(const char *hex, char *path, const char *head); #endif /* UI_BLOB_H */ diff --git a/ui-commit.c b/ui-commit.c index f5b0ae5..2b4f677 100644 --- a/ui-commit.c +++ b/ui-commit.c @@ -3,94 +3,110 @@ * 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) +void cgit_print_commit(char *hex, const char *prefix) { struct commit *commit, *parent; - struct commitinfo *info; + struct commitinfo *info, *parent_info; struct commit_list *p; + struct strbuf notes = STRBUF_INIT; unsigned char sha1[20]; - char *tmp; + char *tmp, *tmp2; 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); + format_note(NULL, sha1, ¬es, PAGE_ENCODING, 0); + load_ref_decorations(DECORATE_FULL_REFS); html("<table summary='commit info' class='commit-info'>\n"); html("<tr><th>author</th><td>"); html_txt(info->author); 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); 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); + cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix, 0); html(" ("); - cgit_patch_link("patch", NULL, NULL, NULL, tmp); + cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix); + html(") ("); + if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff)) + cgit_commit_link("unidiff", NULL, NULL, ctx.qry.head, tmp, prefix, 1); + else + cgit_commit_link("side-by-side diff", NULL, NULL, ctx.qry.head, tmp, prefix, 1); 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); + if (prefix) { + html(" /"); + cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix); + } 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)); + tmp = tmp2 = sha1_to_hex(p->item->object.sha1); + if (ctx.repo->enable_subject_links) { + parent_info = cgit_parse_commit(parent); + tmp2 = parent_info->subject; + } + cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix, 0); html(" ("); cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex, - sha1_to_hex(p->item->object.sha1), NULL); + sha1_to_hex(p->item->object.sha1), prefix, 0); 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) @@ -98,21 +114,33 @@ void cgit_print_commit(char *hex) 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 (notes.len != 0) { + html("<div class='notes-header'>Notes</div>"); + html("<div class='notes'>"); + if (ctx.repo->commit_filter) + cgit_open_filter(ctx.repo->commit_filter); + html_txt(notes.buf); + if (ctx.repo->commit_filter) + cgit_close_filter(ctx.repo->commit_filter); + html("</div>"); + html("<div class='notes-footer'></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_print_diff(ctx.qry.sha1, tmp, prefix); } + strbuf_release(¬es); cgit_free_commitinfo(info); } diff --git a/ui-commit.h b/ui-commit.h index 40bcb31..8198b4b 100644 --- a/ui-commit.h +++ b/ui-commit.h @@ -1,6 +1,6 @@ #ifndef UI_COMMIT_H #define UI_COMMIT_H -extern void cgit_print_commit(char *hex); +extern void cgit_print_commit(char *hex, const char *prefix); #endif /* UI_COMMIT_H */ @@ -1,46 +1,48 @@ /* ui-diff.c: show diff between two blobs * * 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-ssdiff.h" unsigned char old_rev_sha1[20]; unsigned char new_rev_sha1[20]; static int files, slots; static int total_adds, total_rems, max_changes; static int lines_added, lines_removed; static struct fileinfo { char status; unsigned char old_sha1[20]; unsigned char new_sha1[20]; unsigned short old_mode; unsigned short new_mode; char *old_path; char *new_path; unsigned int added; unsigned int removed; unsigned long old_size; unsigned long new_size; int binary:1; } *items; +static int use_ssdiff = 0; static void print_fileinfo(struct fileinfo *info) { char *class; switch (info->status) { case DIFF_STATUS_ADDED: class = "add"; break; case DIFF_STATUS_COPIED: class = "cpy"; break; @@ -74,25 +76,25 @@ static void print_fileinfo(struct fileinfo *info) cgit_print_filemode(info->new_mode); } if (info->old_mode != info->new_mode && !is_null_sha1(info->old_sha1) && !is_null_sha1(info->new_sha1)) { html("<span class='modechange'>["); cgit_print_filemode(info->old_mode); html("]</span>"); } htmlf("</td><td class='%s'>", class); cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, - ctx.qry.sha2, info->new_path); + ctx.qry.sha2, info->new_path, 0); if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) htmlf(" (%s from %s)", info->status == DIFF_STATUS_COPIED ? "copied" : "renamed", info->old_path); html("</td><td class='right'>"); if (info->binary) { htmlf("bin</td><td class='graph'>%d -> %d bytes", info->old_size, info->new_size); return; } htmlf("%d", info->added + info->removed); html("</td><td class='graph'>"); @@ -116,25 +118,25 @@ static void count_diff_lines(char *line, int len) } } static void inspect_filepair(struct diff_filepair *pair) { int binary = 0; unsigned long old_size = 0; unsigned long new_size = 0; files++; lines_added = 0; lines_removed = 0; cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, &new_size, - &binary, count_diff_lines); + &binary, 0, ctx.qry.ignorews, count_diff_lines); if (files >= slots) { if (slots == 0) slots = 4; else slots = slots * 2; items = xrealloc(items, slots * sizeof(struct fileinfo)); } items[files-1].status = pair->status; hashcpy(items[files-1].old_sha1, pair->one->sha1); hashcpy(items[files-1].new_sha1, pair->two->sha1); items[files-1].old_mode = pair->one->mode; items[files-1].new_mode = pair->two->mode; @@ -143,35 +145,51 @@ static void inspect_filepair(struct diff_filepair *pair) items[files-1].added = lines_added; items[files-1].removed = lines_removed; items[files-1].old_size = old_size; items[files-1].new_size = new_size; items[files-1].binary = binary; if (lines_added + lines_removed > max_changes) max_changes = lines_added + lines_removed; total_adds += lines_added; total_rems += lines_removed; } void cgit_print_diffstat(const unsigned char *old_sha1, - const unsigned char *new_sha1) + const unsigned char *new_sha1, const char *prefix) { - int i; + int i, save_context = ctx.qry.context; html("<div class='diffstat-header'>"); cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1, - ctx.qry.sha2, NULL); + ctx.qry.sha2, NULL, 0); + if (prefix) + htmlf(" (limited to '%s')", prefix); + html(" ("); + ctx.qry.context = (save_context > 0 ? save_context : 3) << 1; + cgit_self_link("more", NULL, NULL, &ctx); + html("/"); + ctx.qry.context = (save_context > 3 ? save_context : 3) >> 1; + cgit_self_link("less", NULL, NULL, &ctx); + ctx.qry.context = save_context; + html(" context)"); + html(" ("); + ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2; + cgit_self_link(ctx.qry.ignorews ? "ignore" : "show", NULL, NULL, &ctx); + ctx.qry.ignorews = (ctx.qry.ignorews + 1) % 2; + html(" whitespace changes)"); html("</div>"); html("<table summary='diffstat' class='diffstat'>"); max_changes = 0; - cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, NULL); + cgit_diff_tree(old_sha1, new_sha1, inspect_filepair, prefix, + ctx.qry.ignorews); for(i = 0; i<files; i++) print_fileinfo(&items[i]); html("</table>"); html("<div class='diffstat-summary'>"); htmlf("%d files changed, %d insertions, %d deletions", files, total_adds, total_rems); html("</div>"); } /* * print a single line returned from xdiff @@ -237,44 +255,73 @@ static void header(unsigned char *sha1, char *path1, int mode1, else html_txt(path1); html("<br/>+++ b/"); if (mode2 != 0) cgit_tree_link(path2, NULL, NULL, ctx.qry.head, sha1_to_hex(new_rev_sha1), path2); else html_txt(path2); } html("</div>"); } +static void print_ssdiff_link() +{ + if (!strcmp(ctx.qry.page, "diff")) { + if (use_ssdiff) + cgit_diff_link("Unidiff", NULL, NULL, ctx.qry.head, + ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1); + else + cgit_diff_link("Side-by-side diff", NULL, NULL, + ctx.qry.head, ctx.qry.sha1, + ctx.qry.sha2, ctx.qry.path, 1); + } +} + static void filepair_cb(struct diff_filepair *pair) { unsigned long old_size = 0; unsigned long new_size = 0; int binary = 0; + linediff_fn print_line_fn = print_line; + if (use_ssdiff) { + cgit_ssdiff_header_begin(); + print_line_fn = cgit_ssdiff_line_cb; + } header(pair->one->sha1, pair->one->path, pair->one->mode, pair->two->sha1, pair->two->path, pair->two->mode); + if (use_ssdiff) + cgit_ssdiff_header_end(); 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); + print_line_fn(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); + print_line_fn(fmt("+Subproject %s", sha1_to_hex(pair->two->sha1)), 52); + if (use_ssdiff) + cgit_ssdiff_footer(); return; } - if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, - &new_size, &binary, print_line)) + if (cgit_diff_files(pair->one->sha1, pair->two->sha1, &old_size, + &new_size, &binary, ctx.qry.context, + ctx.qry.ignorews, print_line_fn)) cgit_print_error("Error running diff"); - if (binary) - html("Binary files differ"); + if (binary) { + if (use_ssdiff) + html("<tr><td colspan='4'>Binary files differ</td></tr>"); + else + html("Binary files differ"); + } + if (use_ssdiff) + cgit_ssdiff_footer(); } void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefix) { enum object_type type; unsigned long size; struct commit *commit, *commit2; if (!new_rev) new_rev = ctx.qry.head; get_sha1(new_rev, new_rev_sha1); type = sha1_object_info(new_rev_sha1, &size); @@ -294,20 +341,31 @@ void cgit_print_diff(const char *new_rev, const char *old_rev, const char *prefi hashclr(old_rev_sha1); if (!is_null_sha1(old_rev_sha1)) { type = sha1_object_info(old_rev_sha1, &size); if (type == OBJ_BAD) { cgit_print_error(fmt("Bad object name: %s", sha1_to_hex(old_rev_sha1))); return; } commit2 = lookup_commit_reference(old_rev_sha1); if (!commit2 || parse_commit(commit2)) cgit_print_error(fmt("Bad commit: %s", sha1_to_hex(old_rev_sha1))); } - cgit_print_diffstat(old_rev_sha1, new_rev_sha1); - html("<table summary='diff' class='diff'>"); - html("<tr><td>"); - cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix); - html("</td></tr>"); + if ((ctx.qry.ssdiff && !ctx.cfg.ssdiff) || (!ctx.qry.ssdiff && ctx.cfg.ssdiff)) + use_ssdiff = 1; + + print_ssdiff_link(); + cgit_print_diffstat(old_rev_sha1, new_rev_sha1, prefix); + + if (use_ssdiff) { + html("<table summary='ssdiff' class='ssdiff'>"); + } else { + html("<table summary='diff' class='diff'>"); + html("<tr><td>"); + } + cgit_diff_tree(old_rev_sha1, new_rev_sha1, filepair_cb, prefix, + ctx.qry.ignorews); + if (!use_ssdiff) + html("</td></tr>"); html("</table>"); } @@ -24,107 +24,123 @@ void count_lines(char *line, int size) 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); + &new_size, &binary, 0, ctx.qry.ignorews, + 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); + cgit_log_link(buf, NULL, "branch-deco", buf, NULL, + ctx.qry.vpath, 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); + sha1_to_hex(commit->object.sha1), + ctx.qry.vpath, 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)); + sha1_to_hex(commit->object.sha1), + ctx.qry.vpath, 0); } 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); + tmp = cgit_fileurl(ctx.repo->url, "commit", ctx.qry.vpath, 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)); + sha1_to_hex(commit->object.sha1), ctx.qry.vpath, 0); 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) { + struct strbuf notes = STRBUF_INIT; + format_note(NULL, commit->object.sha1, ¬es, PAGE_ENCODING, 0); + 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"); + if (notes.len != 0) { + html("<tr class='nohover'>"); + html("<td class='lognotes-label'>Notes:</td>"); + htmlf("<td colspan='%d' class='lognotes'>", + cols); + html_txt(notes.buf); + html("</td></tr>\n"); + } + strbuf_release(¬es); } 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; @@ -137,28 +153,31 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern { 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 (grep && pattern) { + if (!strcmp(grep, "grep") || !strcmp(grep, "author") || + !strcmp(grep, "committer")) + argv[argc++] = fmt("--%s=%s", grep, pattern); + if (!strcmp(grep, "range")) + argv[1] = 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(DECORATE_FULL_REFS); @@ -167,25 +186,25 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern 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.vpath, 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++; } } @@ -204,31 +223,31 @@ void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern 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, + ctx.qry.sha1, ctx.qry.vpath, 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, + ctx.qry.sha1, ctx.qry.vpath, 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); + cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, + ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg); html("</td></tr>\n"); } } @@ -62,31 +62,31 @@ static void filepair_cb(struct diff_filepair *pair) 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)) + &new_size, &binary, 0, 0, print_line)) html("Error running diff"); if (binary) html("Binary files differ\n"); } -void cgit_print_patch(char *hex) +void cgit_print_patch(char *hex, const char *prefix) { 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; @@ -113,17 +113,19 @@ void cgit_print_patch(char *hex) 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); + if (prefix) + htmlf("(limited to '%s')\n\n", prefix); + cgit_diff_tree(old_sha1, sha1, filepair_cb, prefix, 0); html("--\n"); htmlf("cgit %s\n", CGIT_VERSION); cgit_free_commitinfo(info); } @@ -1,6 +1,6 @@ #ifndef UI_PATCH_H #define UI_PATCH_H -extern void cgit_print_patch(char *hex); +extern void cgit_print_patch(char *hex, const char *prefix); #endif /* UI_PATCH_H */ @@ -1,94 +1,146 @@ /* 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_baselen; int match; static void print_object(const unsigned char *sha1, const char *path) { enum object_type type; 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 = NULL; ext = strrchr(path, '.'); if (ext && *(++ext)) { - mime = string_list_lookup(ext, &ctx.cfg.mimetypes); + mime = string_list_lookup(&ctx.cfg.mimetypes, ext); 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 void print_dir(const unsigned char *sha1, const char *path, + const char *base) +{ + char *fullpath; + if (path[0] || base[0]) + fullpath = fmt("/%s%s/", base, path); + else + fullpath = "/"; + ctx.page.etag = sha1_to_hex(sha1); + cgit_print_http_headers(&ctx); + htmlf("<html><head><title>%s</title></head>\n<body>\n" + " <h2>%s</h2>\n <ul>\n", fullpath, fullpath); + if (path[0] || base[0]) + html(" <li><a href=\"../\">../</a></li>\n"); + match = 2; +} + +static void print_dir_entry(const unsigned char *sha1, const char *path, + unsigned mode) +{ + const char *sep = ""; + if (S_ISDIR(mode)) + sep = "/"; + htmlf(" <li><a href=\"%s%s\">%s%s</a></li>\n", path, sep, path, sep); + match = 2; +} + +static void print_dir_tail(void) +{ + html(" </ul>\n</body></html>\n"); +} + 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)) + if (baselen == match_baselen) { + if (S_ISREG(mode)) + print_object(sha1, pathname); + else if (S_ISDIR(mode)) { + print_dir(sha1, pathname, base); + return READ_TREE_RECURSIVE; + } + } + else if (baselen > match_baselen) + print_dir_entry(sha1, pathname, mode); + else if (S_ISDIR(mode)) return READ_TREE_RECURSIVE; - if (S_ISREG(mode) && !strncmp(base, match_path, baselen) && - !strcmp(pathname, match_path + baselen)) - print_object(sha1, pathname); + return 0; +} +static int basedir_len(const char *path) +{ + char *p = strrchr(path, '/'); + if (p) + return p - path + 1; 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; + if (!paths[0]) { + paths[0] = ""; + match_baselen = -1; + print_dir(commit->tree->object.sha1, "", ""); + } + else + match_baselen = basedir_len(paths[0]); read_tree_recursive(commit->tree, "", 0, 0, paths, walk_tree, NULL); if (!match) html_status(404, "Not found", 0); + else if (match == 2) + print_dir_tail(); } @@ -67,25 +67,25 @@ 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); + cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL, 0); 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; } @@ -180,24 +180,26 @@ 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 (ctx.repo->enable_remote_branches) + for_each_remote_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]); diff --git a/ui-shared.c b/ui-shared.c index 8a7cc32..ae29615 100644 --- a/ui-shared.c +++ b/ui-shared.c @@ -18,25 +18,25 @@ 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", "Nov", "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) +void cgit_print_error(const char *msg) { html("<div class='error'>"); html_txt(msg); html("</div>\n"); } char *cgit_httpscheme() { if (ctx.env.https && !strcmp(ctx.env.https, "on")) return "https://"; else return "http://"; @@ -124,25 +124,25 @@ const char *cgit_repobasename(const char *reponame) 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) +static void site_url(const char *page, const 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); @@ -151,53 +151,53 @@ static void site_url(char *page, char *search, int ofs) 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) +static void site_link(const char *page, const char *name, const char *title, + const char *class, const 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) +void cgit_index_link(const char *name, const char *title, const char *class, + const 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) +static char *repolink(const char *title, const char *class, const char *page, + const char *head, const char *path) { char *delim = "?"; html("<a"); if (title) { html(" title='"); html_attr(title); html("'"); } if (class) { html(" class='"); html_attr(class); @@ -231,66 +231,68 @@ static char *repolink(char *title, char *class, char *page, char *head, } 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) +static void reporevlink(const char *page, const char *name, const char *title, + const char *class, const char *head, const char *rev, + const char *path) { char *delim; delim = repolink(title, class, page, head, path); if (rev && ctx.qry.head != NULL && 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) +void cgit_summary_link(const char *name, const char *title, const char *class, + const char *head) { reporevlink(NULL, name, title, class, head, NULL, NULL); } -void cgit_tag_link(char *name, char *title, char *class, char *head, - char *rev) +void cgit_tag_link(const char *name, const char *title, const char *class, + const char *head, const 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) +void cgit_tree_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, const 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) +void cgit_plain_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, const 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) +void cgit_log_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, const char *path, + int ofs, const char *grep, const 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); @@ -307,119 +309,225 @@ void cgit_log_link(char *name, char *title, char *class, char *head, 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) +void cgit_commit_link(char *name, const char *title, const char *class, + const char *head, const char *rev, const char *path, + int toggle_ssdiff) { 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); + + char *delim; + + delim = repolink(title, class, "commit", head, path); + if (rev && strcmp(rev, ctx.qry.head)) { + html(delim); + html("id="); + html_url_arg(rev); + delim = "&"; + } + if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { + html(delim); + html("ss=1"); + delim = "&"; + } + if (ctx.qry.context > 0 && ctx.qry.context != 3) { + html(delim); + html("context="); + htmlf("%d", ctx.qry.context); + delim = "&"; + } + if (ctx.qry.ignorews) { + html(delim); + html("ignorews=1"); + delim = "&"; + } + html("'>"); + html_txt(name); + html("</a>"); } -void cgit_refs_link(char *name, char *title, char *class, char *head, - char *rev, char *path) +void cgit_refs_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, const 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) +void cgit_snapshot_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, + const 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) +void cgit_diff_link(const char *name, const char *title, const char *class, + const char *head, const char *new_rev, const char *old_rev, + const char *path, int toggle_ssdiff) { char *delim; delim = repolink(title, class, "diff", head, path); if (new_rev && ctx.qry.head != NULL && 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); + delim = "&"; + } + if ((ctx.qry.ssdiff && !toggle_ssdiff) || (!ctx.qry.ssdiff && toggle_ssdiff)) { + html(delim); + html("ss=1"); + delim = "&"; + } + if (ctx.qry.context > 0 && ctx.qry.context != 3) { + html(delim); + html("context="); + htmlf("%d", ctx.qry.context); + delim = "&"; + } + if (ctx.qry.ignorews) { + html(delim); + html("ignorews=1"); + delim = "&"; } html("'>"); html_txt(name); html("</a>"); } -void cgit_patch_link(char *name, char *title, char *class, char *head, - char *rev) +void cgit_patch_link(const char *name, const char *title, const char *class, + const char *head, const char *rev, const char *path) { - reporevlink("patch", name, title, class, head, rev, NULL); + reporevlink("patch", name, title, class, head, rev, path); } -void cgit_stats_link(char *name, char *title, char *class, char *head, - char *path) +void cgit_stats_link(const char *name, const char *title, const char *class, + const char *head, const char *path) { reporevlink("stats", name, title, class, head, NULL, path); } +void cgit_self_link(char *name, const char *title, const char *class, + struct cgit_context *ctx) +{ + if (!strcmp(ctx->qry.page, "repolist")) + return cgit_index_link(name, title, class, ctx->qry.search, + ctx->qry.ofs); + else if (!strcmp(ctx->qry.page, "summary")) + return cgit_summary_link(name, title, class, ctx->qry.head); + else if (!strcmp(ctx->qry.page, "tag")) + return cgit_tag_link(name, title, class, ctx->qry.head, + ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL); + else if (!strcmp(ctx->qry.page, "tree")) + return cgit_tree_link(name, title, class, ctx->qry.head, + ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, + ctx->qry.path); + else if (!strcmp(ctx->qry.page, "plain")) + return cgit_plain_link(name, title, class, ctx->qry.head, + ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, + ctx->qry.path); + else if (!strcmp(ctx->qry.page, "log")) + return cgit_log_link(name, title, class, ctx->qry.head, + ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, + ctx->qry.path, ctx->qry.ofs, + ctx->qry.grep, ctx->qry.search, + ctx->qry.showmsg); + else if (!strcmp(ctx->qry.page, "commit")) + return cgit_commit_link(name, title, class, ctx->qry.head, + ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, + ctx->qry.path, 0); + else if (!strcmp(ctx->qry.page, "patch")) + return cgit_patch_link(name, title, class, ctx->qry.head, + ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, + ctx->qry.path); + else if (!strcmp(ctx->qry.page, "refs")) + return cgit_refs_link(name, title, class, ctx->qry.head, + ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, + ctx->qry.path); + else if (!strcmp(ctx->qry.page, "snapshot")) + return cgit_snapshot_link(name, title, class, ctx->qry.head, + ctx->qry.has_sha1 ? ctx->qry.sha1 : NULL, + ctx->qry.path); + else if (!strcmp(ctx->qry.page, "diff")) + return cgit_diff_link(name, title, class, ctx->qry.head, + ctx->qry.sha1, ctx->qry.sha2, + ctx->qry.path, 0); + else if (!strcmp(ctx->qry.page, "stats")) + return cgit_stats_link(name, title, class, ctx->qry.head, + ctx->qry.path); + + /* Don't known how to make link for this page */ + repolink(title, class, ctx->qry.page, ctx->qry.head, ctx->qry.path); + html("><!-- cgit_self_link() doesn't know how to make link for page '"); + html_txt(ctx->qry.page); + html("' -->"); + html_txt(name); + html("</a>"); +} + 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); + ctx.qry.head, fullrev, NULL, 0); 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) +void cgit_print_date(time_t secs, const 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) +void cgit_print_age(time_t t, time_t max_relative, const 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; } @@ -500,25 +608,25 @@ void cgit_print_docstart(struct cgit_context *ctx) 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='"); html(cgit_httpscheme()); html_attr(cgit_hosturl()); - html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.path, + html_attr(cgit_fileurl(ctx->repo->url, "atom", ctx->qry.vpath, fmt("h=%s", ctx->qry.head))); 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() @@ -580,59 +688,79 @@ int print_archive_ref(const char *refname, const unsigned char *sha1, 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) +void cgit_add_hidden_formfields(int incl_head, int incl_search, + const 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); + if (ctx.qry.vpath) + url = fmt("%s/%s", url, ctx.qry.vpath); 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"; +static const char *hc(struct cgit_context *ctx, const char *page) +{ + return strcmp(ctx->qry.page, page) ? NULL : "active"; +} -char *hc(struct cgit_cmd *cmd, const char *page) +static void cgit_print_path_crumbs(struct cgit_context *ctx, char *path) { - return (strcmp(cmd ? cmd->name : fallback_cmd, page) ? NULL : "active"); + char *old_path = ctx->qry.path; + char *p = path, *q, *end = path + strlen(path); + + ctx->qry.path = NULL; + cgit_self_link("root", NULL, NULL, ctx); + ctx->qry.path = p = path; + while (p < end) { + if (!(q = strchr(p, '/'))) + q = end; + *q = '\0'; + html_txt("/"); + cgit_self_link(p, NULL, NULL, ctx); + if (q < end) + *q = '/'; + p = q + 1; + } + ctx->qry.path = old_path; } static void print_header(struct cgit_context *ctx) { html("<table id='header'>\n"); html("<tr>\n"); if (ctx->cfg.logo && ctx->cfg.logo[0] != 0) { html("<td class='logo' rowspan='2'><a href='"); if (ctx->cfg.logo_link) html_attr(ctx->cfg.logo_link); else @@ -666,108 +794,116 @@ static void print_header(struct cgit_context *ctx) 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"), + cgit_summary_link("summary", NULL, hc(ctx, "summary"), ctx->qry.head); - cgit_refs_link("refs", NULL, hc(cmd, "refs"), ctx->qry.head, + cgit_refs_link("refs", NULL, hc(ctx, "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); + cgit_log_link("log", NULL, hc(ctx, "log"), ctx->qry.head, + NULL, ctx->qry.vpath, 0, NULL, NULL, + ctx->qry.showmsg); + cgit_tree_link("tree", NULL, hc(ctx, "tree"), ctx->qry.head, + ctx->qry.sha1, ctx->qry.vpath); + cgit_commit_link("commit", NULL, hc(ctx, "commit"), + ctx->qry.head, ctx->qry.sha1, ctx->qry.vpath, 0); + cgit_diff_link("diff", NULL, hc(ctx, "diff"), ctx->qry.head, + ctx->qry.sha1, ctx->qry.sha2, ctx->qry.vpath, 0); if (ctx->repo->max_stats) - cgit_stats_link("stats", NULL, hc(cmd, "stats"), - ctx->qry.head, NULL); + cgit_stats_link("stats", NULL, hc(ctx, "stats"), + ctx->qry.head, ctx->qry.vpath); if (ctx->repo->readme) reporevlink("about", "about", NULL, - hc(cmd, "about"), ctx->qry.head, NULL, + hc(ctx, "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)); + ctx->qry.vpath, 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_option("range", "range", 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); + site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, 0); if (ctx->cfg.root_readme) - site_link("about", "about", NULL, hc(cmd, "about"), + site_link("about", "about", NULL, hc(ctx, "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"); + if (ctx->qry.vpath) { + html("<div class='path'>"); + html("path: "); + cgit_print_path_crumbs(ctx, ctx->qry.vpath); + html("</div>"); + } 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 *prefix; char *filename; + unsigned char sha1[20]; + if (get_sha1(fmt("refs/tags/%s", hex), sha1) == 0 && + (hex[0] == 'v' || hex[0] == 'V') && isdigit(hex[1])) + hex++; + prefix = xstrdup(fmt("%s-%s", cgit_repobasename(repo), hex)); for (f = cgit_snapshot_formats; f->suffix; f++) { if (!(snapshots & f->bit)) continue; - filename = fmt("%s-%s%s", cgit_repobasename(repo), hex, - f->suffix); + filename = fmt("%s%s", prefix, f->suffix); cgit_snapshot_link(filename, NULL, NULL, NULL, NULL, filename); html("<br/>"); } } diff --git a/ui-shared.h b/ui-shared.h index b12aa89..3cc1258 100644 --- a/ui-shared.h +++ b/ui-shared.h @@ -1,51 +1,66 @@ #ifndef UI_SHARED_H #define UI_SHARED_H extern char *cgit_httpscheme(); extern char *cgit_hosturl(); extern char *cgit_rooturl(); 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_index_link(const char *name, const char *title, + const char *class, const char *pattern, int ofs); +extern void cgit_summary_link(const char *name, const char *title, + const char *class, const char *head); +extern void cgit_tag_link(const char *name, const char *title, + const char *class, const char *head, + const char *rev); +extern void cgit_tree_link(const char *name, const char *title, + const char *class, const char *head, + const char *rev, const char *path); +extern void cgit_plain_link(const char *name, const char *title, + const char *class, const char *head, + const char *rev, const char *path); +extern void cgit_log_link(const char *name, const char *title, + const char *class, const char *head, const char *rev, + const char *path, int ofs, const char *grep, + const char *pattern, int showmsg); +extern void cgit_commit_link(char *name, const char *title, + const char *class, const char *head, + const char *rev, const char *path, + int toggle_ssdiff); +extern void cgit_patch_link(const char *name, const char *title, + const char *class, const char *head, + const char *rev, const char *path); +extern void cgit_refs_link(const char *name, const char *title, + const char *class, const char *head, + const char *rev, const char *path); +extern void cgit_snapshot_link(const char *name, const char *title, + const char *class, const char *head, + const char *rev, const char *archivename); +extern void cgit_diff_link(const char *name, const char *title, + const char *class, const char *head, + const char *new_rev, const char *old_rev, + const char *path, int toggle_ssdiff); +extern void cgit_stats_link(const char *name, const char *title, + const char *class, const char *head, + const char *path); +extern void cgit_self_link(char *name, const char *title, + const char *class, struct cgit_context *ctx); 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_error(const char *msg); +extern void cgit_print_date(time_t secs, const char *format, int local_time); +extern void cgit_print_age(time_t t, time_t max_relative, const 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); + const char *page); #endif /* UI_SHARED_H */ diff --git a/ui-snapshot.c b/ui-snapshot.c index 4136b3e..1b25dca 100644 --- a/ui-snapshot.c +++ b/ui-snapshot.c @@ -26,29 +26,35 @@ static int write_compressed_tar_archive(struct archiver_args *args,const char *f } 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"); } +static int write_tar_xz_archive(struct archiver_args *args) +{ + return write_compressed_tar_archive(args,"xz"); +} + 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 }, + { ".zip", "application/x-zip", write_zip_archive, 0x01 }, + { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x02 }, + { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x04 }, + { ".tar", "application/x-tar", write_tar_archive, 0x08 }, + { ".tar.xz", "application/x-xz", write_tar_xz_archive, 0x10 }, {} }; 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) diff --git a/ui-ssdiff.c b/ui-ssdiff.c new file mode 100644 index 0000000..408e620 --- a/dev/null +++ b/ui-ssdiff.c @@ -0,0 +1,369 @@ +#include "cgit.h" +#include "html.h" +#include "ui-shared.h" + +extern int use_ssdiff; + +static int current_old_line, current_new_line; + +struct deferred_lines { + int line_no; + char *line; + struct deferred_lines *next; +}; + +static struct deferred_lines *deferred_old, *deferred_old_last; +static struct deferred_lines *deferred_new, *deferred_new_last; + +static char *longest_common_subsequence(char *A, char *B) +{ + int i, j, ri; + int m = strlen(A); + int n = strlen(B); + int L[m + 1][n + 1]; + int tmp1, tmp2; + int lcs_length; + char *result; + + for (i = m; i >= 0; i--) { + for (j = n; j >= 0; j--) { + if (A[i] == '\0' || B[j] == '\0') { + L[i][j] = 0; + } else if (A[i] == B[j]) { + L[i][j] = 1 + L[i + 1][j + 1]; + } else { + tmp1 = L[i + 1][j]; + tmp2 = L[i][j + 1]; + L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2); + } + } + } + + lcs_length = L[0][0]; + result = xmalloc(lcs_length + 2); + memset(result, 0, sizeof(*result) * (lcs_length + 2)); + + ri = 0; + i = 0; + j = 0; + while (i < m && j < n) { + if (A[i] == B[j]) { + result[ri] = A[i]; + ri += 1; + i += 1; + j += 1; + } else if (L[i + 1][j] >= L[i][j + 1]) { + i += 1; + } else { + j += 1; + } + } + return result; +} + +static int line_from_hunk(char *line, char type) +{ + char *buf1, *buf2; + int len; + + buf1 = strchr(line, type); + if (buf1 == NULL) + return 0; + buf1 += 1; + buf2 = strchr(buf1, ','); + if (buf2 == NULL) + return 0; + len = buf2 - buf1; + buf2 = xmalloc(len + 1); + strncpy(buf2, buf1, len); + buf2[len] = '\0'; + int res = atoi(buf2); + free(buf2); + return res; +} + +static char *replace_tabs(char *line) +{ + char *prev_buf = line; + char *cur_buf; + int linelen = strlen(line); + int n_tabs = 0; + int i; + char *result; + char *spaces = " "; + + if (linelen == 0) { + result = xmalloc(1); + result[0] = '\0'; + return result; + } + + for (i = 0; i < linelen; i++) + if (line[i] == '\t') + n_tabs += 1; + result = xmalloc(linelen + n_tabs * 8 + 1); + result[0] = '\0'; + + while (1) { + cur_buf = strchr(prev_buf, '\t'); + if (!cur_buf) { + strcat(result, prev_buf); + break; + } else { + strcat(result, " "); + strncat(result, spaces, 8 - (strlen(result) % 8)); + strncat(result, prev_buf, cur_buf - prev_buf); + } + prev_buf = cur_buf + 1; + } + return result; +} + +static int calc_deferred_lines(struct deferred_lines *start) +{ + struct deferred_lines *item = start; + int result = 0; + while (item) { + result += 1; + item = item->next; + } + return result; +} + +static void deferred_old_add(char *line, int line_no) +{ + struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines)); + item->line = xstrdup(line); + item->line_no = line_no; + item->next = NULL; + if (deferred_old) { + deferred_old_last->next = item; + deferred_old_last = item; + } else { + deferred_old = deferred_old_last = item; + } +} + +static void deferred_new_add(char *line, int line_no) +{ + struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines)); + item->line = xstrdup(line); + item->line_no = line_no; + item->next = NULL; + if (deferred_new) { + deferred_new_last->next = item; + deferred_new_last = item; + } else { + deferred_new = deferred_new_last = item; + } +} + +static void print_part_with_lcs(char *class, char *line, char *lcs) +{ + int line_len = strlen(line); + int i, j; + char c[2] = " "; + int same = 1; + + j = 0; + for (i = 0; i < line_len; i++) { + c[0] = line[i]; + if (same) { + if (line[i] == lcs[j]) + j += 1; + else { + same = 0; + htmlf("<span class='%s'>", class); + } + } else if (line[i] == lcs[j]) { + same = 1; + htmlf("</span>"); + j += 1; + } + html_txt(c); + } +} + +static void print_ssdiff_line(char *class, + int old_line_no, + char *old_line, + int new_line_no, + char *new_line, int individual_chars) +{ + char *lcs = NULL; + if (old_line) + old_line = replace_tabs(old_line + 1); + if (new_line) + new_line = replace_tabs(new_line + 1); + if (individual_chars && old_line && new_line) + lcs = longest_common_subsequence(old_line, new_line); + html("<tr>"); + if (old_line_no > 0) + htmlf("<td class='lineno'>%d</td><td class='%s'>", + old_line_no, class); + else if (old_line) + htmlf("<td class='lineno'></td><td class='%s'>", class); + else + htmlf("<td class='lineno'></td><td class='%s_dark'>", class); + if (old_line) { + if (lcs) + print_part_with_lcs("del", old_line, lcs); + else + html_txt(old_line); + } + + html("</td>"); + if (new_line_no > 0) + htmlf("<td class='lineno'>%d</td><td class='%s'>", + new_line_no, class); + else if (new_line) + htmlf("<td class='lineno'></td><td class='%s'>", class); + else + htmlf("<td class='lineno'></td><td class='%s_dark'>", class); + if (new_line) { + if (lcs) + print_part_with_lcs("add", new_line, lcs); + else + html_txt(new_line); + } + + html("</td></tr>"); + if (lcs) + free(lcs); + if (new_line) + free(new_line); + if (old_line) + free(old_line); +} + +static void print_deferred_old_lines() +{ + struct deferred_lines *iter_old, *tmp; + iter_old = deferred_old; + while (iter_old) { + print_ssdiff_line("del", iter_old->line_no, + iter_old->line, -1, NULL, 0); + tmp = iter_old->next; + free(iter_old); + iter_old = tmp; + } +} + +static void print_deferred_new_lines() +{ + struct deferred_lines *iter_new, *tmp; + iter_new = deferred_new; + while (iter_new) { + print_ssdiff_line("add", -1, NULL, + iter_new->line_no, iter_new->line, 0); + tmp = iter_new->next; + free(iter_new); + iter_new = tmp; + } +} + +static void print_deferred_changed_lines() +{ + struct deferred_lines *iter_old, *iter_new, *tmp; + int n_old_lines = calc_deferred_lines(deferred_old); + int n_new_lines = calc_deferred_lines(deferred_new); + int individual_chars = (n_old_lines == n_new_lines ? 1 : 0); + + iter_old = deferred_old; + iter_new = deferred_new; + while (iter_old || iter_new) { + if (iter_old && iter_new) + print_ssdiff_line("changed", iter_old->line_no, + iter_old->line, + iter_new->line_no, iter_new->line, + individual_chars); + else if (iter_old) + print_ssdiff_line("changed", iter_old->line_no, + iter_old->line, -1, NULL, 0); + else if (iter_new) + print_ssdiff_line("changed", -1, NULL, + iter_new->line_no, iter_new->line, 0); + if (iter_old) { + tmp = iter_old->next; + free(iter_old); + iter_old = tmp; + } + + if (iter_new) { + tmp = iter_new->next; + free(iter_new); + iter_new = tmp; + } + } +} + +void cgit_ssdiff_print_deferred_lines() +{ + if (!deferred_old && !deferred_new) + return; + if (deferred_old && !deferred_new) + print_deferred_old_lines(); + else if (!deferred_old && deferred_new) + print_deferred_new_lines(); + else + print_deferred_changed_lines(); + deferred_old = deferred_old_last = NULL; + deferred_new = deferred_new_last = NULL; +} + +/* + * print a single line returned from xdiff + */ +void cgit_ssdiff_line_cb(char *line, int len) +{ + char c = line[len - 1]; + line[len - 1] = '\0'; + if (line[0] == '@') { + current_old_line = line_from_hunk(line, '-'); + current_new_line = line_from_hunk(line, '+'); + } + + if (line[0] == ' ') { + if (deferred_old || deferred_new) + cgit_ssdiff_print_deferred_lines(); + print_ssdiff_line("ctx", current_old_line, line, + current_new_line, line, 0); + current_old_line += 1; + current_new_line += 1; + } else if (line[0] == '+') { + deferred_new_add(line, current_new_line); + current_new_line += 1; + } else if (line[0] == '-') { + deferred_old_add(line, current_old_line); + current_old_line += 1; + } else if (line[0] == '@') { + html("<tr><td colspan='4' class='hunk'>"); + html_txt(line); + html("</td></tr>"); + } else { + html("<tr><td colspan='4' class='ctx'>"); + html_txt(line); + html("</td></tr>"); + } + line[len - 1] = c; +} + +void cgit_ssdiff_header_begin() +{ + current_old_line = -1; + current_new_line = -1; + html("<tr><td class='space' colspan='4'><div></div></td></tr>"); + html("<tr><td class='head' colspan='4'>"); +} + +void cgit_ssdiff_header_end() +{ + html("</td><tr>"); +} + +void cgit_ssdiff_footer() +{ + if (deferred_old || deferred_new) + cgit_ssdiff_print_deferred_lines(); + html("<tr><td class='foot' colspan='4'></td></tr>"); +} diff --git a/ui-ssdiff.h b/ui-ssdiff.h new file mode 100644 index 0000000..64b4b12 --- a/dev/null +++ b/ui-ssdiff.h @@ -0,0 +1,13 @@ +#ifndef UI_SSDIFF_H +#define UI_SSDIFF_H + +extern void cgit_ssdiff_print_deferred_lines(); + +extern void cgit_ssdiff_line_cb(char *line, int len); + +extern void cgit_ssdiff_header_begin(); +extern void cgit_ssdiff_header_end(); + +extern void cgit_ssdiff_footer(); + +#endif /* UI_SSDIFF_H */ @@ -166,36 +166,36 @@ static void add_commit(struct string_list *authors, struct commit *commit, struct cgit_period *period) { struct commitinfo *info; struct string_list_item *author, *item; struct authorstat *authorstat; struct string_list *items; char *tmp; struct tm *date; time_t t; info = cgit_parse_commit(commit); tmp = xstrdup(info->author); - author = string_list_insert(tmp, authors); + author = string_list_insert(authors, tmp); if (!author->util) author->util = xcalloc(1, sizeof(struct authorstat)); else free(tmp); authorstat = author->util; items = &authorstat->list; t = info->committer_date; date = gmtime(&t); period->trunc(date); tmp = xstrdup(period->pretty(date)); - item = string_list_insert(tmp, items); + item = string_list_insert(items, tmp); if (item->util) free(tmp); item->util++; authorstat->total++; cgit_free_commitinfo(info); } static int cmp_total_commits(const void *a1, const void *a2) { const struct string_list_item *i1 = a1; const struct string_list_item *i2 = a2; const struct authorstat *auth1 = i1->util; @@ -270,25 +270,25 @@ void print_combined_authorrow(struct string_list *authors, int from, int to, total = 0; htmlf("<tr><td class='%s'>%s</td>", leftclass, fmt(name, to - from + 1)); for (j = 0; j < period->count; j++) { tmp = period->pretty(tm); period->inc(tm); subtotal = 0; for (i = from; i <= to; i++) { author = &authors->items[i]; authorstat = author->util; items = &authorstat->list; - date = string_list_lookup(tmp, items); + date = string_list_lookup(items, tmp); if (date) subtotal += (size_t)date->util; } htmlf("<td class='%s'>%d</td>", centerclass, subtotal); total += subtotal; } htmlf("<td class='%s'>%d</td></tr>", rightclass, total); } void print_authors(struct string_list *authors, int top, struct cgit_period *period) { @@ -322,25 +322,25 @@ void print_authors(struct string_list *authors, int top, author = &authors->items[i]; html("<tr><td class='left'>"); html_txt(author->string); html("</td>"); authorstat = author->util; items = &authorstat->list; total = 0; for (j = 0; j < period->count; j++) period->dec(tm); for (j = 0; j < period->count; j++) { tmp = period->pretty(tm); period->inc(tm); - date = string_list_lookup(tmp, items); + date = string_list_lookup(items, tmp); if (!date) html("<td>0</td>"); else { htmlf("<td>%d</td>", date->util); total += (size_t)date->util; } } htmlf("<td class='sum'>%d</td></tr>", total); } if (top < authors->nr) print_combined_authorrow(authors, top, authors->nr - 1, diff --git a/ui-summary.c b/ui-summary.c index a2c018e..02f191e 100644 --- a/ui-summary.c +++ b/ui-summary.c @@ -1,24 +1,26 @@ /* ui-summary.c: functions for generating repo summary page * * Copyright (C) 2006 Lars Hjemli + * Copyright (C) 2010 Jason A. Donenfeld <Jason@zx2c4.com> * * 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" +#include "ui-blob.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) @@ -59,34 +61,50 @@ void cgit_print_summary() 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(char *path) { - char *slash, *tmp; + char *slash, *tmp, *colon, *ref = 0; if (!ctx.repo->readme) return; if (path) { slash = strrchr(ctx.repo->readme, '/'); - if (!slash) - return; + if (!slash) { + slash = strchr(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; + colon = strchr(tmp, ':'); + if (colon && strlen(colon) > 1) { + *colon = '\0'; + ref = tmp; + tmp = colon + 1; + while ((*tmp == '/' || *tmp == ':') && *tmp != '\0') + ++tmp; + if (!(*tmp)) + return; + } html("<div id='summary'>"); if (ctx.repo->about_filter) cgit_open_filter(ctx.repo->about_filter); - html_include(tmp); + if (ref) + cgit_print_file(tmp, ref); + else + html_include(tmp); if (ctx.repo->about_filter) cgit_close_filter(ctx.repo->about_filter); html("</div>"); } @@ -21,24 +21,32 @@ static void print_tag_content(char *buf) 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 print_download_links(char *revname) +{ + html("<tr><th>download</th><td class='sha1'>"); + cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, + revname, ctx.repo->snapshots); + html("</td></tr>"); +} + 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)); @@ -47,46 +55,50 @@ void cgit_print_tag(char *revname) 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>"); + 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>"); + 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("<tr><td>tagged by</td><td>"); html_txt(info->tagger); if (info->tagger_email && !ctx.cfg.noplainemail) { html(" "); html_txt(info->tagger_email); } html("</td></tr>\n"); } - html("<tr><td>Tagged object</td><td>"); + html("<tr><td>tagged object</td><td class='sha1'>"); cgit_object_link(tag->tagged); html("</td></tr>\n"); + if (ctx.repo->snapshots) + print_download_links(revname); html("</table>\n"); print_tag_content(info->msg); } else { html("<table class='commit-info'>\n"); - htmlf("<tr><td>Tag name</td><td>"); + htmlf("<tr><td>tag name</td><td>"); html_txt(revname); html("</td></tr>\n"); - html("<tr><td>Tagged object</td><td>"); + html("<tr><td>Tagged object</td><td class='sha1'>"); cgit_object_link(obj); html("</td></tr>\n"); + if (ctx.repo->snapshots) + print_download_links(revname); html("</table>\n"); } return; } @@ -93,28 +93,34 @@ static void print_object(const unsigned char *sha1, char *path, const char *base 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(" ("); + htmlf("blob: %s (", sha1_to_hex(sha1)); cgit_plain_link("plain", NULL, NULL, ctx.qry.head, curr_rev, path); - htmlf(")<br/>blob: %s\n", sha1_to_hex(sha1)); + html(")\n"); + + if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) { + htmlf("<div class='error'>blob size (%dKB) exceeds display size limit (%dKB).</div>", + size / 1024, ctx.cfg.max_blob_size); + return; + } if (buffer_is_binary(buf, size)) print_binary_buffer(buf, size); else 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) { @@ -160,24 +166,26 @@ static int ls_item(const unsigned char *sha1, const char *base, int baselen, 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); + cgit_plain_link("plain", NULL, "button", ctx.qry.head, curr_rev, + 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>"); @@ -208,35 +216,28 @@ static void ls_tree(const unsigned char *sha1, char *path) 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, pathname); return 0; } } @@ -261,25 +262,21 @@ void cgit_print_tree(const char *rev, char *path) 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, "", 0, 0, paths, walk_tree, NULL); ls_tail(); } |